Kinectを使ってみよう

本チュートリアルの内容

開発環境の確認
■ 前提とする環境
このチュートリアルでは、以下の環境を想定します。

注意:開発にはVisual Studio2010を使います。下位のVisual Studioではうまく動きませんでした。

■ Kinectのドライバーのインストール
Kinectのドライバーは、Kinect for Windows SDK Beta2を使用します。
インストールは、上記のリンクから自分の環境にあったプログラムをダウンロードして、インストールします。この時に、インストールが終わるまで、 KinectのUSBをさしてはいけません。Googleで検索するとインストールの方法が書かれているページがたくさん出てきますので、参考にしながら 実施してください。

ページの先頭へ

サンプルプログラムを動かしてみる
まずは、SDKに入っているサンプルプログラムを動かしてみましょう。
スタートから、Microsoft Kinect 1.0 Beta2 SDK>Sample Skeletal Viewerを選びましょう。



以下のような画面が立ち上がってきます。実際に、Kinectの前に立ってみましょう。驚くほど正確にスケルトンが表示さると思います。(一人での作業では、残念ながら、スケルトンの画面をキャプチャできませんでした)



では、以降、このサンプルに書かれているコードを参考に、深度センサーを使って、カメラと人の距離を測ってみましょう。

深度センサーで距離を測る
■ 深度センサー
Kinectの深度センサーでは、850o〜4000mmまでの距離を測定できるようです。また、人数は、同時に7人認識できるようです。ということで、7人までの距離を表示するフォームアプリケーションを作ってみましょう。

まず、Visual Studio2010で、C#のフォームアプリケーションを立ち上げます。ここでは、KinectDepth01という名前にしました。



そして、Kinect SDKを参照設定に設定しておきます。参照設定から、NETを選択し、その一覧から、Microsoft.Research.Kinectを選んでOKを押します。


画面には、7人分の距離を表示するTextBoxを配置しておきます。


では、実際にコードを追加していきます。まず、「表示」>「コード」を選択して、コードを表示します。そして、ディレクティブにKinectのNuiを追加します。

using Microsoft.Research.Kinect.Nui;

そして、フォームのクラスにRuntimeの変数として、nuiを、byte[]の変数として、depthFrame32を以下のように追加します。これらは、サンプルプログラムと同じです。

public partial class Form1 : Form
{
  Runtime nui;
  byte[] depthFrame32 = new byte[320 * 240 * 4];
  public Form1()
  {
     InitializeComponent();
  }
}

次に、フォーム実行時に行う処理であるForm_Loadとして、次の処理を追加しておきます。Form_Loadはデザイン画面で、フォームを選択し、プロパティのイベントマーク(カミナリのマーク)を選択し、その中のLoadをダブルクリックして追加します。

private void Form1_Load(object sender, EventArgs e)
{
    nui = new Runtime();
    try
    {
      nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex);
    }
    catch (InvalidOperationException)
    {
      MessageBox.Show("Runtime initialization failed!");
      return;
    }

    try
    {
      nui.DepthStream.Open(ImageStreamType.Depth, 2, ImageResolution.Resolution320x240, ImageType.DepthAndPlayerIndex);

    }
    catch (InvalidOperationException)
    {
      MessageBox.Show("Fail to open stream.");
      return;
    }

    nui.DepthFrameReady += new EventHandler<ImageFrameReadyEventArgs>(nui_DepthFrameReady);
}

ここでは、まだ、nui_DepthFrameReadyを定義していませんので、これも、サンプルプログラムを参考に追加しておきます。

void nui_DepthFrameReady(object sender, ImageFrameReadyEventArgs e)
{
    Bitmap bmp;

    PlanarImage Image = e.ImageFrame.Image;
    int[] personDepth = personDepthFrame(Image.Bits);

    textBox1.Text = personDepth[1].ToString();
    textBox2.Text = personDepth[2].ToString();
    textBox3.Text = personDepth[3].ToString();
    textBox4.Text = personDepth[4].ToString();
    textBox5.Text = personDepth[5].ToString();
    textBox6.Text = personDepth[6].ToString();
    textBox7.Text = personDepth[7].ToString();
}

