見出し画像

ARKit / フェイストラッキング

iOSの「ARKit」で「フェイストラッキング」を行うプログラムを作ります。

1. リソースの準備

以下のサイトのサンプルプログラムのリソースを使います。「Model.scnassets」と「Assets.xcassets」をコピペします。

Tracking and Visualizing Faces

◎Model.scnassets
・coordinateOrigin.scn

画像1

・overlayModel.scn

画像2

・robotHead.scn

画像3

◎Assets.xcassets
・wireframeTexture

画像4

2. 座標原点の表示

はじめに、「フェイストラッキング」を行い、顔と左目と右目の座標原点を表示します。

import UIKit
import SceneKit
import ARKit

//フェイストラッキング(座標原点の表示)
class ViewController: UIViewController, ARSCNViewDelegate {
   @IBOutlet var sceneView: ARSCNView!

   //ノード
   var contentNode = SCNReferenceNode(named: "coordinateOrigin")
   var leftEyeNode = SCNReferenceNode(named: "coordinateOrigin")
   var rightEyeNode = SCNReferenceNode(named: "coordinateOrigin")

   //ロード時に呼ばれる
   override func viewDidLoad() {
       super.viewDidLoad()

       //シーンの作成
       sceneView.scene = SCNScene()

       //ARSCNViewデリゲートの指定
       sceneView.delegate = self
   }

   //ビュー表示時に呼ばれる
   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)

       //フェイストラッキングの開始
       let configuration = ARFaceTrackingConfiguration()
       configuration.isLightEstimationEnabled = true
       sceneView.session.run(configuration)
   }

   //ビュー非表示時に呼ばれる
   override func viewWillDisappear(_ animated: Bool) {
       super.viewWillDisappear(animated)
       //セッションの一時停止
       sceneView.session.pause()
   }

   //追加されたARアンカーに対応するノードを提供
   func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
       //ARFaceAnchorの取得
       guard anchor is ARFaceAnchor else {return nil}

       //コンテンツノードに目ノードを追加
       rightEyeNode.simdPivot = float4x4(diagonal: float4(3, 3, 3, 1))
       leftEyeNode.simdPivot = float4x4(diagonal: float4(3, 3, 3, 1))
       self.contentNode.addChildNode(leftEyeNode)
       self.contentNode.addChildNode(rightEyeNode)
       return self.contentNode
   }

   //ARアンカーの位置をARアンカーに対応するノードに反映した後に呼ばれる
   func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
       //ARFaceAnchorの取得
       guard let faceAnchor = anchor as? ARFaceAnchor else {return}

       //目ノードの位置の更新
       rightEyeNode.simdTransform = faceAnchor.rightEyeTransform
       leftEyeNode.simdTransform = faceAnchor.leftEyeTransform
   }
}

//SCNReferenceNodeの拡張
extension SCNReferenceNode {
   //リソース名でノードを生成
   convenience init(named resourceName: String, loadImmediately: Bool = true) {
       let url = Bundle.main.url(forResource: resourceName,
           withExtension: "scn", subdirectory: "Models.scnassets")!
       self.init(url: url)!
       if loadImmediately {
           self.load()
       }
   }
}

◎フェイストラッキングの開始
「フェイストラッキング」を開始するには、コンフィギュレーションに「ARFaceTrackingConfiguration」を利用します。

◎座標原点ノードの生成
フェイストラッキングが開始すると、renderer(renderer:nodeFor anchor:)が呼ばれます。通知されるアンカーは「ARFaceAnchor」です。ここで、座標原点のノードを生成します。

◎座標原点ノードの更新
フェイストラッキングが更新されると、renderer(renderer:didUpdate node:for anchor:)が呼ばれます。ここで、左目と右目のノードの位置を更新します。右目と左目の位置は、「ARFaceAnchor」の「rightEyeTransform」と「leftEyeTransform」で取得します。

3. 顔ジオメトリの表示

次に、検出した顔ジオメトリをテクスチャで表示します。

import UIKit
import SceneKit
import ARKit

//フェイストラッキング(顔ジオメトリの表示)
class ViewController: UIViewController, ARSCNViewDelegate {
   @IBOutlet var sceneView: ARSCNView!

   //ノード
   var contentNode: SCNNode!

   //ロード時に呼ばれる
   override func viewDidLoad() {
       super.viewDidLoad()

       //シーンの作成
       sceneView.scene = SCNScene()

       //ARSCNViewデリゲートの指定
       sceneView.delegate = self
   }

