顔認識でつかまえて with ARKit



Qiitaをご覧の皆さん
はじめまして
ですけど
僕が誰だか分かりますよね

Appleが提供するARKitの顔認識で遊んでみました

ごきげんようー( ∴ )
笑い男マークを顔面に貼り付けるアプリをつくってみました。

アプリをつくることにした動機

顔認識技術への動向

いま顔認識技術への関心が高まっています。マイクロソフトは顔認識技術の抱える問題解決への具体的な行動にでました。問題への警鐘が鳴らされたことによってなのか、Taylorの方のSwiftのコンサート会場にストーカー検知用の顔認識システムが設置してあったことが半年ほど前の出来事として発覚し浮き彫りとなっていたりしました。

それと... 攻殻機動隊の新シリーズが制作決定!

みんな大好き攻殻機動隊の新シリーズ制作が発表されましたね!
私も大好きな作品です。

僕は顔面を笑い男にしようと考えたんだ

さて私はというと趣味でARKitを勉強中なのですが、攻殻機動隊に登場する笑い男による顔情報の上書きはARで実現できそうだと思ったのでやってみることにしました。

やっていること

  • XcodeのScene Editorでアニメーションする笑い男マークの3Dモデルを作成
  • ARFaceTrackingConfigurationで顔認識ベースのARアプリを作成
  • 認識した顔に3Dモデルを添付する
  • 作中のイメージを元に3Dモデルの挙動を制御

動かしている環境

  • iPhoneXS iOS12.1
  • MBP Mojave
  • Xcode 10.1

ARKit2.0 現在、ARFaceTrackingConfigurationはTrueDepthのフロントカメラでしか利用できません。

プロジェクトの作成

プロジェクトをAugumented Reality Appのテンプレートで作成してviewDidLoad(_:)で飛行機を表示している部分を削除しておきます

override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.delegate = self
    // 自動ロックをオフにしておく
    UIApplication.shared.isIdleTimerDisabled = false
}

素材を作成する

一応言っておきますが、これはXcodeの画面です。
Xcodeに付属しているScene Editorなるものを使って笑い男マークを作成します。3Dモデルはアニメーションするパーツと動かないパーツの2つのノードで構成されています。作成した3DモデルにScene Editorでアニメーションを追加する編集をしていきます。

テクスチャとなる画像は拾い物を元にアニメーションする背面部分と動かない前面部分の2パーツに分けてつくりました。

SceneEditorでコンテンツをつくる


新しくSceneのファイルを追加します。テンプレートをSceneKit Scene Fileで作成するとScene Editorが立ち上がります。

Plane2枚で外観を作成


UI部品を追加する要領でObjects LibraryからPlaneを2つScene Graphに追加します。


Planeのテクスチャを変更するためにMaterialインスペクターよりPropertiesの中のDiffuseを用意した画像に設定します。ありがたいことに透過画像だと角ばっていたPlaneも透過してくれるようです。これを背面と前面の2パーツ分作成します。

2パーツのサイズ感を合わせるためにAttributesインスペクターよりsize、NodeインスペクターよりTransformの中のPositionを調整して2パーツをうまいこと合体させます。2パーツの前後の関係を調整する必要がありますのでPosition z軸は背面パーツを0,前面パーツを0.001としました。

Scene Graphに追加する要素はEmpty Nodeで親子関係を作っておくとなにかと便利かと思います。

ライトを追加


後で実装するディテール再現のために、ライトは現実の光を推定したものを使いたくないのでシーン内にライトをつくります。Ambient lighitという均一な光を提供するライトを頭上に置いておきます。

アニメーションを追加する

次にアニメーションを追加します。アニメーションの追加にはセカンダリーエディターを使います。

セカンダリエディタが隠れている場合はエディターエリアの右下にあるShow Secondary Editorボタンをクリックして表示させてください。背景のノードを選択しActionタブを選択するとアニメーションを追加するタイムラインが表示されます。


Objects LibraryからRotate Actionをタイムラインに追加します。


今回は回転アニメーションのみ永続的に実行したいのでActionをマウスオーバーしたときに表示されるリピートアイコンよりLoopingの設定を∞無限にしておきます。


次は回転の挙動を制御するためにAttributeインスペクターよりActionの値をいじります。再生ボタンを押すと挙動が確認できるので確認しつつ設定をいじって調節します。等速でZ軸だけ反時計周りに回すようにします。

