ARKitを使ったFaceTrackingで地点間の距離を測る

ARKitを用いてあれこれする機会があったので
備忘録代わりにまとめておきます。

##バージョン情報
Xcode10.0
Swift4.2

##準備
StoryboardにUIViewを貼り付けてクラスをARSCNViewに設定します。
<img width="1166" alt="スクリーンショット 2018-10-30 10.23.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/47337/689a9102-bd36-80e9-dbd1-57153fb22fb0.png">

カメラを使うので忘れずに`Info.plist`に
`Privacy - Camera Usage Description`を追加しておきましょう。
<img width="545" alt="スクリーンショット 2018-10-30 11.04.03.png" src="https://qiita-image-store.s3.amazonaws.com/0/47337/94a0a175-6525-090b-bc38-a73aa52e6d7e.png">


##実装

```ViewController.swift
import ARKit
import SceneKit

class ViewController: UIViewController {
    @IBOutlet weak var sceneView: ARSCNView!
    @IBOutlet weak var distanceLabel: UILabel!
    let rightVertexID = 244
    let leftVertexID  = 824

    override func viewDidLoad() {
        super.viewDidLoad()
        guard ARFaceTrackingConfiguration.isSupported else { fatalError() }
        sceneView.delegate = self
        sceneView.scene.background.contents = UIColor.black
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let configuration = ARFaceTrackingConfiguration()
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }
}
```
* 観測地点のVertexIDは予め調べておく。(*顔面に全部で1220個のIDが振られている)

ここでは`rightVertexID`に右の口角`leftVertexID`に左の口角のIDを設定しています。
`viewWillAppear`で`sceneView.session.run()`をコールして顔のトラッキングを開始します。


```ViewController.swift
extension ViewController: ARSCNViewDelegate {
    // 初回マスクがレンダリングされるとき
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        guard let device = MTLCreateSystemDefaultDevice() else {
            return nil
        }
        let faceGeometry = ARSCNFaceGeometry(device: device)
        let faceNode = SCNNode(geometry: faceGeometry)
        faceNode.geometry?.firstMaterial?.fillMode = .lines

        let rightNode = SphereNode(with: .red)
        rightNode.name = "right"
        faceNode.addChildNode(rightNode)
        let leftNode = SphereNode(with: .blue)
        leftNode.name = "left"
        faceNode.addChildNode(leftNode)

        return faceNode
    }

    // マスクが更新されるとき
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return }

        faceGeometry.update(from: faceAnchor.geometry)
        updateFeatures(for: node, using: faceAnchor)
    }
```

`sceneView.scene`のデリゲートを受け取ります。

初回レンダリング時に呼ばれるメソッド内で観測するためのNodeを追加します。
`func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode
`
`rightNode`と`leftNode`を生成し`name`を振って`faceNode`に追加します。

`func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor)`は顔の動きが観測された毎に呼ばれるメソッドです。
ここでマスクの位置情報と先ほど追加したNodeの位置情報を更新します。



分かりやすくするため、地点に目印を表示するカスタムNodeクラスを作成する。

```SphereNode.swift
// 円型のノード
class SphereNode: SCNNode {
    init(with color: UIColor = .white) {
        super.init()
        let sphere = SCNSphere(radius: 0.003)
        let material = SCNMaterial()
        material.diffuse.contents = color
        sphere.materials = [material]
        sphere.segmentCount = 16
        geometry = sphere
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // 線の位置を更新する
    func updatePosition(for vectors: [vector_float3]) {
        let newPos = vectors.reduce(vector_float3(), +) / Float(vectors.count)
        position = SCNVector3(newPos)
    }
}
```

円型のノードを追加するカスタムクラスを追加


```ViewConroller.swift
// 始点と終点から距離を計算して返す
    func distanceMeasurement(startPosition: SCNVector3, endPosition: SCNVector3) -> Double {
        let position = SCNVector3Make(endPosition.x - startPosition.x, endPosition.y - startPosition.y, endPosition.z - startPosition.z)
        let distance = sqrt(position.x * position.x + position.y * position.y + position.z * position.z)
        return Double(distance * 100.0)
    }

    // 顔に貼り付けたオブジェクト位置の更新
    func updateFeatures(for node: SCNNode, using anchor: ARFaceAnchor) {
        guard let rightNode = node.childNode(withName: "right", recursively: true) as? SphereNode,
        let leftNode = node.childNode(withName: "left", recursively: true) as? SphereNode else {
            return
        }
        let rightVertex = anchor.geometry.vertices[rightVertexID]
        rightNode.updatePosition(for: [rightVertex])

        let leftVertex = anchor.geometry.vertices[leftVertexID]
        leftNode.updatePosition(for: [leftVertex])

        DispatchQueue.main.async {
            let distance = String(format: "%.2f",
                                  self.distanceMeasurement(startPosition: rightNode.position, endPosition: leftNode.position))
            self.distanceLabel.text = distance + "cm"
        }
    }
```

