顔認識でつかまえて with ARKit

<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/203147/effb25cf-1bc2-3a2c-ab55-363ef5183b8a.gif">
Qiitaをご覧の皆さん
はじめまして
ですけど
僕が誰だか分かりますよね

## Appleが提供するARKitの顔認識で遊んでみました
ごきげんようー( ∴ )
笑い男マークを顔面に貼り付けるアプリをつくってみました。
### アプリをつくることにした動機
##### 顔認識技術への動向
いま顔認識技術への関心が高まっています。マイクロソフトは顔認識技術の抱える問題解決への具体的な行動にでました。問題への警鐘が鳴らされたことによってなのか、[Taylor](https://www.universal-music.co.jp/taylor-swift/)の方のSwiftのコンサート会場にストーカー検知用の顔認識システムが設置してあったことが半年ほど前の出来事として発覚し浮き彫りとなっていたりしました。

##### それと... 攻殻機動隊の新シリーズが制作決定！
みんな大好き攻殻機動隊の新シリーズ制作が発表されましたね！
私も大好きな作品です。

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

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

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

## 動かしている環境
- iPhoneXS iOS12.1
- MBP Mojave
- Xcode 10.1

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

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

```swift
override func viewDidLoad() {
    super.viewDidLoad()
    
    sceneView.delegate = self
    // 自動ロックをオフにしておく
    UIApplication.shared.isIdleTimerDisabled = false
}
```

## 素材を作成する
<img width="80%" alt="笑い男3Dモデル構成" src="https://qiita-image-store.s3.amazonaws.com/0/203147/e4f7ceac-a07e-703b-0182-c88519b4f521.gif">

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

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

### SceneEditorでコンテンツをつくる
<img width="80%" alt="SceneKit Scene Fileテンプレート選択" src="https://qiita-image-store.s3.amazonaws.com/0/203147/e62114a7-123d-b93d-8012-f1471746e653.png">
新しくSceneのファイルを追加します。テンプレートをSceneKit Scene Fileで作成するとScene Editorが立ち上がります。

### Plane2枚で外観を作成
<img width="80%" alt="Objects Libraryを開く" src="https://qiita-image-store.s3.amazonaws.com/0/203147/86101542-61db-02a4-22e2-7aeba9f36832.png">
UI部品を追加する要領でObjects LibraryからPlaneを2つScene Graphに追加します。

<img width="80%" alt="PlaneのMaterialインスペクター" src="https://qiita-image-store.s3.amazonaws.com/0/203147/46a5f002-17b7-fb16-71a1-e7d9dc32a98f.png">
Planeのテクスチャを変更するためにMaterialインスペクターよりPropertiesの中のDiffuseを用意した画像に設定します。ありがたいことに透過画像だと角ばっていたPlaneも透過してくれるようです。これを背面と前面の２パーツ分作成します。

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

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

### ライトを追加
<img width="80%" alt="Ambient light" src="https://qiita-image-store.s3.amazonaws.com/0/203147/af442d6d-c889-7ce3-4525-4c6cb884de30.png">
後で実装するディテール再現のために、ライトは現実の光を推定したものを使いたくないのでシーン内にライトをつくります。Ambient lighitという均一な光を提供するライトを頭上に置いておきます。

### アニメーションを追加する
次にアニメーションを追加します。アニメーションの追加にはセカンダリーエディターを使います。
<img width="80%" alt="セカンダリーエディターを開く" src="https://qiita-image-store.s3.amazonaws.com/0/203147/3762e45d-48c2-f870-9679-9118425c7786.png">
セカンダリエディタが隠れている場合はエディターエリアの右下にあるShow Secondary Editorボタンをクリックして表示させてください。背景のノードを選択しActionタブを選択するとアニメーションを追加するタイムラインが表示されます。

<img width="80%" alt="スクリーンショット 2018-12-14 1.57.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/203147/2cfb550a-aa97-c6c0-5c76-1bb89a3e9b69.png">
Objects LibraryからRotate Actionをタイムラインに追加します。

<img width="80%" alt="アニメーション永続の設定" src="https://qiita-image-store.s3.amazonaws.com/0/203147/94ab7249-9c5f-997d-ec43-972f450efcff.png">
今回は回転アニメーションのみ永続的に実行したいのでActionをマウスオーバーしたときに表示されるリピートアイコンよりLoopingの設定を∞無限にしておきます。

<img width="80%" alt="スクリーンショット 2018-12-14 0.38.11.png" src="https://qiita-image-store.s3.amazonaws.com/0/203147/43addf39-b326-7f28-c350-f7c43308fd2f.png">
次は回転の挙動を制御するためにAttributeインスペクターよりActionの値をいじります。再生ボタンを押すと挙動が確認できるので確認しつつ設定をいじって調節します。等速でZ軸だけ反時計周りに回すようにします。

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

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

### 顔認識の設定
```swift
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にします。
    
### コンテンツを添付する
```swift
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()`を呼んでノードとして利用します。

```swift
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ありがとー！

## 作中の挙動を再現したい
<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/203147/890b9d79-e0b9-ce97-84a7-8726dcd5269b.gif">
トラッキングしてくれるのはいいんですが、そのままだと被写体の首振りの動きまで追従してくるのと光を反射するのが微妙です。これでは緊張感がまるでないので、なるべく作中のイメージに近づけるようにチャレンジしていきます。

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

### 向きの制約を追加する
```swift
// ノードを読み込み
content.load()

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

// 顔面にノードを追加
node.addChildNode(content)
```
被写体の首振りには追従せずに常にカメラに対して２次元的な挙動を期待しています。そんな時に使えるのがSCNBillboardConstraintです。これは看板など文字情報をカメラから見て読みやすいような表示を表現するときに利用できます。  

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

### グリッチエフェクト
```swift
// 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モデルのポジションを微妙に動かすことで再現してみました。

## できあがり！
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">笑い男になれるアプリが完成した。 <a href="https://t.co/uexSZ0stAv">pic.twitter.com/uexSZ0stAv</a></p>&mdash; UMA (@trueSuperior) <a href="https://twitter.com/trueSuperior/status/1071766702323458048?ref_src=twsrc%5Etfw">2018年12月9日</a></blockquote>
Twitterのビデオはグリッチが弱いですが...

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

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