VisionFrameworkを使ったiOSアプリケーション開発(矩形検出と描画)

# はじめに
この記事は、

https://qiita.com/mikazuki_mitsutsuki/items/2402e0a126822116d188

の続きの記事である。
この記事では、VisionFrameworkの矩形検出でA4の用紙を検出し、検出したA4の用紙を画面に描画するところまでを書く。

# 矩形検出と描画
### 矩形検出
矩形検出を実装していくが、コードの大まかな構造としては、以前書いた、

https://qiita.com/mikazuki_mitsutsuki/items/6ea7acf851f1dcd66f04

この記事と一緒である。手の認識のためのクラスを矩形認識のためのクラスに置き換えればよい。
まず、矩形検出を行うオブジェクトを作り、最低限のパーツとエラー処理を実装する。
```CameraViewController.swift
    //略

    //矩形検出をするためのオブジェクト
    private func getRectangleObservations(sampleBuffer: CMSampleBuffer, completion: @escaping (([VNRectangleObservation])->())){
        
        let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer,orientation: .up, options: [:])
        do{
            try handler.perform([RectangleRequest])
        }catch{
            cameraFeedSession?.stopRunning()
            let error = AppError.visionError(error: error)
            DispatchQueue.main.async {
                error.displayInViewController(self)
            }
        }
    }
```
次に、リクエストから頂点4つの座標を取得する。
```CameraViewController.swift
    //略

    //矩形検出をするためのオブジェクト
    private func getRectangleObservations(sampleBuffer: CMSampleBuffer, completion: @escaping (([VNRectangleObservation])->())){
        
        let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer,orientation: .up, options: [:])
        do{
            try handler.perform([RectangleRequest])
            guard let observation = RectangleRequest.results?.first else{
                return
            }
            let tl = observation.topLeft
            let tr = observation.topRight
            let bl = observation.bottomLeft
            let br = observation.bottomRight
            //確認用
            print(tl,tr,bl,br)
        }catch{
            cameraFeedSession?.stopRunning()
            let error = AppError.visionError(error: error)
            DispatchQueue.main.async {
                error.displayInViewController(self)
            }
        }
    }
```
手の認識と同様、毎フレーム呼び出す。
```CameraViewController.swift
//略

extension CameraViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
    //フレームごとに呼び出されるオブジェクト
    public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
        
        //ハンドトラッキング
        getHandObservations(sampleBuffer: sampleBuffer){
            VNHumanHandPoseObservation in
        }
        //矩形検出
        getRectangleObservations(sampleBuffer: sampleBuffer){
            VNRectangleObservation in
        }
    }
}

```
この時点で、実行してみて４点の座標が表示されていれば矩形検出は成功している。
```
(0.002045193687081337, 0.7891198992729187) (0.29567036032676697, 0.8105332255363464) (0.11949463188648224, 0.24217313528060913) (0.29661551117897034, 0.2614938020706177)
```
矩形検出は成功しているため、次は画面に検出した４点を結ぶ四角形を描画をする。

### 描画
「CameraView.swift」を編集する。

これも以前書いた、

https://qiita.com/mikazuki_mitsutsuki/items/2402e0a126822116d188

この記事とほぼ同じである。

まず、矩形検出用の「CAShapeLayer」クラスと「UIBezierPath」クラスのインスタンスを生成する。次に、レイヤーの大きさを指定する。
さらに、作成したレイヤーをサブレイヤーとして追加する。
```CameraView.swift
class CameraView: UIView{
    //略
    
    private var rectangleoverLayer = CAShapeLayer()
    private var rectanglepointPath = UIBezierPath()
    
    //略

    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        if layer == previewLayer{
            handoverLayer.frame = layer.bounds
            //今回追加した行
            rectangleoverLayer.frame = layer.bounds
        }
    }
    
    private func setupOverlay(){
        previewLayer.addSublayer(rectangleoverLayer)
        //今回追加した行
        previewLayer.addSublayer(handoverLayer)
        
    }
}
```

レイヤーの準備は終わったので、描画するオブジェクトをつくる。
```CameraView.swift
    //略

    func rectangleshowPoint(tl:CGPoint,tr:CGPoint,bl:CGPoint,br:CGPoint){
        //開始位置
        rectanglepointPath.move(to: tl)
        //各頂点位置
        rectanglepointPath.addLine(to: tr)
        rectanglepointPath.addLine(to: br)
        rectanglepointPath.addLine(to: bl)
        rectanglepointPath.close()
        //ラインの太さ
        rectanglepointPath.lineWidth = 1
        //内側の色
        rectangleoverLayer.fillColor = UIColor.clear.cgColor
        //枠線の色
        rectangleoverLayer.strokeColor = UIColor.green.cgColor
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        rectangleoverLayer.path = rectanglepointPath.cgPath
        CATransaction.commit()
    }
```

次のポイントを描画するために今格納されているポイントを削除するオブジェクトを作成する。
```CameraView.swift
    //略

    func rectangleremovePoint(){
        rectanglepointPath.removeAllPoints()
    }
```
「CameraView.swift」の編集は終了。
次に、「CameraView」クラスに今作成したオブジェクトを「CameraViewController」クラスの矩形検出をするオブジェクトで呼び出す。
```CameraViewCintroller.swift
    //略

    //矩形検出をするためのオブジェクト
    private func getRectangleObservations(sampleBuffer: CMSampleBuffer, completion: @escaping (([VNRectangleObservation])->())){
        
        let handler = VNImageRequestHandler(cmSampleBuffer: sampleBuffer,orientation: .up, options: [:])
        do{
            try handler.perform([RectangleRequest])
            guard let observation = RectangleRequest.results?.first else{
                return
            }
            let tl = observation.topLeft
            let tr = observation.topRight
            let bl = observation.bottomLeft
            let br = observation.bottomRight
            print(tl,tr,bl,br)
            //ここで追加した行            
            self.cameraView.rectangleshowPoint(tl: transformation.visionToUIKit(visionPoint: tl), tr: transformation.visionToUIKit(visionPoint: tr), bl: transformation.visionToUIKit(visionPoint: bl), br: transformation.visionToUIKit(visionPoint: br))
            //ここで追加した行
            self.cameraView.rectangleremovePoint()
        }catch{
            cameraFeedSession?.stopRunning()
            let error = AppError.visionError(error: error)
            DispatchQueue.main.async {
                error.displayInViewController(self)
            }
        }
    }
```
以前作成した、Vision座標をUIKit座標に変換する関数を使い、取得した4点の座標を変換してから描画するためのオブジェクトに渡している。
ここまでで描画の処理は終了である。
実行してみて、矩形検出がされ画面に描画されていれば成功である。

# 次へ
この記事では、VisionFrameworkを使い、矩形検出をし、描画するところまでを書いた。
次の記事では、矩形検出で検出したものがA4の用紙であるかを判断する関数をつくろうと思う。