MediaPipeUnityPluginのハマりポイントとか

機械学習
※なぜかこの記事のSEOが強いのでGitHubへのリンクを先に貼っておきます。
https://github.com/homuler/MediaPipeUnityPlugin

UnityでMediapipeを使った画像認識を色々試していたので、躓いた所とか参考になりそうな所をピックアップ
(執筆時点でのバージョンは v0.6.2 、Unityは2020の最新版)

AndroidとWindowsでクロスプラットフォームにしたい

Windows向けでビルドしてから、Android版(WSL2でのビルド)をしたら両方のプラットフォームで動くようになった。
ただし、DLLローディングのエラーは出る(無視出来る)ので、必要に応じてエラーをなんとかする必要があるかもしれない。

ざっくりVRM動かしたい

OpenCV For Unityをアセットストアから買ってCVVTuberExampleをベースにMediapipeを突っ込む。
https://assetstore.unity.com/packages/tools/integration/opencv-for-unity-21088
具体的には、MediapipeForUnityでは「カメラと顔の特徴点推論」をやってくれるので、CVVTuberExampleのProcess Order ListでウェブカメラとDlibの処理を削って、MatSourceGetter, FaceLandmarkGetterは新しくスクリプトを作ってアタッチする。(スクリプト内では白い画像と顔の特徴点を返す実装をしてあげる)
MediapipeのFaceLandmarkは468ポイントありますが、Dlibの68ポイントとのいい感じの変換は自分で書いてあげないといけないので、私は以下のように実装しました。
    // 特徴点68ポイントとLandmarkListsの位置をマッピング
    // 左右は向かっての位置
    // @see https://github.com/ManuelTS/augmentedFaceMeshIndices
    private static int[] dlib68Order = new int[] {
        // face edge 17
        139,
        34,
        137,
        177,
        215,
        135,
        170,
        171,
        175, // center
        396,
        395,
        364,
        435,
        401,
        366,
        264,
        368,
        // 左眉毛
        225, 224, 223, 222, 221,
        // 右眉毛
        441, 442, 443, 444, 445,
        // 鼻筋
        197, 195, 5, 4,
        // 鼻
        115, 237, 1, 457, 344,
        // 左目 6pt
        33, 160, 158, 133, 153, 144,
        // 右目 6pt
        362, 385, 387, 263, 373, 380,
        // 口(外周)12pt
        76, 74, 73, 11, 303, 304, 306, 321, 405, 17, 181, 91,
        // 口(内周)8pt
        62, 41, 12, 271, 292, 402, 14, 178
    };


    internal void PushAnnotation(WebCamScreenController screenController, FaceMeshValue faceMeshValue)
    {
        if (screenController == null)
        {
            this.screenController = screenController;
        }
        // Debug Logging
        //Debug.Log("PushAnnotation");
        List<NormalizedLandmarkList> landmarkLists = faceMeshValue.MultiFaceLandmarks;
        var drawingCount = Mathf.Min(landmarkLists.Count, 1);


        List<Vector2> newLandmarkPoints = new List<Vector2>();
        //Debug.Log("drawingCount: " + drawingCount);
        for (var i = 0; i < drawingCount; i++)
        {
            NormalizedLandmarkList list = landmarkLists[i];

            if (isEmpty(list))
            {
                //Clear();
                // 何故かここには落ちない
                return;
            }

            //Debug.Log("CalculateSize: " + list.CalculateSize());
            for (var j = 0; j < Mathf.Min(list.CalculateSize(), 68); j++)
            {
                var landmark = list.Landmark[dlib68Order[j]];
                newLandmarkPoints.Add(new Vector2(landmark.X * imageWidth, landmark.Y * imageHeight));
            }
        }
        // ここで顔が「カメラから消えた」時にCountが0になる。
        //Debug.Log("total landmarkPoints: " + newLandmarkPoints.Count);
        landmarkPoints = newLandmarkPoints;
    }
MediapipeForUnityでは毎フレーム推論が終わるたびに PushAnnotation メソッドを呼ぶので、上記のように実装して出来たList<Vector2>をGetFaceLanmarkPointsメソッドで返してあげれば良い。MediapipeとCVVTuberでアップデートのライフサイクル(?)が違うのでイケてねーと思いつつこういう実装してあげれば動く。

ポイントの番号は上記コードに記載されているURLを参考に当てただけなので、ちょっと違うかもしれないので使う時は確認してみて下さい。
やっぱりうまく動かなかったのでBlenderで3Dビューで頂点のIndexを確認した。

Blender2.8系以上の最新版をインストール(無ければ)→「編集→プリファレンス→インターフェイス→開発者youオプション」をチェック→468の頂点を持つARキット用のFBXをインポート→「ビューポートオーバーレイ→インデックス」にチェックで上図のようになる。
現状適当なポイント当ててしか動作確認していないので、ガッツリ使うのであれば動作確認を含めて利用する頂点のIndexはチューニングする必要がありそう。

DllNotFoundException: mediapipe_c

公式のREADME.mdに雑にヒントが書いてあるけれど、実際は「ライブラリのビルドを行う前にUnityエディタでプロジェクトを開くと必要だったmetaファイルが消されて依存関係がぶっこわれる」ような動きをしているので、もし見に覚えがあったらソースコードを落とし直して、Packagesフォルダをまるごと上書きしてあげればmetaファイルが復活する。

DllNotFoundException: mediapipe_jni

Android向けにビルドした時にこうなる場合→Player Settingsを見直しましょう。
ソースコードに含まれているデモアプリで動くが、自分のアプリに突っ込んだ時に何故かこのエラーが……という場合、Player SettingsをデモアプリのようにIL2CPPでビルドしてみたり、APIレベルをいじってみましょう(深堀りしていないけれどコード呼び出しの時にMonoだとバグる気がする。)

WebCam一発でフルトラの時代が来そう

某社が某社と協業してWebカメラによる高品質なフルトラ技術を作った、などのPRが出てきたので、アバターを使ったアプリがどんどんコモディティ化してきそうな予感。
僕はOSSのバグトラする位しか貢献できないので、色々いじった結果をこういう記事にしてみた感じ。