やってみよう!Kinectアプリ開発 - 第4回 骨格情報の利用

2012-06-22 17:18

アプリケーションの修正

それでは、Visual Studio®を使い、アプリケーションの修正を行いましょう。今回の修正は以下の手順で行います。

  1. プロジェクトを開く
  2. プロジェクトへの画像ファイルの追加
  3. コードビハインド(C#によるロジック)の修正
    1. フィールドの追加
    2. 初期化処理("Loaded"イベントハンドラ)の修正
    3. Kinectイベントハンドラの修正
    4. 骨格情報取得処理の追加
    5. 描画処理の追加

プロジェクトを開く

既存のプロジェクトで作業するには、以下のいずれかの方法でプロジェクトを開きます。

  1. Explorerでプロジェクトを保存したフォルダを開き、"KinectCameraSample.sln"をダブルクリックする。
  2. Windowsのスタートメニューから、[すべてのプログラム]-[Microsoft Visual Studio 2010 Express]-[Microsoft Visual C# 2010 Express]を選び、Visual C#®を起動し、以下のいずれかを行います。
    1. メニューから[ファイル(F)]-[最近使ったプロジェクトとソリューション(J)]-[<前回作成したプロジェクトフォルダのslnファイル>]を選択する。
    2. メニューから[ファイル(F)]-[プロジェクトを開く(P)...]を選択し、[プロジェクトを開く]ダイアログで、前回作成したプロジェクトフォルダのslnファイルを選択し、[開く(O)]をクリックする。

プロジェクトへの画像ファイルの追加

アプリケーションからマスク画像を扱うために、今回はexeファイルにマスク画像のファイルを埋め込みます。以下その手順を示します。

  1. 画像ファイルを格納するフォルダを作成します。
    [ソリューションエクスプローラー]で、プロジェクト名(ここでは"KinectCameraSample")のアイコンを右クリックし[追加(D)]-[新しいフォルダ(D)]を選択します。

    ※ [ソリューションエクスプローラー]が表示されていない場合は、メニューから[表示(V)]-[その他のウィンドウ(E)]-[ソリューションエクスプローラー(P)]を選択します。
  2. プロジェクト直下に新しいフォルダが作られるので、名前を"images"と入力してエンターを押します。
  3. Explorerで追加する画像ファイル(ここでは"tanakazu.png")の置かれたフォルダを開き、画像ファイルをドラッグし作成した"images"フォルダにドロップします。
    あるいは、画像ファイルをコピーし、"images"フォルダを右クリックして[貼り付け(P)]を選択します。
  4. 追加された画像ファイルを右クリックして[プロパティ(R)]を選択します。
  5. [プロパティ]ビューで、[ビルドアクション]が"Resource"、[出力ファイルにコピー]が"コピーしない"になっていることを確認します。
    この設定で、ビルド時に画像ファイルがexeファイルに埋め込まれるようになります。

コードビハインドの修正

  • フィールドの追加
    前回MainWindowクラスの宣言の直下に追加したフィールドを以下のように追加・修正します。
    ※ bmpBufferをRenderTargetBitmapに変更しているので注意してください。
    追加・修正したものの役割に関しては後述します。
  • "Loaded"イベントハンドラの修正
    "Loaded"イベントハンドラで行っていた初期化処理には以下を追加します。

    • マスク画像の読み込み: プロジェクトに画像ファイルの追加の手順でexeファイルに埋め込んだ画像ファイルを読み込み、ビットマップオブジェクト(maskImage)を作成する処理です。"pack://application,,,/"の後ろがプロジェクト内でのパスになります。
    • 骨格ストリームの有効化: カラーストリームの有効化と同様に、SkeletonStreamのEnableメソッドを呼び出すことで有効化します。
    • バッファの初期化: SkeletonStreamからの骨格情報のバッファ(skeletonBuffer)を追加し、またImageコントロールにセットするビットマップオブジェクト(bmpBuffer)は、マスクの上書きなど複雑な描画を行うため、RenderTargetBitmapに変更しています。
    • 前回はColorImageReadyイベントのイベントハンドラを登録していましたが、今回はSkeletonStreamのイベント(SkeletonFrameReady)も処理する必要があるため、これらをまとめて扱えるAllFramesReadyイベントのハンドラに変更しています。
  • Kinectイベントハンドラの修正
    AllFrameReadyメソッドは、全てのフレーム(ここではImageStreamとSkeletonStream)が準備できた時に呼び出されます。
    • SkeletonFrameから骨格情報を取得し、Kinectセンサーの範囲内の人の頭部を認識し、その位置を取得します(処理はgetHeadPointsメソッドで行います)。
    • ImageFrameから背景画像を取得し、その上に頭部の位置情報から、人の顔の位置にマスク画像を上書きし、それをImageコントロールにセットしたビットマップオブジェクト(bmpBuffer)に描画します(処理はfillBitmapメソッドで行います)。
  • 骨格情報取得処理の追加

    • CopySkeletonDataToで骨格情報をバッファにコピーします。
    • Skeletonオブジェクトは1人分の骨格情報を表します。従ってバッファ(Skeletonの配列)には複数人の骨格情報が入りますので、foreachループで1人分ずつ処理します。
    • TrackingStateを調べ、Tracked状態以外の場合は飛ばします。
      (Tracked状態は関節の情報まで取得できていることを示します。)
    • skeleton.Jointsに各関節の情報が入っていますので、そこから頭部(JointType.Head)の関節を取得します。(ここではJointTypeを直接指定して取得していますが、skeleton.Jointsをforeachに渡して各関節を処理するパターンもあります)
    • 頭部の関節情報のTrackingStateを調べ、TrackedまたはInferred状態でない場合は飛ばします。
      (Tracked状態は関節の位置まで取得できていることを示し、Inferredは推測した関節の位置だけ取得できていることを示します。)
    • Positionプロパティから頭部の位置を取得し、リストに保存します。
    • Kinectが認識しているすべての人を処理し終わったら(foreachループを抜けたら)頭部の位置が保存されたリストを返します。
  • 描画処理の追加

    • DrawingVisualのRenderOpenメソッドでDrawingContextオブジェクトを作成します。
    • 画像情報をバッファにコピーし、それを元にWritePixedlsメソッドでビットマップオブジェクト(ここではbgImg)に書き込みます。この処理は前回とほぼ同様なので問題ないでしょう。
    • DrawingContextのDrawImageメソッドで背景画像のビットマップの描画を指定します。
    • 各頭部の位置情報をforループで処理します。
    • MapSkeletonPointToColorメソッドで、骨格の座標から、画像情報の座標に変換します。
    • DrawingContextに、背景画像上の頭部位置にマスク画像の描画を指定します。ここではマスク画像を128x128の大きさで描画しています。もし使用するマスク画像が正方形でない場合は、適宜修正してください。また、頭の位置として取得した座標は顔の中心ですが、描画に指定する座標は画像の左上の点なので、それらが一致するように座標からマスク画像の描画サイズの半分を引いています(この場合は、それぞれ-64しています)。
    • 以上で描画処理は終了なので、DrawingContextのCloseメソッドを呼び出して確定させ、ビットマップのRenderメソッドで実際に描画します。
    ※ なお、ここではC#特有の構文を一個使っています。”var bgImg = new WriteableBitmap(...);”などの”var”を使った構文です。ローカル変数の宣言時に型として”var”を指定するとコンパイラが適切な型を推論してくれますので、”WriteableBitmap”のような長い型名のときに利用するとよいでしょう(ただし、あまり使いすぎると型が分からなくなり可読性が下がる可能性もありますので注意が必要です)。

実行

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

なぜか若干怖い気もしますが、正しく顔の位置にマスク画像を上書きできました。みなさんのアプリケーションではいかがでしょうか?

 

4件の質問スレッド
  • バージョンアップで? kazgb 2012-10-31 22:05 SDKがバージョンアップされて、
    警告みたいなのがでていたので気になりました。

    >= kinect.MapSkeletonPointToColor(headPos, rgbFormat);

    大雑把でアレですが上の部分。。。
    一応動くみたい?ですが、[使用しないでください]と記述されていました。

    調べて、以下に書き換えました。

    = kinect.CoordinateMapper.MapSkeletonPointToColorPoint(headPos, rgbFormat);

    動いているので問題なさそうですが、、、大丈夫・・・ですよね? うん。
  • 解説のDrawingVisualについて claw 2012-08-09 17:03 はじめまして。
    現在、大学でプログラミングを学びはじめた者です。

    非常に細かいことで申し訳ないのですが、
    2ページ目、fillBitmapメソッドの解説の部分、

    > DrawingVisualのRenderOpenメソッドでDrawingContextオブジェクトを作成します。

    となっていますが、
    DrawingVisualのRenderOpenメソッドでDrawContextオブジェクトを作成します。
    の間違い(?)ではないでしょうか?

    読んでいて少し混乱してしまったので……
    1件のコメント
    • kinection.jp管理人 clawさん

      ご質問を理解できていないかも知れませんが、RenderOpenメソッドでDrawingContextクラスのオブジェクトを作成しているので、意図通りです(DrawContextというクラスはありません)。
      もし、用語として「<クラス名>オブジェクト」ではなく「<変数名>オブジェクト」とすべき、という意味であれば、例えばgoogleで"DrawingContext-object"で検索すれば普通に使われているのがわかると思います。
      2012-08-10 10:00
  • 保存について 柏木 2012-07-17 10:06 初めまして。
    初心者なのですが、取得した骨格情報というのはテキストなどに保存可能でしょうか?
    全20ポイントのスケルトンのx,y,z座標を30フレームで取得し続け、10分程度のデータを保存することは可能でしょうか?
    続いて、取得したRBG画像を動画として保存することは可能でしょうか?
    環境は
    VisualStudio2010でc#
    Kinect for Windows v.1.5
    Windows7
    です。

    よろしくお願いいたします。
    2件のコメント
    • 柏木 コメントありがとうございます!えと・・・そうなると、たとえば
      (1)
      void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
      {
      try
      {
      using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
      {
      if (skeletonFrame != null)
      {
      DrawSkeleton(skeletonFrame);
      }
      }
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.Message);
      }
      }
      private void DrawSkeleton(SkeletonFrame skeletonFrame)
      {
      // スケルトンのデータを取得する
      Skeleton[] skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
      skeletonFrame.CopySkeletonDataTo(skeletons);

      SkeletonCanvas.Children.Clear();

      // スケルトンのジョイントを一つ一つ取得
      foreach (Skeleton skeleton in skeletons)
      {
      // スケルトンがトラッキング状態の場合は、ジョイントを取得
      if (skeleton.TrackingState == SkeletonTrackingState.Tracked)
      {
      // ジョイントを保存する
      foreach (Joint joint in skeleton.Joints)
      {
      // ジョイントがトラッキングされていなければ次へ
      if (joint.TrackingState == JointTrackingState.NotTracked)
      {
      continue;
      }

      // ジョイントの座標を保存する
      JointRecord(joint.Position);
      }
      }
      }
      }
      とでもして、JointRecord部分で・・・どう作ればいいのかわからなくなってしまっております。Console.WriteLineで保存可能なのでしょうか?

      (2)
      void kinect_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e)
      {
      try
      {
      // RGBカメラのフレームデータを取得する
      using (ColorImageFrame colorFrame = e.OpenColorImageFrame())
      {
      if (colorFrame != null)
      {
      // RGBカメラのピクセルデータを取得する
      byte[] colorPixel = new byte[colorFrame.PixelDataLength];
      colorFrame.CopyPixelDataTo(colorPixel);

      // ピクセルデータをビットマップに変換する
      RGBCameraImage.Source = BitmapSource.Create(colorFrame.Width, colorFrame.Height, 96, 96,
      PixelFormats.Bgr32, null, colorPixel, colorFrame.Width * colorFrame.BytesPerPixel);
      }
      }
      }
      catch (Exception ex)
      {
      MessageBox.Show(ex.Message);
      }
      }
      たとえばこの状態だとして、GridのImageを動画で保存するにはどのようにすればいいのでしょうか?

      色々検索はしてみたのですが、画像保存の場合くらいしか見つからず、よく分かりませんで。

      どうぞよろしくお願いいたします。
      2012-07-20 10:40
    • 初音玲 (1)
      保存先の書き込みスピードにもよりますがテキストデータとしての保存は可能です。

      (2)
      取得したRGB画像を動画として保存することは可能です。
      2012-07-19 02:19
  • バグ修正 kinection.jp管理人 2012-07-03 09:38 かおるんさんご指摘の画像が反転するバグの修正です。

    Matrix rot = new Matrix(-headMtrx.M11, headMtrx.M12,
    -headMtrx.M21, headMtrx.M22,
    headPt.X, headPt.Y);

    M11 と M12 のところの符号を反転させています。

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

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