VisionFrameworkを使ったiOSアプリケーション開発(座標変換と描画)

# はじめに
この記事は、

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

の続きの記事である。
この記事では、vision座標をUIKit座標に変換し、画面にその点を描画するところまで書く。
また、この記事内での「画面のサイズ」という言葉が頻出するが、「画面のサイズ」は「ポイント」のことであり、「ポイント」については、下記の記事参照。

https://qiita.com/pe-ta/items/b3b754bde584abc98f44#:~:text=iPhone%201%2C3G%2C3GS%201x%20320x480%201%20320x480%203.5%20iPhone,375x667%202%20750x1334%205.4%20iPhone%2012mini%20Super%20Retina

# UIKit座標への変換
![座標.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821619/6dab663c-1c03-1178-e1ad-2eb347afeda3.png)
Vision座標は画面左上が原点に対し、UIKit座標は画面右上が原点である。
また、Vision座標は0~1の画面に対する比率であり、UIKit座標は0~(画面のサイズ)である。

### 変換関数作成
まず、座標変換を行うクラスを作る。
```Transformation.swift
class Transformation{
}
```
「import UIKit」と「import Vision」を挿入し、「CameraView」クラスの「previewLayer」を受け取る。
```Transformation.swift
class CoordinateTransformation{

    private var previewLayer:AVCaptureVideoPreviewLayer? 
    //previewLayerにlayerをセットするセッター
    public func setlayer(layer: AVCaptureVideoPreviewLayer){
        previewLayer = layer
    }
}
```
次に、Vision座標からUIKit座標に変換する関数を作る。
```Transformation.swift
class CoordinateTransformation{
    
    private var previewLayer:AVCaptureVideoPreviewLayer? 
    //previewLayerにlayerをセットするセッター
    public func setlayer(layer: AVCaptureVideoPreviewLayer){
        previewLayer = layer
    }
    //Vision座標からUIKit座標に変換する関数
    public func VisionToUikit(visionPoint:VNRecognizedPoint) -> CGPoint{
    }
}
```
まず、VNRecognizedPointをCGPointに変換し、次にVision座標をAVFoundation座標に変換し、「layerPointConverted」関数を使いUIKit座標に変換する。その結果を返す。
```Transformation.swift
class CoordinateTransformation{
    //変数初期化
    var viewsize = CGSize()
    //viewsizeに値をセットするセッター
    public func setSize(size: CGSize){
        viewsize = size
        //確認用
        print(viewsize)
    }
//Vision座標からUIKit座標に変換する関数
    public func visionToUIKit(visionPoint:VNRecognizedPoint) -> CGPoint{
        //VNRecognizedPointをCGPointに変換
        let visionCGPoint = CGPoint(x:visionPoint.location.x, y:visionPoint.location.y)
        //Vision座標をAVFoundation座標に変換する
        let avFoundationPoint = CGPoint(x: visionCGPoint.x, y: 1 - visionCGPoint.y)
        //AVFoundation座標をUIKit座標に変換する
        let uiKitPoint = previewLayer?.layerPointConverted(fromCaptureDevicePoint: avFoundationPoint)
        
        guard let newPoint = uiKitPoint else{
            return CGPoint()
        }
        return newPoint
    }

}
```
### 関数を使う
「CameraViewController.swift」の編集をする。
「Transformation」クラスのインスタンスを生成し、アプリ起動時にセッターを使い、「CameraView.previewlayer」をセットする。
```CameraViewController.swift
class CameraViewController: UIViewController{
    //略
    private let transformation = Transformation()
    overrrride func viewDidLoad(){
        //略
        transformation.setlayer(layer: cameraView.previewLayer)
    }
}
```
関数が使えるか確認のために、「getHandObservations」オブジェクトに実装する。
```CameraViewController.swift
    private func getHandObservations(/*略*/){
        //略
        do{
            //略
            print(transformation.visionToUIKit(visionPoint: thumbTipPoint))
        }catch{
            //略
        }
    }
```
以下のような座標が表示されていれば変換成功。
```
(571.9337112903595, 177.07752585411072)
(576.660945892334, 183.00748586654663)
(592.9437434673309, 191.74261093139648)
(615.0112144947052, 192.16133773326874)
(636.4420087337494, 185.88124930858612)
```

