ARKit で Face Tracking して左右を正しくアバターを動かす

これは [クラスター Advent Calendar 2020](https://qiita.com/advent-calendar/2020/cluster) 14日目の記事です。

昨日はurauraさんの [clusterサーバーサイドにおける技術選定について](https://qiita.com/uraura/items/f2bcd2c007d997236750) でした。とても興味深い話でしたね。

## はじめに

こんにちは、ねおりんです。
最近はビデオミーティングで使うためだけにアバターを動かすアプリを自作しています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">まじでアバターで来て大丈夫なのか不安という声があったのでとある社内Mtgの様子を貼っておきます<br><br>アバター面談歓迎！バーチャルSNSを開発するUnityエンジニア募集！ by クラスター株式会社 (<a href="https://twitter.com/cluster_jp?ref_src=twsrc%5Etfw">@cluster_jp</a>) <a href="https://t.co/gG3piF0ete">https://t.co/gG3piF0ete</a> <a href="https://twitter.com/hashtag/wantedly?src=hash&amp;ref_src=twsrc%5Etfw">#wantedly</a> <a href="https://t.co/CnOb631UHG">pic.twitter.com/CnOb631UHG</a></p>&mdash; とみね∞遠見海@バーチャル多趣味アンドロイド (@htomine) <a href="https://twitter.com/htomine/status/1337018801628479489?ref_src=twsrc%5Etfw">December 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

↑はリーク画像。下が私です(できればもうちょっとかわいい瞬間を撮ってほしかったですね)。

この記事は、世の中にすでにあるアプリでは満足できずに自分で作り始めてしまった原因の一つと、その解決方法を紹介するものです。[Unityで前髪の分け目を変えてみた](https://zenn.dev/noir_neo/articles/2eb48d2a14cbe8fa4091) の精神的続編にあたります。

## Face Tracking のデータは鏡

ARKit の Face Tracking で取得できるデータは左右反転して入ってきます。
つまり、カメラの前で右を向くと左回転を、[右目を閉じると左目を閉じるようなデータ](https://developer.apple.com/documentation/arkit/arfaceanchor/blendshapelocation)を返します。

これは、普通に扱う分にはそのまま動きが鏡になっていて便利なのですが、トラッキングしたデータをアバターに適用する際には問題があります。

仮にトラッキングデータをそのまま適用し、映像が鏡になっているものとしてストリーミングソフト側で反転させた場合、今度はアバターの左右が逆になってしまいます。
アバターが左右対称であれば問題はありませんが、前髪の分け目や髪飾りの位置、オッドアイなど左右の意味合いが重要な場合もあるため、正しく対応しておきたいところです。

## 顔とカメラの pose を鏡写しにする

顔の位置は普通に ARFaceManager.facesChanged を購読して取得します。

カメラの位置は ARPoseDriver の transform でよいかと思いますが、レンダリングと更新の実行順序に不安があるので ARPoseDriver にあたるものを(ほぼコピペで)再実装しました。

取得した pose を鏡にするのはこんな感じです。

```.cs
rotation.x *= -1;
rotation.y *= -1;
position.z *= -1;
```

カメラは更にy軸を180度回転します。

## BlendShapeLocation の左右を入れ替える

以下は VRM の BlendShapePreset と ARKitBlendShapeLocation のマッピング例です。

「左を見ている率」は、「(向かって)左の目が内側を見ている率」と「(向かって)右の
目が外側を見ている率」です。

```
{
    {
        BlendShapePreset.LookLeft,
        new []
        {
            ARKitBlendShapeLocation.EyeLookInLeft,
            ARKitBlendShapeLocation.EyeLookOutRight
        }
    },
    {
        BlendShapePreset.LookRight,
        new []
        {
            ARKitBlendShapeLocation.EyeLookOutLeft,
            ARKitBlendShapeLocation.EyeLookInRight
        }
    },
    {
        BlendShapePreset.Blink_L,
        new []
        {
            ARKitBlendShapeLocation.EyeBlinkRight
        }
    },
    {
        BlendShapePreset.Blink_R,
        new []
        {
            ARKitBlendShapeLocation.EyeBlinkLeft
        }
    }
}
```


## 自分で見る映像は鏡にする

最終的な映像として左右は正しくなりましたが、自分で見る画面ではやはり鏡になっているほうが違和感が少ないです。

Unity での Camera のフリップは、[projectionMatrix で反転する方法](https://answers.unity.com/questions/20337/flipmirror-camera.html) もありますが、 Render Texture に描いたものを Canvas の UV で反転させるのが楽なのでそれを採用しました。

ついでに、 OBS に送出する映像には UI を入れたくないことなどを考えても Render Texture に描いてしまうのがよさそうです。

## おまけ: Unity から透過映像を OBS に入れる

グリーンバックをゲームキャプチャでも良いのですが、直接 OBS に透過映像を入れられた方がきれいです。

keijiro氏が公開している package [KlakSpout](https://github.com/keijiro/KlakSpout) が便利です。

## おわりに

こんな感じになりました。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">今日のところはこの辺にしておいてやろう。 <a href="https://t.co/b6bLjY5tfn">pic.twitter.com/b6bLjY5tfn</a></p>&mdash; ねおりん (@noir_neo) <a href="https://twitter.com/noir_neo/status/1337877267041538048?ref_src=twsrc%5Etfw">December 12, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

明日はulaphさんの SwiftでViewの状態をenumで管理する です。楽しみですね。