手認識が実用レベルに到達した件

MediaPipe(Google)のHandTrackingで、Issueで話題になっているジェスチャー実装について書きます。

# こんな感じで動きました

百聞は一見にしかずということで先に検証結果です。
![output.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/c3a119b0-364d-c5ea-dca4-52d3242c34f0.gif)

# MediaPipe

今年6月のCVPR2019でMediaPipeの発表があり、
そのアプリケーションのひとつとしてHandTrackingが公開されました。
MediaPipeはMLパイプラインと呼ばれるコードを書かなくても
ビジュアライズツールだけでMLアプリケーションを構築するフレームワークです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/96f2923d-9881-e53c-1faa-9d0ecb177cd0.png)


[ROS](http://wiki.ros.org/ja)をやったことがある人はツールをイメージしやすいと思います。
HandTracking以外にも顔認識、ヘアセグメンテーション、物体認識に物体追跡のアプリケーションが公開しています。

# HandTrackingのマルチプラットフォーム対応状況
下記の環境で動作します。

- デスクトップ(C++)
- iOS(ObjC)
- Android(Java)
- [WebAssembly](https://github.com/google/mediapipe/blob/master/mediapipe/docs/web.md)('19年度末に公開予定)

実際にデスクトップ(Linux)と、iOSで動作を確認しました。
HandTrackingを直ぐに試したい人はWebAssembly版の[Demo](https://github.com/google/mediapipe/blob/master/mediapipe/docs/web.md)が試せます。

# このHandTrackingの何がすごいのか？

実際にHandTrackingが使えるレベルになるには認識精度と処理速度の両方の実現が必要です。
一般に認識精度と処理速度はトレードオフの関係で両立するのは大変です。

アプローチとして、手のひらの認識に絞ることで、指を含めるアプローチと比べアンカー(領域)の数を3～5分の1に減らし、かつ高精度を達成したそうです。
結果として、計算量を減らせ、高速でかつ高精度を達成できたそうです。

詳しくはブログを参照ください。
[On-Device, Real-Time Hand Tracking with MediaPipe](https://ai.googleblog.com/2019/08/on-device-real-time-hand-tracking-with.html)

# 手認識は何処で使えるか？

![r-vte.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/49f721cc-634a-f217-8703-ca42f7fef7b7.gif)

PCの画面操作、VR空間で使えます。

- 仮想オブジェクトとのインタラクション
- ジェスチャーベース制御インターフェース
- 手話の理解

これらタスクに対応するには、ジェスチャーが必須です。
しかし、MediaPipeのHandTrackingではジェスチャーの実装予定はないそうです。

- [Identification of Gesture in hand tracking model #40](https://github.com/google/mediapipe/issues/40)
- [Details on Gesture Recognition Approach #82](https://github.com/google/mediapipe/issues/82)

## ジェスチャーを実装してみる

今回、Issue[#40](https://github.com/google/mediapipe/issues/40)で紹介しているブログの距離索引の実装を試しました。

- [Move Mirror: An AI Experiment with Pose Estimation in the Browser using TensorFlow.js](https://medium.com/tensorflow/move-mirror-an-ai-experiment-with-pose-estimation-in-the-browser-using-tensorflow-js-2f7b769f9b23)

ブログでは全身の姿勢推定(PoseNet)で試していますが、ハンドジェスチャーでも使えるそうです。
実装は、ベクトルをスケールして、L2 normalizationし、コサイン類似度で求めるという内容です。
最後にVPTreeで探索しましたが。。。どうもうまくいきません。

もう一度HandTrackingの実装を思い出します。

今回の手認識はふたつのモデルが使われています。

- 手のひら検出器モデル(BlazePalm) palm_detection.tflite
- 手の目印モデル hand_landmark.tflite

処理手順としては、
1. 手のひらの方向と領域を識別
2. 方向に合わせ回転させた後、領域から手の目印を推論

「あー回転ね、回転したらいいのか」ということで実装したところ記事冒頭の検証結果となりました。

# 導入編

環境はMacで構築しました。
なお、Python版のHandTrackingですが、本家の実装ではないので精度はいまひとつです。
了承のうえお試しください。

導入に際してですが、MediaPipeではTensorflowを拡張した実装があります。
今後Tensorflowでも実装予定のようですが、ビルドが必要で導入が手間です。
なのでそこは飛ばしてカスタムしたモデルを導入しました。
[palm_detection_without_custom_op.tflite](https://github.com/metalwhale/hand_tracking/blob/master/palm_detection_without_custom_op.tflite)

### コマンドラインで構築
```
$ git clone https://github.com/metalwhale/hand_tracking
$ cd hand_tracking
$ wget https://raw.githubusercontent.com/wolterlw/hand_tracking/optical_flow/hand_tracker2.py

// 回転前の値を取得する為の変更点
$ diff -u hand_tracker2.py.1 hand_tracker2.py
--- hand_tracker2.py.1  2019-12-19 18:14:51.767858700 +0900
+++ hand_tracker2.py    2019-12-19 18:14:32.905475000 +0900
@@ -269,6 +269,7 @@
         Minv = np.linalg.inv(Mtr)
         kp_orig = (self._pad1(reg) @ Minv.T)[:,:2]
         hand['joints'] = kp_orig
+        hand['base_joints'] = reg
         return hand

     def __call__(self, img, hands=None):

$ wget https://gist.githubusercontent.com/otmb/d8837508d2694b11fbbda8229b9bb4ec/raw/5f8088239110549d0cab527699f8770b50990c87/hand_gesture.py
$ pip3 install opencv-python tensorflow
$ pip3 install scikit-learn
$ pip3 install vptree
$ mkdir gestures
// gesturesフォルダに適当なジェスチャ画像を追加後実行
$ python3 hand_gesture.py
```

iOSでジェスチャー導入したかったのですが今のところAPIから回転が取得できないようです。
[Access hand landmarks position in iOS #237](https://github.com/google/mediapipe/issues/237)

## 2019/12/25 追記

上の手順ではジェスチャ画像の追加が手間ですので、特徴点を抽出したコードを用意しました。
下記のジェスチャに対応しています。
<img width="400" alt="gestures.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/35973/01e58b7a-0d13-6a14-d0e5-61f8fb84e7d5.png">

```
$ wget https://gist.githubusercontent.com/otmb/d756d3359d74be070f92812db9b13f3c/raw/e7c0e1c64f4c61870c27e5af4c682a9e18b7249e/hand_gesture.py
```

試して認識精度がいまひとつだなと思われた方がいると思います。
実際のMediaPipeの認識精度を確かめるには、WebAssembly版の[Demo](https://storage.googleapis.com/mediapipe-viz.appspot.com/wasm-demos/hand-tracking/hand_tracking_demo.html)を試すと良いと思います。


# おわりに

今回は、ジェスチャー実装を試しました。
ついにか、ようやくかHandTrackingが実用レベルに到達しました。
来年(2020年)は様々な活用が出てきそうですね！