   //ビュー表示時に呼ばれる
   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)

       //フェイストラッキングの開始
       let configuration = ARFaceTrackingConfiguration()
       configuration.isLightEstimationEnabled = true
       sceneView.session.run(configuration)
   }

   //ビュー非表示時に呼ばれる
   override func viewWillDisappear(_ animated: Bool) {
       super.viewWillDisappear(animated)
       //セッションの一時停止
       sceneView.session.pause()
   }

   //追加されたARアンカーに対応するノードを提供
   func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
       //ARSCNViewとARFaceAnchorの取得
       guard let sceneView = renderer as? ARSCNView  else {return nil}
       guard anchor is ARFaceAnchor else {return nil}

       //顔ジオメトリを持つ顔ノードの生成
       let faceGeometry = ARSCNFaceGeometry(device: sceneView.device!)!
       let material = faceGeometry.firstMaterial!
       material.diffuse.contents = #imageLiteral(resourceName: "wireframeTexture")
       material.lightingModel = .physicallyBased
       contentNode = SCNNode(geometry: faceGeometry)
       return contentNode
   }

   //ARアンカーの位置をARアンカーに対応するノードに反映した後に呼ばれる
   func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
       //ARSCNFaceGeometryとARFaceAnchorの取得
       guard let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return }
       guard let faceAnchor = anchor as? ARFaceAnchor else { return }

       //顔ジオメトリの更新
       faceGeometry.update(from: faceAnchor.geometry)
   }
}

◎顔ジオメトリを持つ顔ノードの生成
フェイストラッキングが開始すると、renderer(renderer:nodeFor anchor:)が呼ばれます。ここで、顔ジオメトリを持つノードを生成します。顔ジオメトリは「ARSCNFaceGeometry」で生成し、diffuse.contentsにテクスチャ、lightingModelに光源を指定します。

◎顔ジオメトリの更新
フェイストラッキングが更新されると、func renderer(renderer:didUpdate node:for anchor:)が呼ばれます。ここで顔ジオメトリをupdate(from:)で更新します。

4. ARメガネの表示

次に、ARメガネを表示します。鼻とメガネでオクルージョンがかかってます。

import UIKit
import SceneKit
import ARKit

//フェイストラッキング(ARメガネ)
class ViewController: UIViewController, ARSCNViewDelegate {
   @IBOutlet var sceneView: ARSCNView!

   //ノード
   var contentNode: SCNNode!
   var occlusionNode: SCNNode!

   //ロード時に呼ばれる
   override func viewDidLoad() {
       super.viewDidLoad()

       //シーンの作成
       sceneView.scene = SCNScene()

       //ARSCNViewデリゲートの指定
       sceneView.delegate = self
   }

   //ビュー表示時に呼ばれる
   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)

       //フェイストラッキングの開始
       let configuration = ARFaceTrackingConfiguration()
       configuration.isLightEstimationEnabled = true
       sceneView.session.run(configuration)
   }

   //ビュー非表示時に呼ばれる
   override func viewWillDisappear(_ animated: Bool) {
       super.viewWillDisappear(animated)
       //セッションの一時停止
       sceneView.session.pause()
   }

   //追加されたARアンカーに対応するノードを提供
   func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
       //ARSCNViewとARFaceAnchorの取得
       guard let sceneView = renderer as? ARSCNView  else {return nil}
       guard anchor is ARFaceAnchor else {return nil}

       //顔ジオメトリを持つ顔ノードの生成
       let faceGeometry = ARSCNFaceGeometry(device: sceneView.device!)!
       faceGeometry.firstMaterial!.colorBufferWriteMask = []
       occlusionNode = SCNNode(geometry: faceGeometry)
       occlusionNode.renderingOrder = -1

       //メガネノードの生成
       let faceOverlayContent = SCNReferenceNode(named: "overlayModel")

       //顔ノードとメガネノードを持つノードの生成
       contentNode = SCNNode()
       contentNode!.addChildNode(occlusionNode)
       contentNode!.addChildNode(faceOverlayContent)
       return contentNode
   }


   //ARアンカーの位置をARアンカーに対応するノードに反映した後に呼ばれる
   func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
       //ARSCNFaceGeometryとARFaceAnchorの取得
       guard let faceGeometry = node.geometry as? ARSCNFaceGeometry else { return }
       guard let faceAnchor = anchor as? ARFaceAnchor else { return }

       //顔ジオメトリの更新
       faceGeometry.update(from: faceAnchor.geometry)
   }
}

