やってみよう!Kinectアプリ開発 - 第10回 ジェスチャー認識

2012-08-03 17:57

zigsow 長谷川 勇
 

はじめに

前回はKinect™ for Windows® SDKを使った実用的なアプリケーションのために、ポーズ認識の基本について説明しました。今回はその続きとして、ジェスチャー認識の基本について説明します。

ジェスチャーの認識

ジェスチャー認識ロジックの考え方

前回ジェスチャーとポーズの違いとして、 ジェスチャーは「ある姿勢とそれに付随する動きを含めたもの」と説明しました。「姿勢」に関しては前回ポーズの認識を説明しましたが、「動き」はどのように認識すればよいでしょうか? これはアプリケーションやライブラリによっても異なりますが、一般的にはキーとなるポーズを複数定義し、 順に遷移したものとして認識できそうです。

例えば、「右手を上げる」というジェスチャーを認識する場合を考えると、以下の2つのポーズがキーとなります。

  1. 右手を下げているポーズ
  2. 右手を上げているポーズ

これら2つのポーズを基にして、まず1のポーズをとり、その後2のポーズをとった場合に「右手を上げる」というジェスチャーを行ったと考えることができそうです。

次に、もう少し複雑な場合を考えましょう。「右手を頭の上で右から左に振る」ジェスチャーを考えます。まず、こちらも同様に2つのキーとなるポーズを考えることができます。

  1. 右手を右上(頭より上)に上げる
  2. 右手を左上(頭より上)に上げる

そして、1→2と順にポーズをとった場合(図1)に、 上記のジェスチャーを行ったと考えることができるでしょうか?


図1: 右手を頭の上で右から左に振るジェスチャー

残念ながらこれだけでは不十分なこともあります。例えば、1の状態から右手を右に一周させて左上に上げたとしたら(図2)、1→2と順にポーズをとってはいますが、右手を左に振ったわけではないので(むしろ右に振っていますよね)、上記のジェスチャーとして認識するべきではないでしょう。


図2: 右手を右に回した場合

これを防ぐために、ポーズとポーズの間の遷移状態における条件を定義する方法があります。例えば今回の場合では以下のように遷移状態における条件を考えます。

  1. 右手を右上(頭より上)に上げる
    • (遷移状態)右手は頭より下にならないこと
  2. 右手を左上(頭より上)に上げる

このように遷移状態の条件を定義し(図3)、ポーズ1からポーズ2に移る際にその条件を維持していた場合だけジェスチャーを行ったと判断することで、ジェスチャーを正しく認識することができます。先ほどの右手を右に一周させるようなケースでは、ポーズ1(右手が右上)からポーズ2(右手が左上)に移る際に右手が下がってしまうため、遷移状態の条件を満たさずジェスチャーではないと判断することができます。


図3: 遷移状態の条件

まとめると、ジェスチャーの認識の実装は、以下の手順で行います。

  1. ジェスチャーからキーとなるポーズを抜き出す
  2. ポーズ間の遷移状態の条件を挙げる
  3. それらのポーズと遷移状態を認識するロジックを作る

ジェスチャー認識の具体例

それでは、もう少し複雑な例で、具体的なジェスチャー認識を考え、それを実装してみましょう。ここでは「右手を右上、右下、左下、左上を通りぐるっと一周回す」ジェスチャーを考えます。(図4)


図4: 右手を一周回すジェスチャー

まずは、このジェスチャーからキーとなるポーズを抜き出します。

  1. 右手を右上(頭より上)に上げる
  2. 右手を右下(腰より下)に下げる
  3. 右手を左下(腰より下)に下げる
  4. 右手を左上(頭より上)に上げる

そして、1→2→3→4→1と順にポーズをとるときの遷移状態の条件を考えます。

  • (遷移状態 1→2)右手は右側にあり、直前の姿勢よりも右手が下にあること
  • (遷移状態2→3)右手は腰より下にあり、直前の姿勢よりも右手が左にあること
  • (遷移状態3→4)右手は左側にあり、直前の姿勢よりも右手が上にあること
  • (遷移状態4→1)右手は頭より上にあり、直前の姿勢よりも右手が右にあること

