やってみよう!Kinectアプリ開発 - 第5回 深度情報の利用

2012-06-29 18:31

zigsow 長谷川 勇
 

はじめに

前回はKinect™の最大の特長である骨格情報の利用を紹介しました。今回は前回説明を飛ばした深度情報について、Kinect™ for Windows® SDKからの利用方法を紹介します。

深度情報

骨格情報からの深度情報の取得

前回作成したアプリケーションは、人物がカメラからどれだけ離れていても、顔に上書きする画像のサイズは一定でした。顔に上書きする画像は、人物がカメラに近いほど大きく、カメラから遠いほど小さくなる方が自然ですよね。まずはこの仕様の実現を考えます。

直観的には、Kinectから深度情報の取得が必要と考えてしまいますが、実は深度情報は不要で、すでに取得済みの骨格情報だけで実現できます(もちろん内部的にはKinect自身が骨格の認識に深度情報を使っています)。骨格情報には各関節の位置情報として、 Kinectセンサーから見た三次元空間上の座標が含まれていますが、そのZ座標がまさにカメラからの深度であるためです。

図1:三次元空間上の座標とカメラからの深度

これが前回骨格情報を先に説明した理由です。アプリケーションを開発する場合、明示的にKinectから深度情報を取得しなくても、骨格情報に含まれる深度情報だけで十分なこともあります。このため、必ず Depth Stream を有効にして、フレーム毎に深度情報をバッファにコピーして・・・という処理が必要なわけではありません。アプリケーションが何を必要とするかに応じて利用するストリームを選択するとよいでしょう。

一方、深度情報が全く不要なわけではありません。深度情報を使うことで、骨格情報だけではできないことも可能になります。以降でそれらの機能を紹介します。

深度センサーからの深度情報の取得

Kinectは赤外線照射器と赤外線センサーを使い、Kinectセンサーの正面の各点の深度を測定できます。取得できる深度はNear Modeで0.4m~3m、Default Modeで0.8m~4mの範囲で深度の単位はミリメートルです(取得できる値の単位がミリメートルですが、取得した数値の精度がミリメートルという意味ではありません)。

なお、本連載では「距離」ではなく「深度」と表記します。これは、Kinectで測定するのが「カメラの平面からの距離」であり、「カメラからの直線距離」ではないためです。(図2)

図2:距離と深度

Kinect for Windows SDKから取得する深度情報は、カメラから取得する画像情報と同様に、ビットマップの形で取得できます。その解像度は 640×480(デフォルト)、320×240、80×60から選ぶことができます。

アプリケーションからの深度情報の利用

深度情報を利用するアプリケーションは多くの場合、以下の処理パターンになります。(後述しますが、座標変換が深度情報から画像情報の方向に限定されるためです)

  1. カメラからの画像情報をバッファにコピー。
  2. 深度センサーからの深度情報をバッファにコピー。
  3. 深度情報上の各点を調べ、深度・プレイヤーの有無から処理を決定。
  4. 決定した処理に基づき、深度情報上の各点に対応する画像情報上の点を加工。

ここで注意が必要なのは、カメラと深度センサーどちらも640×480の解像度で取得したとしても、それらから見える範囲は同じではないことです(図3)。

図3:カメラと深度センサーの範囲イメージ

このため、深度情報を取得したある点に対して、対応する画像情報を取得する場合は座標の変換が必要になります。

さらに、画像情報上の点のうち、対応する深度情報上の点がないものがあることにも注意が必要です。典型的には図3に示す通り画面の端ですが、それ以外にも深度が大きく変わるような点には深度情報がない点が出ることがあります。(ここは文章で説明するより、実際に試す方が分かりやすいので、後ほど紹介するサンプルアプリケーションも試してみてください)このため、深度情報がない画像情報上の点があっても処理が破たんしないようにする必要があります。

また、カメラの解像度を深度センサーの解像度よりも大きくしている場合も同様です。例えば、カメラを640×480、深度センサーを320×240とした場合、大まかに考えると深度情報上の1点が画像情報上の4点に対応します。しかしSDKの用意する座標変換では、深度情報上の1点から画像情報上の1点しかとれません。ここで上記のような処理パターンを考えると、深度情報により加工されるのは4ピクセルのうち1ピクセルだけ、ということになってしまいます。