これで3Dモデルが完成しました。

顔に素材をくっつける

次にARFaceTrackingConfigurationを使って顔認識ベースのARを開始し、作成した3Dモデルを顔に添付させます。

顔認識の設定

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    resetTracking()
}

private func resetTracking() {
    guard ARFaceTrackingConfiguration.isSupported else {
        // Face tracking is not supported.
        return
    }
    let configuration = ARFaceTrackingConfiguration()
    // ライトは3Dモデルのものを使います
    configuration.isLightEstimationEnabled = false
    let options = [.resetTracking, .removeExistingAnchors]
    sceneView.session.run(configuration, options: [)
}

ARFaceTrackingConfigurationでセッションを開始するとフロントカメラを利用した顔認識ベースのARを開始することができます。3Dモデル作成のときにライトを設置したので、ここで現実の光を推定する挙動はOffにします。

コンテンツを添付する

private let laughingmanNode: SCNReferenceNode? = {
    let path = Bundle.main.path(forResource: "laughingman", 
        ofType: "scn",
        inDirectory: "art.scnassets")!
    let url = URL(fileURLWithPath: path)
    return SCNReferenceNode(url: url)
}()

3Dモデルの読み込みの処理を追加します。SCNReferenceNodeはload()を呼んでノードとして利用します。

extension ViewController: ARSCNViewDelegate {
    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        guard anchor is ARFaceAnchor else { return }

        if node.childNodes.isEmpty, let content = laughingmanNode {
            // ノードを読み込み
            content.load()

            // 顔面にノードを追加
            node.addChildNode(content)
        }
    }
}

このように追加するだけで特に座標は指定しなくてもサイズさえあっていれば顔面が隠れる感じに配置してくれて、しかもリアルタイムにトラッキングしてくれます。Appleありがとー!

作中の挙動を再現したい


トラッキングしてくれるのはいいんですが、そのままだと被写体の首振りの動きまで追従してくるのと光を反射するのが微妙です。これでは緊張感がまるでないので、なるべく作中のイメージに近づけるようにチャレンジしていきます。

光の反射を無効化して均一にする

3Dモデルを作る過程からライトを追加して、コード上でも光の推定をOffにしていましたのでこれはできています。

向きの制約を追加する

// ノードを読み込み
content.load()

// 常にカメラを向く制約を追加
let constraint = SCNBillboardConstraint()
constraint.freeAxes = [.X, .Y]
content.constraints = [constraint]

// 顔面にノードを追加
node.addChildNode(content)

被写体の首振りには追従せずに常にカメラに対して2次元的な挙動を期待しています。そんな時に使えるのがSCNBillboardConstraintです。これは看板など文字情報をカメラから見て読みやすいような表示を表現するときに利用できます。

freeAxesで角度調節をする軸を設定します。今回はカメラが左右や上下に動いたときはカメラを向くようにしたいけれど、カメラの回転といったZ軸の傾きに対しては追従せず地表に対して常に垂直でよいので上記のようにX軸とY軸を設定しています。

グリッチエフェクト

// extension ViewController: ARSCNViewDelegateに追加
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    // 作中のぶれを再現
    let xRand = Float.random(in: 1...5)
    let yRand = Float.random(in: 1...5)
    laughingmanNode?.position = SCNVector3(0.001*xRand, 0.001*yRand, 0)
}

作品をみると笑い男マークはグリッチエフェクトが適用されています。なんらかのノイズでレンダリングされたマークがブルブルしててそれがなかなか不気味でかっこよいのでこれを再現したいです。更新の度に乱数で3Dモデルのポジションを微妙に動かすことで再現してみました。

できあがり!

Twitterのビデオはグリッチが弱いですが...

おわりに

Scene Editorで3Dモデルを作ってさらにアニメーションを追加できるとは思わなかったです。UIKit同様にシンプルなものならなんとかつくれそうですね。

Appleの顔認識は視線や表情の情報を取得できるので、視線でマウス、ウィンクでクリックというUI操作を実現しているデモが拡散されていて。なにこれすごいと思いました。
個人的には顔認識APIを使ったアプリの可能性をすごい感じたので、顔認識技術の問題への対処としてAppleのとる行動が気になっています。