これで、キーとなるポーズとそれらの間の遷移状態ができました。 これらを認識するロジックを考えます。

  • ポーズ1:
    • 「右手首」と「頭」の座標のX、Y成分を比較し、「右手首」が「頭」より右上にある
  • 遷移状態 1→2:
    • 「右手首」と「腰」の座標のX成分を比較し、「右手首」が「腰」より右にある
    • 「右手首」の座標のY成分を直前と比較し、直前よりも下にある
  • ポーズ2:
    • 「右手首」と「腰」の座標のX、Y成分を比較し、「右手首」が「腰」より右下にある
  • 遷移状態 2→3:
    • 「右手首」と「腰」の座標のY成分を比較し、「右手首」が「腰」より下にある
    • 「右手首」の座標のX成分を直前と比較し、直前よりも左にある
  • ポーズ3:
    • 「右手首」と「腰」の座標のX、Y成分を比較し、「右手首」が「腰」より左下にある
  • 遷移状態 3→4:
    • 「右手首」と「腰」の座標のY成分を比較し、「右手首」が「腰」より左にある
    • 「右手首」の座標のY成分を直前と比較し、直前よりも上にある
  • ポーズ4:
    • 「右手首」と「頭」の座標のX、Y成分を比較し、「右手首」が「頭」より左上にある
  • 遷移状態 4→1:
    • 「右手首」と「頭」の座標のY成分を比較し、「右手首」が「頭」より上にある
    • 「右手首」の座標のX成分を直前と比較し、直前よりも右にある

※ ここで書いているロジックは、ジェスチャーを認識する対象のプレイヤーが、 Kinectセンサーの方を向いていることを前提にしています。 横や後ろを向いているプレイヤーのジェスチャーを認識しようとすると、 もう少し複雑な数学になってしまうのでここでは省略します。 なお、実際問題としてKinectで横や後ろを向いているプレイヤーを扱う場合、 正しい骨格情報がとれないことがあるので、あまり考えなくてもよいのかもしれません。

アプリケーションの修正

それでは、上で説明したジェスチャーの認識ロジックを実装してみましょう。 前回作成したアプリケーションを修正し、 上記のジェスチャーを行ったときに、マスク画像のサイズを2倍にしてみます。 今回もコードの修正だけですので、順に見ていきましょう。

  • フィールドの追加
    以下のフィールドを追加します。

    • MOVE_PLAY: 遷移状態の判定において「直前よりも下」などの条件を置いているが、センサーの誤差などで一瞬上に移動しているように見えることもあるため、この値までの移動は戻ったとはみなさない。
    • gestureState: プレイヤー毎のジェスチャーの途中経過の状態を表す。初期状態は0、ポーズ1を認識すると1、ポーズ2を認識すると2、と変化していく。
    • prevSkeleton: プレイヤー毎の直前の姿勢を取得するために前回の骨格情報を保持する。
  • 初期化処理の追加

    フィールドの追加で定義したバッファ(gestureStateとprevSkeleton)の初期化を行います。これらは、プレイヤー毎のデータを保持するので、配列のサイズはプレイヤーの数(FrameSkeletonArrayLength)にします。
  • ポーズの認識メソッド(checkPosture)の修正

    前回作成したメソッドと同様の考え方で、引数として与えられた骨格情報のとるポーズの番号を返します。ただし今回のポーズの番号は、上で抜き出したキーとなるポーズの1~4です。各ポーズの認識ロジックも先ほどの説明の通りです。
  • 遷移状態の認識メソッド(checkTransState)の追加

    各ポーズ間の遷移状態の条件を判定します。例えば引数stateが1の場合、上記遷移状態1→2のロジックで判定します。
  • ジェスチャーの認識メソッド(checkGesture)の追加

    上記のポーズ認識メソッドと、遷移状態の認識メソッドを使い、ジェスチャーの途中経過の状態と骨格情報から、ポーズをとっているか、遷移状態の条件を満たしているか判定し、ジェスチャーの認識を行います。このメソッドの引数は、それぞれ「現在の骨格情報」「現在のポーズ番号」「直前の骨格情報」で、戻り値は「認識したジェスチャーの番号」と「現在のポーズ番号」の組です。
  • ジェスチャー認識メソッドの呼び出しの追加

    前回のアプリケーションでポーズ認識メソッドの呼び出しを行っていた、骨格情報から頭部の位置を取得するメソッド(getHeadPoints)を、ジェスチャー認識メソッドを呼び出すように変更します。