これを解決するには上記の処理パターンの3で、画像情報上の各点から逆に座標変換して、深度情報上の点を取得し、それに従って処理すればよいと考えられます。このアイデアは良さそうに見えるのですが簡単には実現できません。 SDKの座標変換が画像情報上の点から深度情報上の点への変換を用意していないためです。このため深度情報を使う場合は、カメラと深度センサーの解像度を合わせておくのが無難でしょう。

プレイヤー認識

Kinectの大きな特長として、カメラと深度センサーから取得した画像情報と深度情報をもとに、 6人までのプレイヤーを認識することができます。(順番が逆になりましたが、これが前回説明したプレイヤーの認識です)

深度センサーから取得する深度情報の各点には、プレイヤーの有無に関する情報が含まれ、0(プレイヤーがいない)、1~6(上記の6人のうちの何番目のプレイヤーか)の値をとります。骨格情報からはプレイヤー(とその関節)の位置が取得できましたが、深度情報からはその点にどのプレイヤーがいるかを取得できます。つまり、深度情報を利用することでプレイヤーの輪郭の取得といった処理が可能になります。なお、プレイヤーとして認識されるのは人体だけではなく、何か手に持っている場合は、そこまで含めてプレイヤーとして認識されているようです。後で作成するアプリケーションでもこの辺りを紹介しますので、いろいろ試してみてください。

作成するアプリケーションについて

今回は以下の3種類のアプリケーションを作成します。(といっても2つ目と3つ目はほとんど同じです)

  1. マスク画像の拡大・縮小
    関節の位置情報を使い、深度によってマスク画像を拡大・縮小します。
  2. 深度情報の表示(画像・深度情報が同一の解像度の場合)
    深度情報を取得し、プレイヤーがいない点を青く塗ります(画像情報640×480、深度情報640×480)。
  3. 深度情報の表示(画像・深度情報が異なる解像度の場合)
    同上(画像情報640×480、深度情報320×240)。

なお、2と3は今回限りの実験です。次回は1をベースにしますので、1で作ったプロジェクトを保存しておいてください。

