ARKit / フェイストラッキング
iOSの「ARKit」で「フェイストラッキング」を行うプログラムを作ります。
1. リソースの準備
以下のサイトのサンプルプログラムのリソースを使います。「Model.scnassets」と「Assets.xcassets」をコピペします。
・Tracking and Visualizing Faces
◎Model.scnassets
・coordinateOrigin.scn
・overlayModel.scn
・robotHead.scn
◎Assets.xcassets
・wireframeTexture
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で取得できるパラメーター
このブレンドシェイプの値に応じて、キャラクターの左目と右目と顎を操作します。
この記事が気に入ったらサポートをしてみませんか?