Webカメラで表情認識してWebsocketでブラウザアプリ内のキャラを動かす【Python&pyFaceTracker&Live2D版/Unityでやる版】

こんにちは。オリィ研究所( http://orylab.com ) のryo_gridです。
今回はWebカメラで表情認識して2Dアバターを動かす、というのをElectronやWebアプリ(ローカルのWebサーバで配信しているものに限る)で出来るように、Websocketを介して行うというのをやってみました。
まあ、FaceReg ( https://www.4gamer.net/games/032/G003263/20160111001/ ) 的なものを作ってみるって感じです。
実験レベルなのでリップシンク(唇が開いてるか閉じているか)だけ実装します。

#Python&pyFaceTracker&Live2D版
OpenCVを利用して表情認識してくれるpyFaceTrackerというPythonライブラリを使ってまずはやってみました。
アバターはLive2Dのリップシンクのサンプルプログラムに少し修正を加えて利用します。
https://live2d.github.io/CubismJsComponents/

##環境
Mac OS High Sierra 10.13.4
Python 2.7.10
OpenCV 2.4.13.6
pyFaceTracker 0.1.1

##OpenCVをインストールする
homobrewでインストールしてみたところうまく動かなかったので、ソースコードからインストールしました。
詳細は割愛します。気合です。

んでもって、インストール後、何かエラーが出て、こんなことをしないといけなかった記憶があります。

```shell:
cp /usr/local/Cellar/opencv@2/2.4.13.6_2/lib/python2.7/site-packages/cv2.so <自分の使っているPythonのsite-packagesのパス>
```

もしかしたら、OpenCVビルド時にpythonなりなんなりの言語からOpenCVを叩くための共有ライブラリをビルドするということを追加してやったかもしれません。すみません、正確なところは忘れてしまいました。。。

##pyFaceTrackerともろもろをインストール
おおむね、以下のサイトと同じようにすればよいと思いますが、setup.pyの修正はいくらか追加でやる必要があった記憶があります。
https://nixeneko.hatenablog.com/entry/2016/05/02/112638

あと、Websocketサーバを実装するためのモジュールをインストールします。

```shell
brew install libevent
pip install gevent gevent-websocket
```

http://d.hatena.ne.jp/hekyou/20120712/p1

他にもカメラ画像をウィンドウで移すためにあれこれやった気がしますが、とりあえず最終的に私の環境に入っているpythonモジュールは以下のようになりました(virtual env でこのプロジェクト用のPython環境を作っています)。

```text
%pip list
Package                       Version
----------------------------- -------
apptools                      4.4.0  
backports.functools-lru-cache 1.5    
configobj                     5.0.6  
cycler                        0.10.0 
gevent                        1.3.3  
gevent-websocket              0.10.1 
greenlet                      0.4.13 
kiwisolver                    1.0.1  
matplotlib                    2.2.2  
mayavi                        4.5.0  
numpy                         1.14.3 
Pillow                        5.1.0  
pip                           10.0.1 
pyface                        6.0.0  
pyparsing                     2.2.0  
python-dateutil               2.7.3  
pytz                          2018.4 
scipy                         1.1.0  
setuptools                    39.2.0 
six                           1.11.0 
subprocess32                  3.5.1  
traits                        4.6.0  
traitsui                      6.0.0  
vtk                           8.1.0  
wheel                         0.31.1 
wxPython                      4.0.1 
```


##表情認識サーバを作る
ソース一式はこちら。
https://github.com/ryogrid/faceTrackServer/tree/5b4c6eaca50a66969d3e5111f7ddf4fe4689b27c

基本的にはpyFaceTrackerのサンプルコードであるface_video.pyに手を加えたものです。
https://github.com/ryogrid/faceTrackServer/blob/5b4c6eaca50a66969d3e5111f7ddf4fe4689b27c/src/facetrack_server.py

返ってくる特徴点がどのパーツかわかるようになっておらず最初は頭を抱えましたが、毎回、各パーツは同じインデックスで返ってくることを突き止めたので、あとは唇の特徴点群のY-MAXとY-MINをとってその差が一定以上になったら唇が開いている、そうでなければ閉じていると判定して、その結果をWebsocketで送信するようにしました。

##Websocket通信してリップシンクするアバターを作る
次に、アバターを用意します。
Live2Dのサンプルプログラムを持ってきます

```shell
git clone https://github.com/Live2D/CubismJsComponents.git
```

https://github.com/Live2D/CubismJsComponents/blob/a7db4dc0cb2ed474589e0389c3e3caf4b9d6bc69/example/wwwroot/js/pixilipsync.js
のonLipsync関数を以下のように変更します。

```javascript:pixilipsync.js
        var onLipsync = function () {
            emptyAnimation.evaluate = function (time, weight, blend, target) {
                var param_mouth_open_y = target.parameters.ids.indexOf("PARAM_MOUTH_OPEN_Y");
                if (param_mouth_open_y < 0) {
                    param_mouth_open_y = model.parameters.ids.indexOf("ParamMouthOpenY");
                }
                if (param_mouth_open_y >= 0) {
                    if(ripOpen == true){
                      target.parameters.values[param_mouth_open_y] = 1
                    }else{
                      target.parameters.values[param_mouth_open_y] = 0
                    }

                }
            };
```

1行目の下に以下を挿入します。

```javascript:pixilipsync.js
var ripOpen = false;
```

50行目の下に以下を挿入します。

```javascript:pixilipsync.js
        var ws = new WebSocket("ws://127.0.0.1:8001/");
        ws.onmessage = function(e) {
          if(e.data == "open"){
            ripOpen = true
          }else{
            ripOpen = false
          }
          console.log("received msg")
          console.log(e)
        };
```

##実行する
###表情認識サーバを実行する。
pythonコマンドではなく、pythonwコマンドなのでお間違いなく。

```shell
pythonw facetrack_server.py
```

これで、8001番にWebsocketサーバが起動します。

###Live2Dの修正したアバターをブラウザで動作させる

```shell
cd CubismJsComponents/example/
python -m SimpleHTTPServer 8000
```

これでwwwroot以下がポート8000番でHTTP配信されます。

あとは、
http://localhost:8000/wwwroot/
にブラウザでアクセスして、Audio Lip Sync のリンクをクリックしてもらえば、表情認識に合わせてリップシンクするアバターが表示されます。
なお、リンクをクリックしたタイミングでブラウザはWebsocket接続をサーバに対して行い、そこでサーバは動作を始めます。ウェブカメラ映像に表情認識した結果のマーカーを載せた映像を映すウィンドウも立ち上がるので、アバターと合わせて確認してみて下さい。

なお、websocketサーバは一度接続されるだけという前提の手抜き実装なので、一度アバターのページを離れたら(閉じたり、別のページに遷移したら)、表情認識サーバは再起動する必要があります。

口を開けている状態
![スクリーンショット 2018-07-20 14.51.36.png](https://qiita-image-store.s3.amazonaws.com/0/12325/fe7b5a03-bb54-a303-6cd5-39a2b9519947.png)

口を閉じている状態
![スクリーンショット 2018-07-20 14.51.40.png](https://qiita-image-store.s3.amazonaws.com/0/12325/c214d199-3475-9644-509d-eeeaf22345c0.png)


顔出し恥ずかしいです(>_<)

#Unity&DlibFaceLandmarcDetector&Live2D版
pyFaceTrackerでは表情認識の精度、特にまぶたについての精度がいまいちだったので、Unityで使えるDlibFaceLandmarcDetectorというのも試してみました。
結果を先に書いてしまうと、全体的に確かにDlibFaceLandmarcDetectorの方が精度は良いような印象でしたが、まぶたについてはそれほど変わらないかなあという感じでした。

基本的には以下の記事を参考にさせていただきました。先人に感謝。
https://qiita.com/utibenkei/items/15925db826721f6bb00c

コードも公開されており、動作させる際の手順もこちらに書いてあります。
https://github.com/utibenkei/DlibFaceLandmarkDetectorWithLive2DSample/tree/3722190a02fb91f29a356e507544da878fae1bd4

なお、有料のUnity Assetを2つ利用し、それらの購入には日本円で一万円ちょっとかかります。

##環境
Windows 10 バージョン1803 64bit
Unity 2018.1.6f1
Live2D SDK for Unity 2.1.04_2
[Asset]
OpenCV for Unity ver2.2.9
Dlib Face Landmark Detector ver1.2.1

##表情認識サーバを作る
基本は上の記事とgithubリポジトリのREADME.md通りにやればOKです。

ただ、ファイルとかの配置パスはそのままだとうまくいかなくて、結局Assetsディレクトリの中は以下のファイルのようになりました。
https://github.com/ryogrid/faceTrackServer/blob/master/AssetsTree.txt

Websocketを利用するために、
https://qiita.com/oishihiroaki/items/bb2977c72052f5dd5bd9
を参考にUnityプロジェクトにwebsocket-shapライブラリをプラグインとして追加します。

WebCamTextureLive2DSample.csは以下のように修正します。
https://github.com/ryogrid/faceTrackServer/blob/df78ec29f3279cd33bdd0cdfa438378223e2f4ba/src/WebCamTextureLive2DSample.cs

大きくはWebsocket回りの処理を追加したのと、今回利用したバージョンのDlibFaceLandmarcDetectorとOpenCV for Unityに合わせた修正です。

あとは、webCamTextureToMatHelper.requestWidth()とwebCamTextureToMatHelper.requestWidth()がどうしても通らなかったなかったので、OpenCV for Unityの方に同じ結果を返すメソッドを別名で追加して、それを利用するといったことを行いました。

```C#:WebCamTextureToMatHelper.cs
        public int requestWidth2()
        {
            return _requestedWidth;
        }

        public int requestHeight2()
        {
            return _requestedWidth;
        }
```

Unityプロジェクトをビルドして動かすと表情認識した結果のマーカが載ったWebカメラの映像が表示されますが、そのままだと何故か真っ暗でかなり見にくい状態で表示されるので、
![b5a5caf3e958b2f3d305e2ad49826b45.png](https://qiita-image-store.s3.amazonaws.com/0/12325/54afa7fd-c1e3-de9c-6765-db6214d01a4a.png)
QuadオブジェクトのシェーダーをDefault-Lineとかに設定してあげます。
![ce2ca06b28a1efdd03828ab666f6f8d8.png](https://qiita-image-store.s3.amazonaws.com/0/12325/0b8ef8fd-9ab6-947b-6f68-b659de82f705.png)

###Websocket通信してリップシンクするアバターを作る
Python&pyFaceTracer&Live2D版と同様です。

##実行する
###表情認識サーバ
普通にUnityの開発環境でFile-Build&Runします。
そして、出てきたウィンドウでよしなにボタンをポチポチすると、Webカメラに映る顔と同期するLive2Dアバターが表示されます。
同時に、8001番ポートにWebsocketサーバが起動します。

###Live2Dの修正したアバターをブラウザで動作させる
Python&pyFaceTracer&Live2D版と同様です。

口を開けている状態
![スクリーンショット 2018-07-20 14.42.58.png](https://qiita-image-store.s3.amazonaws.com/0/12325/a5584277-76e7-6cbb-d8e8-bd8386dca0bc.png)
口を閉じている状態
![スクリーンショット 2018-07-20 14.43.16.png](https://qiita-image-store.s3.amazonaws.com/0/12325/7cb98879-5c2b-b6d0-49c8-d2313f8c7da3.png)


#まとめ
- DlibFaceLandmarkDetectorは売り物だけあって、表情認識の精度はpyFaceTrackerに比べて高い（印象）
- Unityアプリでも問題なくWebsocketサーバが実装できたので、他の好きな言語やプラットフォームでアバターを実装することができる。例えば、Electronアプリなど。
- 初の顔出し(>_<)

以上！