実行

修正は以上です。メニューから[デバッグ(D)]-[デバッグ実行(S)]を選択し、実行してみましょう。

アプリケーションが起動したら、右手を大きく回してみてください。ジェスチャーが正しく認識されると、マスク画像が大きくなります。

その他の方法

今回紹介したジェスチャーの認識方法は1例に過ぎず、他にもいくつかの認識方法が考えられます。

例えば、遷移状態の条件は判定せず、キーとなる連続するポーズのみで判定する方法も考えられます。この場合、本来は対象のジェスチャーでない動きも誤認識してしまう可能性がありますが、複雑なジェスチャーの場合は判定が厳しすぎると全然認識されないこともありますので、このような緩い判定の方がよい場合もあります。また、この方法に改良を加え、ジェスチャーやポーズ間に制限時間を設けることで(例えば上記の例で言えば、ポーズ1~4までを1秒以内に行うことなど)誤認識を少なくする方法もあります。興味のある方はいろいろ試してみてください。

まとめ

今回は、ポーズ・ジェスチャー認識の2回目ということで、ジェスチャーの認識の基礎を説明しました。次回は継続的なジェスチャーによる入力を紹介します。

商標について

Kinect、Visual Studio、Windowsは、米国Microsoft Corporationの米国およびその他の国における登録商標または商標です。

2件の質問スレッド
  • プログラム hm 2012-11-08 15:15 数日前から愛読させて頂いております。

    現在、大学でkinectを使ってシステム作成を目指しています。
    システムをつく前に、人のある行動をkinectで読み取る仕組みを勉強していて、参考にさせていただいております。

    第10回のプログラム全体を見ることは不可能なのでしょうか。
    何卒、宜しくお願い致します。
  • 修正の元となるソースは? 某支配人 2012-08-07 21:37 第9回なのか教えてください。


    前回のアプリケーションでポーズ認識メソッドの呼び出しを行っていた、骨格情報から頭部の位置を取得するメソッド(getHeadPoints)を、ジェスチャー認識メソッドを呼び出すように変更します。
    の部分で

    // ポーズのチェック // 追加 第9回
    int pose = checkPosture(skeleton);

    を削除して

    // ジェスチャーのチェック // 追加 第10回
    Tuple<int, int> res =
    checkGesture(skeleton,

    を追加って意味での変更します。

    ってことだと思って入力を進めました。


    が、poseが未定義とされてしまいます。

    ここまで来て、実は元ソースが違うのかな?
    と疑問が出たので質問させていただきます。

    初歩にも満たない質問ですみません。
    1件のコメント
    • kinection.jp管理人 お手間を取らせてしまい申し訳ありません、[ジェスチャー認識メソッドの呼び出しの追加]の部分のソースの画像が途中で切れていました。
      正しいものに差し替えましたのでご確認ください。

      また、ご質問の修正元のソースですが、第9回のものです。

      ご指摘どうもありがとうございました。他にも気づいた点などありましたら、コメントをお願いします。
      2012-08-09 12:52

ジグソープレミアムレビュー

  • やってみようKinect(キネクト)アプリ開発 - ラボクルー集まれ!