さらに、この中で使われているpersonDepthFrameも、サンプルにあるconvertedDepthFrameを変更して、同じように定義します。ここでは、各画素ごとに、Kinectが何人目で認識したか(player)によって、場合分けをして、それぞれの画素の距離を積算し、最後に画素の個数で割ることで、その人の平均距離をもとめるという手法をとります。
depthFrame16というbyte配列に、2バイトごとにkinectがキャプチャした画面のデータが入っているのですが、その値最初の3ビットに何人目で認識したかのデータが入っているので、以下のようにすると何人目で認識したかの値を取ることができます。

int player = depthFrame16[i16] & 0x07;

また、距離のデータは残りのビットに割当てられているので、最初のバイトを3ビットシフトして、それに、2つ目のバイトを左に5ビットシフトしたものの和を取ることで、距離の値になりますので、以下のようにして、その時の距離を求めます。

int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);

int[] personDepthFrame(byte[] depthFrame16)
{
    int[] cntDepth = new int[8];
    int[] aveDepth = new int[8];
    for (int i = 0; i < 8; i++)
    {
      aveDepth[i] = 0;
      cntDepth[i] = 0;
    }
    for (int i16 = 0, i32 = 0; i16 < depthFrame16.Length && i32 < depthFrame32.Length; i16 += 2, i32 += 4)
    {
      int player = depthFrame16[i16] & 0x07;
      int realDepth = (depthFrame16[i16 + 1] << 5) | (depthFrame16[i16] >> 3);

    // 認識した人ごとに距離を計算する
    switch (player)
      {
        case 0:
        aveDepth[0] = aveDepth[0] + realDepth;
        cntDepth[0] = cntDepth[0] + 1;
        break;
        case 1:
        aveDepth[1] = aveDepth[1] + realDepth;
        cntDepth[1] = cntDepth[1] + 1;
        break;
        case 2:
        aveDepth[2] = aveDepth[2] + realDepth;
        cntDepth[2] = cntDepth[2] + 1;
        break;
        case 3:
        aveDepth[3] = aveDepth[3] + realDepth;
        cntDepth[3] = cntDepth[3] + 1;
        break;
        case 4:
        aveDepth[4] = aveDepth[4] + realDepth;
        cntDepth[4] = cntDepth[4] + 1;
        break;
        case 5:
        aveDepth[5] = aveDepth[5] + realDepth;
        cntDepth[5] = cntDepth[5] + 1;
        break;
        case 6:
        aveDepth[6] = aveDepth[6] + realDepth;
        cntDepth[6] = cntDepth[6] + 1;
        break;
        case 7:
        aveDepth[7] = aveDepth[7] + realDepth;
        cntDepth[7] = cntDepth[7] + 1;
        break;
      }
    }
   // 認識した人の画素の数で割ることで、その人の平均距離を求める
    for (int i = 0; i < 8; i++)
    {
      if (cntDepth[i] != 0) aveDepth[i] = aveDepth[i] / cntDepth[i];
      else aveDepth[i] = 0;
    }
    return aveDepth;
}

nui_DepthFrameReadyでは、戻り値として得られた配列の値を、フォーム画面のTextboxに文字列として書き出して、それを画面に表示しています。

最後に、また、フォーム画面に戻って、フォームを閉じたときの終了処理を追加しておきます。追加の仕方は、先ほどと同様に、イベント処理の中から、Form_closingを選んで、ダブルクリックし、以下のようなプログラムを追加します。

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    nui.Uninitialize();
    Environment.Exit(0);
}

以上で終了です。実際にプログラムを起動してみましょう。



上の図のようにKinectの前に立つと、装置との距離がmm単位で表示されると思います。ただ、何人目として認識されるかはわからないので、どこに表示されるかはわかりません。この辺りは、プログラムでのさらなる工夫が必要になるでしょう。

(コード)

研究課題

以上のように、Kinectの深度センサーを使うことで、距離を求めることができます。そこで、次のようなものを考えてみましょう。

■ 距離に応じて動くアプリケーション
人が近付くと動いて、遠ざかると止まるようなアプリケーション

■ Kinectの前の人の動き
時々刻々と変化する値から、Kinectの前で人がどのような動きをしたのかを判定するアプリケーション

(2012年3月18日作成)