コードレシピに投稿してあるサンプルを題材にLeap Motionでどのように左右の手やどの指かを判別するのかを説明したいと思います。
説明に使うサンプルコードは以下のものです。ダウンロードしなくてもコードレシピのサイトでブラウザ上でコードを確認できます。
でも、励みにいなるのでダウンロードだけでもしていただきダウンロード数が伸びたらうれしいなって。
-----
Leap Motion Sample for Windows Store Apps
http://code.msdn.microsoft.com/Windows-Store-Apps-with-1e42d1ba
このサンプルアプリケーションは、Leap Motion Deviceから取得した指の位置の情報を使って、画面上に1~10までの数字の表示位置を調整して表示します。横方向は指の位置、縦方向は手の平の位置を使っています。
-----
Modelの構造
このサンプルはLeap Motion周りの定義をLeapModelファイルにのみ記載して、画面定義などはLeap Motionから独立した構成をとっています。
このような構成にすることで、Model部分を差し換えて他のNUI用のサンプルに容易に作り替えることができます。
LeapModel.vb(またはLeapModel.cs)ファイルの中は次のようなクラスが定義されています。
Leap Motionデバイスから情報を得たいときは、LeapModelクラスの中でSampleListenerクラスを生成しています。
そうすると、Leap Motionデバイスからフレーム情報が送られてくるとSampleListenerの中で処理をされて、DataイベントとしてLeapModelクラスに通知がやってきます。
LeapMotionクラスでは通知の中には指の位置が入っていますのでそれをItemプロパティに設定してあげてPropertyChangedイベントを発行することでBindingしているXAMLへデータを伝搬します。
SampleListenerの構造
SampleListenerクラスの要は「OnFrame」イベントプロシージャです。このイベントプロシージャは基本クラスであるLeap.Listenerに定義されているメソッドで、SampleListenerの中でOverridesして利用しています。
SampleListenerクラスの中の「On~」なメソッドはすべて基本クラスで呼び出しタイミングが制御されているメソッドをOverridesしたものです。
つまり、イベントはすべてLeap SDK側にて用意されていて、情報を得る処理やその他の必要な処理をそれぞれ各イベントが発生したタイミングで実行できるように、お約束に基づいてイベントプロシージャの中にコードを書いていくのがLeap対応プログラミングの基本的な流れになります。
なお、今回のように手や指の位置を得たいときは、OnFrameイベント時の処理を記述するだけで十分です。
どのように左右の手を判断しているか
Leap Motion SDKから得られる情報では、Kinectのように左手や右手という区別がありません。これはLeap Motionデバイスが「手」「指」「指状のもの」の検出に特化しており、手の先がどのように腕につながり、それがどのように胴につながっているかは検出してないからです。
そこで重要になってくるのが次のような判断です。
VB.NET
- Dim leapFrame As Frame = _controller.Frame
- Dim rightHand As Hand = leapFrame.Hands.Rightmost
- Dim leftHand As Hand = leapFrame.Hands.Leftmost
C#
- Frame leapFrame = _controller.Frame();
- Hand rightHand = leapFrame.Hands.Rightmost;
- Hand leftHand = leapFrame.Hands.Leftmost;
OnFrameイベントプロシージャの引数にはControllerがわたってくるので、Frameメソッドを実行して今回のOnFrameイベントに関連したフレームを取得します。
そしてそのフレームのHandsコレクションのRightMostメソッドは一番右に検出された手の位置、LeftMostメソッドは一番左に検出された手の位置になります。
左右どちらの手かをLeap Motionは検出できませんが手の位置関係は把握できるので、このサンプルではLeap Motionが検出した手の中から一番左右にあるものを左右の手と錦するように割り切っています。つまり、Leap Motionで左右を判断したのではなく、検出した位置から左右の手を識別するコードとなります。
RightMostとLeftMostを使うときの注意点としては、手が1つしか認識されなかった時は同じ手をこの2つのものが返してきてしまう点です。そのため、以下のようなロジックで手が1つしか検出されなかった時に左右を判断します。
具体的にはLeap Motionの中心位置が0であり、左右はX値のプラスマイナス(向かって左がマイナス値、右がプラス値)で左右を判断します。
VB.NET
- If (Not leftFingers.Empty AndAlso leftFingers(0).TipPosition.x < 0) Then
- '左手として判断
- End Id
- If (Not rightFingers.Empty AndAlso rightFingers(0).TipPosition.x > 0) Then
- '右手として判断
- End Id
C#
- if (!leftFingers.Empty && leftFingers[0].TipPosition.x < 0) {
- ///左手として判断
- }
- if (!rightFingers.Empty && rightFingers[0].TipPosition.x > 0)
- ///右手として判断
- }
さて、いかがだったでしょうか。
Leapからは「左手」「右手」のように手の種別があがってこない点、そして、それを補う上でどのような前提で判断しているかをご理解いただけたでしょうか。
ただし、ここで使った判定法はかなり簡易的なもので、例えば手をクロスしたようなときは左右の手が反対に認識されてしまいます。
そのため、更に精度を上げるには、Leap Motionから得られる「手の方向」を使って肘の方向を予測し、肘の左右で手の左右を判断するという方式など検討する価値があります。もちろんこのようなときも変な方向に手首を意図的に向けるなどセンサーを欺こうとされると制度は落ちてしまいますので、センサーを欺く必要ない、または欺いてしまうと不利益になるようなアプリコンセプトの想定が必要でしょう。
どのようにそれぞれの指を判断しているか
手と同様に、指についてもLeap Motionから「親指」「人差し指」「中指」「薬指」「親指」という指種別は上がってきません。
手のグルーピングはできるので、あとは5本の指の指種別を位置関係から判定すればいいのですが、問題は、5本すべてが必ずしも検出されるわけではないという点です。
ちょっとした手の向きですぐに指先をロストしてしまうので、5本全部が検出できなかった時のことも考慮して指種別を特定しなければなりません。
このサンプルでは手の平をLeap Motion側に向けていることを前提として次のようなコードで指種別を特定しています。
VB.NET
- Dim finger = (From x In leftFingers Order By x.TipPosition.x Select x).ToList
- For index As Integer = 0 To finger.Count - 1
- Me.Pos.Finger(index) = finger(index).TipPosition
- Next
C#
- var finger = (from x in leftFingers orderby x.TipPosition.x select x).ToList();
- for (int index = 0; index <= finger.Count - 1; index++)
- {
- this.Pos.Finger[index] = finger[index].TipPosition;
- }
このコードは左手のケースのコードですが、LINQを使って TipPosition.x順に並べます。手種別の判定から左手はLeap Motionの中心から向かって左に位置している訳なので、X軸はマイナスとなり、TipPosition.x順とは小指→薬指→中指→人差し指→親指に整列させるということになります。
もし検出数が5に満たない場合も、得られた位置を左寄せして小指から順番に認識します。