### 認識ポイントの描画
「CameraView.swift」を編集する。
UIViewにサブレイヤーを追加するために必要な初期処理を入れる。
```CameraView.swift
import UIKit
import AVFoundation
class CameraView: UIView{
    //略
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    required init?(coder: NSCoder){
        super.init(coder: coder)
    }
}
```
指先のポイントを描画するためのサブレイヤーをつくるために「CAShapeLayer」クラスと「UIBezierPath」クラスのインスタンスを生成する。
```CameraView.swift
import UIKit
import AVFoundation
class CameraView: UIView{
    private var handoverlayer = CAShapeLayer()
    private var handpointPath = UIBezierPath()
    //略
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    required init?(coder: NSCoder){
        super.init(coder: coder)
    }
}
```
サブレイヤーの大きさを指定し、「previewLayer」にサブレイヤーを追加するオブジェクトを作成する。
```CameraView.swift
import UIKit
import AVFoundation
class CameraView: UIView{
    private var handoverlayer = CAShapeLayer()
    private var handpointPath = UIBezierPath()
    //略
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    required init?(coder: NSCoder){
        super.init(coder: coder)
    }
    override func layoutSublayers(of layer: CALayer){
        super.layoutSublayers(of: layer)
        if layer == previewLayer{
            handoverlayer.frame = layer.bounds
        }
    }
    
    private func setupOverlay(){
        previewLayer.addSublayer(handoverlayer)
    }
}
```
描画するポイントに小さな点を描画するオブジェクトを作成する。
```CameraView.swift
import UIKit
import AVFoundation
class CameraView: UIView{
    private var handoverlayer = CAShapeLayer()
    private var handpointPath = UIBezierPath()
    //略
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    required init?(coder: NSCoder){
        super.init(coder: coder)
    }
    override func layoutSublayers(of layer: CALayer){
        super.layoutSublayers(of: layer)
        if layer == previewLayer{
            handoverlayer.frame = layer.bounds
        }
    }
    
    private func setupOverlay(){
        previewLayer.addSublayer(handoverlayer)
    }

    func handshowPoint(point:CGPoint){
        handpointPath.addArc(withCenter: point, radius: 5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        handoverlayer.fillColor = UIColor.red.cgColor
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        handoverlayer.path = handpointPath.cgPath
        CATransaction.commit()
    }

}
```
次のポイントを描画するために今格納されているポイントを削除するオブジェクトを作成する。
```CameraView.swift
import UIKit
import AVFoundation
class CameraView: UIView{
    private var handoverlayer = CAShapeLayer()
    private var handpointPath = UIBezierPath()
    //略
    override init(frame: CGRect){
        super.init(frame: frame)
    }
    required init?(coder: NSCoder){
        super.init(coder: coder)
    }
    override func layoutSublayers(of layer: CALayer){
        super.layoutSublayers(of: layer)
        if layer == previewLayer{
            handoverlayer.frame = layer.bounds
        }
    }
    
    private func setupOverlay(){
        previewLayer.addSublayer(handoverlayer)
    }

    func handshowPoint(point:CGPoint){
        handpointPath.addArc(withCenter: point, radius: 5, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        handoverlayer.fillColor = UIColor.red.cgColor
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        handoverlayer.path = handpointPath.cgPath
        CATransaction.commit()
    }
    
    func handremovePoint(){
        handpointPath.removeAllPoints()
    }
}
```
「CameraViewController」の指認識のオブジェクトで描画するポイントを渡す。
さらに、ポイントパスを空にする。
```CameraViewController.swift
    private func getHandObservations(/*略*/){
        //略
        do{
            //略
            print(transformation.visionToUIKit(visionPoint: thumbTipPoint))
            self.cameraView.handshowPoint(point: indexPoint)
            self.cameraView.handremovePoint()
        }catch{
            //略
        }
    }
```
以上で描画をするための処理は終了である。ビルドして人差し指の位置に赤い点が描画されていれば成功である。

### 次へ
この記事では、Vision座標をUIKit座標に変換し、それを描画するところまで書いた。次の記事では、矩形認識を使い、A4サイズの紙(触図)を認識するところまでを書こうと思う。