//SCNReferenceNodeの拡張
extension SCNReferenceNode {
   //リソース名でノードを生成
   convenience init(named resourceName: String, loadImmediately: Bool = true) {
       let url = Bundle.main.url(forResource: resourceName,
           withExtension: "scn", subdirectory: "Models.scnassets")!
       self.init(url: url)!
       if loadImmediately {
           self.load()
       }
   }
}

◎顔ジオメトリを持つノードの生成
顔ジオメトリを持つノードを生成します。前回はテクスチャを貼りましたが、今回はテクスチャやカラーマスクはなしとします。renderingOrderは描画の順番でデフォルトは0です。-1を指定することで、他より先に描画されるようになり、オクルージョンを実現します。

5. ブレンドシェイプのキャラクターの表示

次に、ブレンドシェイプのキャラクターの表示を行います。

import UIKit
import SceneKit
import ARKit

//フェイストラッキング(ブレンドシェイプのキャラクターの表示)
class ViewController: UIViewController, ARSCNViewDelegate {
   @IBOutlet var sceneView: ARSCNView!

   //ノード
   var contentNode: SCNNode!
   private var originalJawY: Float = 0
   private var jawNode: SCNNode!
   private var eyeLeftNode: SCNNode!
   private var eyeRightNode: SCNNode!
   private var jawHeight: Float = 0.0

   //ロード時に呼ばれる
   override func viewDidLoad() {
       super.viewDidLoad()

       //シーンの作成
       sceneView.scene = SCNScene()

       //ARSCNViewデリゲートの指定
       sceneView.delegate = self
   }

   //ビュー表示時に呼ばれる
   override func viewWillAppear(_ animated: Bool) {
       super.viewWillAppear(animated)

       //フェイストラッキングの開始
       let configuration = ARFaceTrackingConfiguration()
       configuration.isLightEstimationEnabled = true
       sceneView.session.run(configuration)
   }

   //ビュー非表示時に呼ばれる
   override func viewWillDisappear(_ animated: Bool) {
       super.viewWillDisappear(animated)
       //セッションの一時停止
       sceneView.session.pause()
   }

   //追加されたARアンカーに対応するノードを提供
   func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
       //ARFaceAnchorの取得
       guard anchor is ARFaceAnchor else {return nil}

       //キャラクターのノードの生成
       contentNode = SCNReferenceNode(named: "robotHead")
       jawNode = contentNode!.childNode(withName: "jaw", recursively: true)!
       eyeLeftNode = contentNode!.childNode(withName: "eyeLeft", recursively: true)!
       eyeRightNode = contentNode!.childNode(withName: "eyeRight", recursively: true)!
       let (min, max) = jawNode.boundingBox
       jawHeight = max.y - min.y
       originalJawY = jawNode.position.y
       return contentNode
   }

   //ARアンカーの位置をARアンカーに対応するノードに反映した後に呼ばれる
   func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
       //ARFaceAnchorの取得
       guard let faceAnchor = anchor as? ARFaceAnchor else {return}

       //ブレンドシェイプの値の取得
       let blendShapes = faceAnchor.blendShapes
       guard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float else {return}
       guard let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float else {return}
       guard let jawOpen = blendShapes[.jawOpen] as? Float else {return}

       //キャラクターの左目と右目と顎の更新
       eyeLeftNode.scale.z = 1 - eyeBlinkLeft
       eyeRightNode.scale.z = 1 - eyeBlinkRight
       jawNode.position.y = originalJawY - jawHeight * jawOpen
   }
}

//SCNReferenceNodeの拡張
extension SCNReferenceNode {
   //リソース名でノードを生成
   convenience init(named resourceName: String, loadImmediately: Bool = true) {
       let url = Bundle.main.url(forResource: resourceName,
           withExtension: "scn", subdirectory: "Models.scnassets")!
       self.init(url: url)!
       if loadImmediately {
           self.load()
       }
   }
}

◎キャラクターのノードの生成
キャラクターのノードを生成します。キャラクターの左目と右目と顎を操作するため、childNode()で子ノードを取得し保持します。

◎ブレンドシェイプの値の取得
ARFaceAnchorのblendShapesで、顔のブレンドシェイプの値を取得します。取得できるブレンドシェイプの値は、以下が参考になります。

UnityARKitPlugin FaceTracking FaceBlendshapeで取得できるパラメーター

このブレンドシェイプの値に応じて、キャラクターの左目と右目と顎を操作します。


この記事が気に入ったらサポートをしてみませんか?