Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
連載:Leap Motion開発入門(C#編)

連載:Leap Motion開発入門(C#編)

Leap SDKで指を検出してみよう(Tracking Hands, Fingers, and Tools)

2015年8月21日 改訂 (初版:2013/11/18)

Leap Motionの最大の特長である手・指を検出するには? Leap Motion Developer SDKを活用した開発方法を詳しく解説。※C++編の同名タイトルと基本的な内容は同じです。

Natural Software 中村 薫
  • このエントリーをはてなブックマークに追加

 今回はLeap Motionの最大の特長である手・指の検出について解説する。Leap Motion Developer SDK(以下、Leap SDKとする)の特長は手や指の扱い方がシンプルであること、座標や方向のベクトルを表すVectorクラス(Leap名前空間)が使いやすいことが挙げられるだろう。これらの情報を組み合わせて、アプリケーションを開発することになるだろう。

 全体のコードは次のリンク先で公開しているので、参照しながら以下の解説を読み進めてほしい。

Leap Motionで取得できるもの

 Leap Motionで取得できる種類には次のものがある。

  • ツール(=棒状のもの)

 指およびツールはPointableなものとしてまとめて扱うこともできる。

 それぞれに、位置や方向、速さや向きといったパラメーターを持っている。

 では取得方法から順に見てみよう。

手/指/ツールを取得する

 手/指/ツールを取得する方法はいくつかあり。手の取得方法と、指/ツールの取得方法について、それぞれ下記に示す。

【手/指/ツール共通の取得方法】
 ●現在のフレームから、検出された指およびツールの一覧を取得し、その中から所望のものを探す(どの手の指かというフィルターはなく、検出した指やツールが全て取得される)
    ・手の一覧はLeap.Frame.Handsプロパティで取得する
    ・指の一覧はLeap.Frame.Fingersプロパティで取得する
    ・ツールの一覧はLeap.Frame.Toolsで取得する
    ・ポインター(=指とツール)の一覧はLeap.Frame.Pointablesプロパティで取得する
  ●現在のフレームで、以前に取得したIDを渡すことで取得する
  ●現在のフレームで、一番左/右/手前の手を取得する
【指/ツールのみの取得方法】
  ●現在のフレームで、検出した手に属する指やツールを取得する

 では、それぞれの取得方法を見てみよう。

現在のフレームで検出した手などの一覧を取得する

 まずは、現在のフレームで検出した全てのツールを取得してみよう。

 上述の通り、Leap.FrameクラスのHandsFingersToolsPointables プロパティで一覧を取得する。次のプログラムでは、取得した手などの数を表示する。なお、v2では指を全て追跡するため、伸びている指だけを取得したい場合にはFingers.Extended().Countを使用する(詳しくは「これは快適! Leap Motion v2で格段に良くなったSkeletal Tracking機能」を参照)。

C#
HandList hands = frame.Hands;
FingerList fingers = frame.Fingers;
ToolList tools = frame.Tools;
PointableList pointables = frame.Pointables;

Console.WriteLine( string.Format( 
  "Frame Data : Hands : {0} Fingers : {1} Extended Fingers : {2} Tools : {3} Pointers : {4}",
    hands.Count, fingers.Count, fingers.Extended().Count, tools.Count, pointables.Count ) );
リスト1 現在のフレームで検出した全ての手/指/ツールを取得する

 リスト1を実行すると図1~3のようになる。片手、両手や手をグーパーしてみると動作が分かる。ツールは太めのペンを使うとよいだろう。

図1 手指を追跡させた
図2 ツールを追跡させた
図3 手およびツールを追跡させた

 なお、手およびツール(棒)を検出させる際には[Leap Motionコントロール パネル]の[トラッキング]タブから[道具の追跡]および[手の追跡]のチェックボックスがチェックされていることを確認する(図4)。

図4 手およびツールの追跡を有効にする
現在のフレームで、以前に取得したIDを渡すことで取得する

 次に、IDを利用した取得方法を解説する。

 手などを検出すると個別に一意のIDが振られる。このIDをキーとして目的のデータを取得する。検出した手などを、フレームをまたいで追跡し続ける場合などに利用する方法だ。

 次のプログラムでは、手のIDを取得し、そのIDの手を取得し続けるものだ。Leap SDKでは手などを検出しなかった場合でも、既定の無効値のデータが返される。そのため、インデックスのチェックなどは不要となっている。有効なIDを取得できた場合には、そのIDでフレームからデータを取得し、表示するようにしている。

C#
if ( handId == -1 ) {
  handId = frame.Hands[0].Id;
}
else {
  Hand hand = frame.Hand( handId );
  handId = hand.Id;

  // 手の情報を表示する
  Console.WriteLine( string.Format( "ID : {0} 位置 : {1} 速度 : {2} 法線 : {3} 向き : {4}",
  hand.Id, hand.PalmPosition, hand.PalmVelocity, hand.PalmNormal, hand.Direction ) );
}
リスト2 手のIDから、同じ手を追跡し続ける
現在のフレームで、一番左/右/手前の手を取得する

 例えば、両手を使った操作、2本の指でマルチタッチを模すような操作で、左右それぞれの手を取得したい場合に利用する。Leap.HandListLeap.FingerListLeap.ToolListLeap.PointableListというクラスそれぞれにLeftmostRightmostFrontmostプロパティが定義されている。例えば、左右の手の位置を取得したい場合には、Leap.HandList.LeftmostおよびLeap.HandList.Rightmostで取得できる。もし手を1つしか検出していない場合には、左右で同じIDが返ってくるので問題ない。

C#
HandList hands = frame.Hands;
Hand leftMost = hands.Leftmost;
Hand rightMost = hands.Rightmost;
Hand frontMost = hands.Frontmost;

Console.WriteLine( string.Format( "左 : {0} 右 : {1} 手前 : {2}",
  leftMost.PalmPosition, rightMost.PalmPosition, frontMost.PalmPosition ) );
リスト3 検出した中での、一番左/右/手前の手を取得する
現在のフレームで、手の情報を取得する

 v2で追加になった左右の手の検出、ピンチおよびグラブ(いずれの意味も後述)の開閉度合い、手の信頼性を取得する。

 右手や左手の判別には、左右の手を出すとよい。手を回転させたり開閉させたりしてみよう。手のひらをLeap Motionに向けた場合と手の甲を向けた場合の値についても見てみよう。

 ピンチは親指と人差し指の開閉度合い、グラブは手全体の開閉度合いだ(グー、パーする動作)。信頼性は、手を回転させてみると値が変わる。信頼性パラメーターの値によって、手の確実性をフィルタリングできる。

C#
Hand hand = frame.Hands[0];
 
Console.WriteLine(
  string.Format("右手 : {0} ピンチ : {1} グラブ : {2} 信頼性 : {3}",
    hand.IsRight, hand.PinchStrength, hand.GrabStrength, hand.Confidence ) );
リスト4 手の情報取得する

※2015/09/04修正: 詳しくは末尾の【更新履歴】をご参照ください。

現在のフレームで、検出した手に属する指やツールを取得する

 これは、ポインター(=手とツール)についての取得方法だ。

 指のデータを利用する際に、「どの手の指か」ということを知りたい場合があるだろう。その場合には、検出した手から指を取得することで実現できる。ツールについても同様だが、ツールは細長い棒状のものになるので、必ずしも手に属しているわけではないことに注意されたい(フレーム内で独立している)。

 次のプログラムでは、検出した全ての手に対して、その手に属するポインター/指/ツールの数を表示している。実際にこのプログラムを実行すると、先のフレームからの取得では、例えば両指10本が取得されるが、このプログラムでは2つの検出した手に対して5本ずつの指が取得できるのが分かるだろう。

C#
// 手に属している指とツールを取得する
foreach ( var hand in frame.Hands ) {
  Console.WriteLine( string.Format( "ID : {0} ポインタ : {1} 指: {2} ツール : {3}",
    hand.Id, hand.Pointables.Count, hand.Fingers.Count, hand.Tools.Count ) );
}
リスト5 手に属している指とツールを取得する

※2015/09/04修正: 詳しくは末尾の【更新履歴】をご参照ください。

図5 手のIDごとに指が追跡される
現在のフレームで、検出した指の情報を取得する

 いよいよ指の情報を取得する。伸びている指について表示する。TypeTipPositionTipVelocityDirection指の種類指先の位置指の動作速度指の向きを取得できる。位置の座標系は後述するLeap Motionの3次元座標系である。これらのデータを使って指を使ったアプリケーションを開発する。

C#
// 指の情報を表示する
foreach ( var finger in frame.Fingers ) {
  Console.WriteLine( string.Format( "ID : {0} 種類 : {1} 位置 : {2} 速度 : {3} 向き : {4}",
    finger.Id, finger.Type, finger.TipPosition, finger.TipVelocity, finger.Direction ) );
}
リスト6 指の情報を取得する

 例えば、チョキ(=人差し指と中指)を出すと次のようになる。指の種類は親指がFinger.FingerType.TYPE_THUMB、人差し指がFinger.FingerType.TYPE_INDEX、中指がFinger.FingerType.TYPE_MIDDLE、薬指がFinger.FingerType.TYPE_RING、小指がFinger.FingerType.TYPE_PINKYとなる。

図6 指の情報を取得する
検出した指の関節情報を取得する

 最後に指の関節の情報を取得する。Leap Motion SDKではBoneクラスとして定義されており「指骨」を表す。指骨の場所Leap.Bone.BoneType列挙体で次の4つが定義されている。

意味
TYPE_METACARPAL 中手骨
TYPE_PROXIMAL 基節骨
TYPE_INTERMEDIATE 中節骨
TYPE_DISTAL 末節骨
Leap.Bone.BoneType列挙体

 指骨の位置PrevJointCenterNextJointで取得でき、指骨の場所と位置の関係は次のようになっている。

図7 指の情報を取得する

 これらをコードにすると次のようになる。例えばPrevJointのみを描画していけば、Leap Motionのビジュアライザーのような手のモデルを表示できる。

C#
// 指の関節情報を取得する
foreach ( var finger in frame.Fingers ) {
  // 末節骨(指先の骨)
  var bone = finger.Bone( Bone.BoneType.TYPE_DISTAL );
  Console.WriteLine(string.Format("種類 : {0} 中心 : {1} 上端 : {2} 下端 : {3}",
    bone.Type, bone.Center, bone.PrevJoint, bone.NextJoint ));
}
リスト7 指骨の情報を取得する

 なお、親指については骨が3つとなっている。下記のコードで、どの骨が有効であるかを確認できる。実行させるとTYPE_METACARPAL(中手骨)の長さが0であるため、Leap MotionとしてはTYPE_PROXIMAL(基節骨)、TYPE_INTERMEDIATE(中節骨)、TYPE_DISTAL(末節骨)から構成されていることが分かる。

C#
// 親指の定義を確認する
foreach ( var finger in frame.Fingers ) {
  if ( finger.Type == Finger.FingerType.TYPE_THUMB ) {
    for ( int t = (int)Bone.BoneType.TYPE_METACARPAL; t <= (int)Bone.BoneType.TYPE_DISTAL; t++ ) {
      var bone = finger.Bone( (Bone.BoneType)t );
      Console.WriteLine( string.Format( "種類 : {0} 長さ : {1}", bone.Type, bone.Length ) );
    }
  }
}
リスト8 指骨の情報を取得する

手や指から得られる情報

 手などの取得方法を理解したところで、そこからどのような情報を得られるのかを見てみよう。Leap.Hnadクラスから得られる主な情報を次の表に示す。

プロパティ得られる情報
PalmPosition 手の位置
PalmVelocity 手の動作速度(ミリメートル/秒)
PalmNormal 手の法線ベクトル
Direction 手の向き
SphereCenter 手にフィットする球体の中心座標
SphereRadius 手にフィットする球体の半径
Hnadクラス(Leap名前空間)から得られる主な情報

 およびツールはそれぞれLeap.FingerクラスとLeap.Toolクラスであり、両方ともポインターを表すLeap.Pointableクラスを継承している。Leap.Pointableクラスで取得できる主な情報を次の表に示す。

プロパティ得られる情報
TipPosition ポインターの位置
TipVelocity ポインターの動作速度(ミリメートル/秒)
Direction ポインターの向き
IsFinger ポインターが指かどうか
IsTool ポインターがツールかどうか
Pointableクラス(Leap名前空間)から得られる主な情報

 これらの座標系は次の図に示すLeap Motionの座標系で取得できる。この座標を任意の形に加工してアプリケーションで使うことになるだろう。

Leap Motionの座標系(Leap Motion SDKのAPIドキュメントから引用)
Leap Motionの座標系(Leap Motion SDKのAPIドキュメントから引用)

 手にフィットする球体の座標および半径は、次の図の情報だ。

手の球(Leap Motion SDKのAPIドキュメントから引用)

[補足]Leap.Vectorクラス

 手の位置や、法線、速度はベクトルとして返される。ベクトルはLeap.Vectorクラスで表されるが、標準で距離や内積、外積、四則計算など、よく使う機能がすでに実装されている。

 より詳細な機能は、「Leap.Vectorのリファレンス」を参照してもらいたいが、このようにSDKレベルで細かい機能が提供されていることは非常にうれしい(と感じるのは、Kinect SDKやPerC SDKなど、Vectorクラスが最低限の座標データを保持する機能しか持っていない環境の利用経験が長かったからだろうか……)。

まとめ

 このようにLeap SDKでは手や指のさまざまなデータを簡単に扱える。扱い方もほぼ同じなので、1つを覚えれば簡単に他のデータも扱えるだろう。また、Vectorクラスが非常に使いやすいので、他のSDKでも使いたいものだ。

【更新履歴】2015/09/04

 以下の誤りがありましたので、お詫びして訂正させていただきます。

 リスト4がC++で掲載されていましたが、C#に修正しました。

 リスト5にhand.Fingers.Countが2つありましたが、1つはhand.Tools.Countの誤りでした。

連載:Leap Motion開発入門(C#編)
1. C#によるLeap Motion v2開発の全体像

Leap Motion Developer SDKを利用してC#でLeap Motionのアプリケーションを開発する方法を解説する連載(2015年改訂版)。今回はC#の開発環境など、開発の基礎を紹介。

連載:Leap Motion開発入門(C#編)
2. 【現在、表示中】≫ Leap SDKで指を検出してみよう(Tracking Hands, Fingers, and Tools)

Leap Motionの最大の特長である手・指を検出するには? Leap Motion Developer SDKを活用した開発方法を詳しく解説。※C++編の同名タイトルと基本的な内容は同じです。

連載:Leap Motion開発入門(C#編)
3. Leap Motionでのタッチ操作はどう開発するのか?

Leapアプリのタッチ操作の認識方法と開発方法を説明。※C++編の同名タイトルの記事と基本的な内容は同じです。

連載:Leap Motion開発入門(C#編)
4. Leap Motionのカメラ画像を取得する

Leapアプリのタッチ操作の認識方法と開発方法を説明。新規書き下ろし。※C++編の同名タイトルの記事と基本的な内容は同じです。

連載:Leap Motion開発入門(C#編)
5. フレームのいろいろな使い方

Leap Motion公式のサンプルを題材に、さまざまなフレームの扱い方についてコードを交えて解説。※C++編の「Leap SDKのいろいろな使い方」と基本的な内容は同じです。

サイトからのお知らせ

Twitterでつぶやこう!