`updateFeatures()`の中でnodeのChildNodeの中から先ほど追加した`rightNode`と`leftNode`を取り出す。
`ARFaceAnchor`のgeometryからVertexIDを元に位置情報を取得します。
`rightNode`と`leftNode`が取得できたら`distanceMeasurement`で地点間の距離を計算して`distanceLabel`に表記します。


##ビルド
口をすぼめた時と広げた時の様子
<img width="375" alt="Screen Shot 2018-10-30 at 11.06.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/47337/22bd18c3-ab25-1f08-a3ef-7b7e346c2bdf.png">
<img width="375" alt="Screen Shot 2018-10-30 at 11.07.06.png" src="https://qiita-image-store.s3.amazonaws.com/0/47337/a7368472-7d59-a276-2f9f-14cebf971133.png">


##最後に
ものさしで測りながら精度を確かめたところ大体0.7くらいの誤差以内で観測できていました。
なかなかの精度！


以下`ViewController`のソースです。

```ViewController.swift
import UIKit
import ARKit
import SceneKit

class ViewController: UIViewController {

    @IBOutlet weak var sceneView: ARSCNView!
    @IBOutlet weak var distanceLabel: UILabel!

    let rightVertexID = 244
    let leftVertexID  = 824

    override func viewDidLoad() {
        super.viewDidLoad()
        guard ARFaceTrackingConfiguration.isSupported else { fatalError() }
        sceneView.delegate = self
        sceneView.scene.background.contents = UIColor.black
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        let configuration = ARFaceTrackingConfiguration()
        sceneView.session.run(configuration)
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        sceneView.session.pause()
    }

    // 始点と終点から距離を計算して返す
    func distanceMeasurement(startPosition: SCNVector3, endPosition: SCNVector3) -> Double {
        let position = SCNVector3Make(endPosition.x - startPosition.x, endPosition.y - startPosition.y, endPosition.z - startPosition.z)
        let distance = sqrt(position.x * position.x + position.y * position.y + position.z * position.z)
        return Double(distance * 100.0)
    }

    // 顔に貼り付けたオブジェクト位置の更新
    func updateFeatures(for node: SCNNode, using anchor: ARFaceAnchor) {
        guard let rightNode = node.childNode(withName: "right", recursively: true) as? SphereNode,
        let leftNode = node.childNode(withName: "left", recursively: true) as? SphereNode else {
            return
        }
        let rightVertex = anchor.geometry.vertices[rightVertexID]
        rightNode.updatePosition(for: [rightVertex])

        let leftVertex = anchor.geometry.vertices[leftVertexID]
        leftNode.updatePosition(for: [leftVertex])

        DispatchQueue.main.async {
            let distance = String(format: "%.2f",
                                  self.distanceMeasurement(startPosition: rightNode.position, endPosition: leftNode.position))
            self.distanceLabel.text = distance + "cm"
        }
    }
}

extension ViewController: ARSCNViewDelegate {

    // 最初にマスクがレンダリングされるとき
    func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
        guard let device = MTLCreateSystemDefaultDevice() else {
            return nil
        }
        let faceGeometry = ARSCNFaceGeometry(device: device)
        let faceNode = SCNNode(geometry: faceGeometry)
        faceNode.geometry?.firstMaterial?.fillMode = .lines

        let rightNode = SphereNode(with: .red)
        rightNode.name = "right"
        faceNode.addChildNode(rightNode)
        let leftNode = SphereNode(with: .blue)
        leftNode.name = "left"
        faceNode.addChildNode(leftNode)

        return faceNode
    }

    // マスクが更新されるとき
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return }

        faceGeometry.update(from: faceAnchor.geometry)
        updateFeatures(for: node, using: faceAnchor)
    }
}
```