3件の質問スレッド
  • depMaskBuffer 工藝 太郎 2014-11-19 14:22 上に書いてあるプログラムを打って、エラーは出なかったのですが、

    depMaskBuffer[clrIdx ⋇ 4] = 255; の部分に

    「IndexOutOfRangeExceptionはハンドルされませんでした。」 ”インデックスが配列の境界外です。”と出ました。
    どうすればいいのか教えてください。
    トラブルシューティングのヒント:
    リストの最大インデックスが、リストサイズよりも小さくなることを確認します。
    インデックスが負の数でないことを確認します。
    データ列名が適切であることを確認します。
    この例外に関する一般的なヘルプを参照します。
    と書いてありました。
  • マスク画像の拡大縮小のプログラミング のぎむ 2012-08-22 09:17 自分のペースでこちらのkinect開発を進めています!

    前回で行った「第四回の骨格情報」のおまけで頭の傾きをマスク画像に反映させるプログラムを習いました。

    そして、今回その続きからプログラミングをするようになっています。なんとか、今回の第五回目も動作させる事が出来て一安心でした。

    そこで、前回までの復習に、傾きを加えずに拡大縮小させてみようと、第四回目のおまけ前のソースで試してみました。

    しかし、Item1などがエラーが出てしまい、(Mtrix4を追加したら消えたのですが)どこを変えれば動作するのかわかりません。

    どうかご教授いただけたらと思います。よろしくお願いします。
    4件のコメント (すべて表示)
    • のぎむ 某支配人さん、ありがとうございます。

      教えて頂いたおかげで、
      Item1のエラーは消えました。
      しかし、
      //頭の位置を保存
      results.add(head.Position);
      の部分が消えません。

      ListをList<Tuple<SkeletonPoint, Matrix4>> に変更したので、

      // 頭の位置を保存
      results.Add(Tuple.Create(head.Position, 〇〇));

      という形になると思うのですが、Item2を要求され、エラーが出てしまいました。

      実際のサンプルは、Item2に傾きが入っていた為、

      // 頭の位置を保存
      results.Add(Tuple.Create(head.Position, headMtrx));

      となっていたのですが、今回は傾きを入れないのでheadMtrxは出てきていません。
      当然、入力するとエラーがでます。

      // 頭の位置を保存
      results.Add(Tuple.Create(head.Position,   ここ ));
      ここにはなにを入れたらよいのでしょうか?


      度々、申し訳ありません。
      2012-09-09 12:24
    • 某支配人 私も理解出来てるつもりは無い部分なのですが、
      Item1が無いってことなら

      // RGBカメラの画像情報に対して、顔の位置にマスクを上書きして描画する  // 追加 第4回
          private void fillBitmap(KinectSensor kinect,ColorImageFrame imgFrame,
                      List<SkeletonPoint> headList)
          {



      // RGBカメラの画像情報に対して、顔の位置にマスクを上書きして描画する  // 追加 第4回
          private void fillBitmap(KinectSensor kinect, ColorImageFrame imgFrame,
                      List<Tuple<SkeletonPoint, Matrix4>> headList)  //
          {

      このあたりが怪しいと思います。
      合わせて

      private List<Tuple<SkeletonPoint, Matrix4>> getHeadPoints(SkeletonFrame skelFrame)   // 変更

      ここも候補となります。

      きちんとしたアドバイスにならなくてごめんなさいネ
      2012-08-28 23:45
    • のぎむ 返答遅れてしまい、申し訳ありません。

      某支配人さん、丁寧な回答して下さりありがとうございます。
      某支配人さんのレビューを参考にさせて頂いていたので、回答を頂いて驚きました。

      拝見して改めて認識したのですが、
      "Itemの定義が含まれていない"
      とエラーが出てしまいます。


      第四回のP2までに作成したソースで打ち込んだ為、
      必要な文が足りていない事に気づき、
      自分なりに解釈してMatrix4がxとyだけでなくzも取得する構文なのではと考え、
      「処理結果のリストを空状態で作成」(第四回のP3、上段)
      var results = new List<Tuple<SkeletonPoint, Matrix4>>();
      を追加しました。

      しかし、打ち込んでみたところ次々とエラーが出力され、
      すべてのエラーを消す為に頭の向きを取得まで打つ始末。

      Itemのエラーを無くすにはMatrix4を追記する必要があるのでしょうか?

      その場合、
      //頭の位置・向きを保存(第四回のP3)
      results.Add(Tuple.Create(head.Position, headMtrix));

      では、どのように変える必要があるのでしょうか?

      全くの初心者で申し訳ありません。
      よろしくお願いします。
      2012-08-28 17:21
    • 某支配人       // getHeadPointsで取得した、各東部の位置毎にループ    // 追加 第4回2nd
            for (int idx = 0; headList != null && idx < headList.Count; ++idx)
            {
              // 骨格の座標から画像情報の座標に変換
              SkeletonPoint headPos = headList[idx].Item1;    // 追加 第4回2nd
              ColorImagePoint headPt
                = kinect.MapSkeletonPointToColor(headPos, rgbFormat);  // 追加 第4回2nd

              // 距離に応じてサイズを決定   // 追加 第5回
              int size = (int)(192 / headPos.Z);

              // 頭の位置にマスク画像を描画
              Matrix4 headMtrx = headList[idx].Item2;
              Matrix rot = new Matrix(-headMtrx.M11, headMtrx.M12,
                          -headMtrx.M21, headMtrx.M22,
                          headPt.X, headPt.Y);
              drawContext.PushTransform(new MatrixTransform(rot));
              //Rect rect = new Rect(- 64, - 64, 128, 128);
              Rect rect = new Rect(-size / 2, -size / 2, size, size);  // 変更 第5回
              drawContext.DrawImage(maskImage, rect);
              drawContext.Pop();
            }
            // 画面に表示するビットマップに描画
            drawContext.Close();
            bmpBuffer.Render(drawVisual);

      この部分で// 変更 第5回となっているあたりを参考にしていただければ、サイズの変更は可能だと思います。
      2012-08-23 11:31
  • OutOfMemoryの解決方法 mkama 2012-08-11 19:30 ジグソー運営事務局にお願いして、2週間遅れでレビュー書いています。

    入院2週間目で、やっと抜糸(抜鉤25本)しました。
    kinect + ディスクトップを病室に持ち込んで、やっとレビュー開始しました。

    連載 第5回目レビューでOutOfMemoryが発生して困っていましたが、解決方法がわかりました。

    ガーベージコレクションを強制するとエラーが発生しません。

    レビューアーの方で他の方法で解決された方、いらっしゃいましたか?

    private void fillBitmap()の最後に追加します。

    // 画面に表示するビットマップに描画
    drawConText.Close();
    bmpBuffer.Render(drawVisual);

    GC.Collect(); //★これを追加
    1件のコメント
    • kazgb 自分もこの1行でOutOfMemoryの発生が無くなりました。
      他の方法は良く分かっていないので試せていないのですが・・・
      2012-08-12 10:54

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

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