【MediaPipe】Landmarkデータから手のポーズを認識するため、”指の状態”を認識する(その2)
カフェチームの山本です。
前回は、指の状態を2値(open/close)で識別し、全指の状態の組み合わせによって手のポーズを認識しました。
今回は、前回の方式では対応できない、もう少し曖昧な形をした手のポーズも認識できるようにするため、識別する手の状態を拡張します。
結論としては、4本指を「指の根本の曲がり角度」「指の根本から先の曲がり角度」で、親指を「他の指との接触」で、それぞれの状態を識別できそうなことがわかりました。
(MediaPipeに関連する記事はこちらにまとめてあります。)
前回の方式
概要
前回の方式は、以下のようなものでした。
- 指の「全関節の曲がり角度の合計」を計算する。閾値によって2値(open/close)の状態を判定する
- 判定の組み合わせによって、手のポーズを認識する
これは、わかりやすいサインを識別するときなどには、問題なく利用可能そうでした。
問題点
しかし、下の動画のような手の状態では、あまりうまく判定できないように思われます。
- 指先だけ曲げる、根本だけ曲げる、指全体を曲げる
- ものを掴む
- ものをつまむ
他にも、以下の再生リストのような手のポーズがあり得ると思われます。
20200601 MediaPipe Multi Hand Tracking 手のポーズ認識 - YouTube
20200604 MediaPipe Multi Hand Tracking 手のポーズ認識 - YouTube
これらのポーズを認識するのが難しいと考えられる理由としては、以下の点が挙げられます。
- 1つ目の動画:指の曲がりは、全体の曲がり角度だけ(1次元)では表せない
- 2つ目の動画:曲がり角度ははっきりとopen/close(2値)に分かれない
- 3つ目の動画:指先の接触を識別することができない
(前提知識:関節の話)
少しだけ指の関節について、解説を入れておきます。下の図のように、指の関節には以下の3種類があります。
- DIP:指の第1関節
- PIP:指の第2関節
- MCP:指の第3関節(付け根、根本のこと)
親指の場合、DIP・PIPにあたる関節が1つのため、IPとして表されます。以降もこの表記を利用していきます。
LandmarkのIndexとの対応は、以下の通りです。
方針
上に挙げた問題点をもとに、前回の方針を以下のように変更します。
- 指の曲がりを、指先だけの曲がり・根本の曲がり(2次元)で判定する
- 曲がりの状態をもう少し細かく分けて(2値ではなく)判定する
- 指先の接触を判定する
データ分析
上で挙げた要素を、Landmarkのデータから判定できるか調べます。以下の数値に着目し、グラフ化してみました。利用した動画は、「問題点」で挙げたものと同じです。
- DIPの曲がり角度(親指はIP)
- PIPの曲がり角度(親指はIP)
- DIPの曲がり角度+PIPの曲がり角度(親指はIP)
- MCPの曲がり角度
- 親指と各指先の距離
曲がり角度は前回と同じく、3次元座標のベクトルから計算しました。親指との距離も、同様に3次元空間内の距離を計算しました。
結果
グラフの説明
グラフ中において、それぞれ以下を表しています。
- 各凡例の最後の数字(0~4)は、親指から小指までを順にあらわしています。
- angle1はPIPを、angle2はDIPを、angle12はPIP+DIPを、angle0はMCPを表しています。親指の場合は、angle2がIPを、angle1がMCPを表しています(上の方が曲がっている)
- tip_distは親指の指先と各指先の距離を表しています。(tip_distance_0は常に0です)
- thumb_angle0はLandmark1の角度を、thumb_angle1はLandmark2の角度(=MCP)を、thumb_angle2はLandmark3の角度(=IP)を、thumb_angle3はthumb_angle0+ thumb_angle1を、thumb_angle4はthumb_angle1+ thumb_angle2を、それぞれ表しています。
- angleNの縦軸の単位は度数法です。tip_distの縦軸の単位はピクセルで、手を囲う四角が256ピクセルです。
グラフの結果
各動画における数値は以下のグラフのようになりました
- 結果1:指先だけ曲げる、根本だけ曲げる、指全体を曲げる
- 結果2:ものを掴む
- 結果3:ものをつまむ
分析
グラフを見てみると、以下のようなことがわかりました。
- 指先のグラフ(angle1・angle2・angle12)を見ると、angle1・angle2の単体よりもangle12の方が、ピークがはっきり現れ、数値も安定している。軽く曲げているときと、強く曲げているときで、数値が異なっている
- 根本のグラフ(angle0)を見ると、ピークが見て取れる。軽く曲げているときと、強く曲げているときで、数値が異なっている
- 指先の距離のグラフ(tip_dist)を見ると、ピークがみてとれる。接しているときと、近いときで数値が異なっている
結論
以下のように指の状態を判定できることがわかりました。ただ、4本指に関しては、下の「指先の曲がり」と「根本の曲がり」ような判定ができそうですが、親指の場合、指の曲がりを判定するよりも、他の指先と接しているかなどで、ポーズを判定するのが良さそうです。
指先の曲がり
- 状態:空いている・中間・閉じている
- パラメータ:angle12
- 閾値:50度・120度
根本の曲がり
- 状態:空いている・中間・閉じている
- パラメータ:angle0
- 閾値:30度・50度
指先の接触
- 状態:近い・接している
- パラメータ:tip_distance
- 閾値:25・50 [pix]
結果
動画で確認
今回実装した、指の状態の組み合わせによる判定は以下のとおりです。
- すべての指が閉じているとき
- 親指と人差し指の指先が触れていて、それ以外の指がすべて閉じている
- 親指と人差し指の指先が触れていて、それ以外の指がすべて中間
- 親指と人差し指の指先が触れていて、それ以外の指がすべて空いている
- それ以外の場合
結論の方式で微妙な変化をする手のポーズを認識できるか確認しました。利用した動画は下のようなものです。おおよそ、ポーズの判定ができていることがわかります。ただし、途中で結果がぶれており、精度にやや難があることがわかります。
グラフを見てみると、それぞれの段階がはっきりと現れていることがわかります。判定がぶれている箇所は、おそらくangle12が閾値を下回っているようです(40~60フレーム目)。
まとめ
今回は、識別する指の状態を拡張し、前回の方式で対応できなかったポーズを認識することができました。
次回は、実際に商品を取る際のポーズを認識してみたいと思います。
余談
運用面
指の状態が増えたことで、判定結果の組み合わせが爆発しそうです。運用面やメンテナンス・改修していくことを考えると、汎用的な判定を作るよりも、認識したい手のポーズ専用の判定を作った方が良いと思われます。要件を決めてから取り掛かった方が良さそうです。
機械学習の利用
ここまで複雑になってくると機械学習でやった方が良いような気がします。手の形が数値化できているので、そのまま入力できますし、角度なども追加すれば、そこそこ精度が出る気がします。そのうちチャレンジしたいです。
また、時系列での認識(ジェスチャー)も、時系列データとしてRNNを利用したり、特定の時間幅だけ切り出して分類器にかける、といった方法ができそうです。