特集:Kinectセンサーの可能性

C#開発者が“Kinectハック”に挑戦してみた

デジタルアドバンテージ 一色 政彦
2011/05/16
Page1 Page2 Page3

■Kinectハックのサンプルを動かす

●OpenNIフレームワークに標準搭載されているサンプル

 まずはOpenNIサンプルを動かしてみよう。

 次の画面のように「C:\Program Files\OpenNI\Samples\Bin\Release」フォルダ内にはOpenNIのサンプルが多数用意されている。

OpenNIに標準搭載されているサンプル群(Windowsエクスプローラ)

 任意の.exeファイルを実行すればよいが、ここでは「NiSimpleViewer」(=NiSimpleViewer.exeファイル)を実行してみよう。

 NiSimpleViewerは、シーンに対する奥行き画像と写真画像を描画する、小型のOpenGLアプリケーションだ。[1]/[2]/[3]キーを押すと、「オーバーレイ・モード(写真画像の上に奥行き画像を重ねる)」/「奥行き画像モード」/「写真画像モード」にモードが切り替わる。[Esc]キーを押すと、アプリケーションを終了できる。

 次の画面は、奥行き画像モードで表示したNiSimpleViewerの例である。

奥行き画像モードで表示したNiSimpleViewerの例

 同フォルダには、ほかにもさまざまなサンプルが用意されているので、試してみるとよい。各サンプルの説明(英語)は、「C:\Program Files\OpenNI\Documentation」フォルダ内にある「OpenNI.chm」ファイルを実行し、表示されるヘルプの左側にあるツリーから[OpenNI Document]−[Samples]で参照できる(一部のサンプルについては説明がない)。

 また、同フォルダ内にある「SimpleViewer.net.exe」「SimpleRead.net.exe」「UserTracker.net.exe」などのファイルは、.NET言語のC#で作られたサンプルである(ほかのサンプルはすべてC++で作成されている)。次の画面は、「UserTracker.net.exe」ファイルを実行した例で、骨格情報が表示されている(なお、骨格を表示するには、この画像のように両腕を直角に曲げるポーズを取る必要がある)。

骨格情報が表示されているUserTracker.net(C#のサンプル)の例

●NITEミドルウェアに標準搭載されているサンプル

 以上のOpenNIフレームワークのサンプルのほか、「C:\Program Files\PrimeSense\NITE\Samples\Bin\Release」フォルダ内にあるNITEミドルウェアのサンプルも試せる。

●Kinectで操作できる3Dキャラクタのサンプル

 自分の体の動きに合わせて、3DキャラクタをKinectにより操作できるサンプルも提供される。このサンプルは、下記のリンク先よりダウンロードできる。

 次の画面は、SampleAppSinbadを実行して、3Dキャラを動かしているところだ。動かしているアクティブ・プレイヤーは右下に小さく表示される。

Kinectで操作できる3Dキャラクタのサンプル

■C#+OpenNIフレームワークでKinectハックを試す

 それでは、実際にKinectハックを試していこう。ここでは、奥行き画像を表示するWPFアプリケーションを作成する。

 奥行き画像を表示するWindowsフォーム・アプリケーションのサンプルが、「C:\Program Files\OpenNI\Samples\SimpleViewer.net」フォルダに配置されているので、内部の実装はこれを大幅に参考にする。

●WPFアプリケーションのプロジェクトの作成

 それではまずは、WPFアプリケーションのプロジェクトを作成する。

 Visual Studio 2010のメニューバーから[ファイル]−[新規作成]−[プロジェクト]を実行し、そこで表示される[新しいプロジェクト]ダイアログで、左側のツリー表示から[Visual C#]−[Windows]を選択し、右側のテンプレート一覧から「WPF アプリケーション」テンプレートを選択して、「HelloKinect」という名前でプロジェクトを作成する。

●OpenNIフレームワークを活用するためのアセンブリ参照

 「C:\Program Files\OpenNI\Bin」フォルダ内にある「OpenNI.Net.dll」アセンブリを参照に追加する。

●WPFへのコントロールの配置

 WPF上には、奥行き画像を表示するコントロールが必要だ。これに最適なのはImageコントロールなので、メイン画面(=MainWindow.xamlファイル)をWPFデザイナで開き、そのデザイン・サーフェイス上に[ツールボックス]から「Image」をドラッグする。配置されたImageコントロールの右クリック・メニューから[レイアウトのリセット]−[すべて]を実行して、ウィンドウいっぱいにImageコントロールを広げる。

●Kinectから画像を取得して描画

 Kinectから画像データを取得してビットマップ・イメージに変換した後、Imageコントロールのデータソースに指定する。詳細なコードの解説は割愛する(コメントを参考にしてほしい)。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using OpenNI;


namespace HelloKinect
{
  public partial class MainWindow : Window
  {
    private readonly string SAMPLE_XML_FILE =
      @"C:/Program Files/OpenNI/Data/SamplesConfig.xml";

    private Context context;       // コンテキスト
    private DepthGenerator depth;  // 奥行きマップ生成ノード
    private bool shouldRun;        // 実行中かのフラグ
    private DispatcherTimer dispatcherTimer;   // 描画更新用
    private Bitmap bitmap;    // 奥行きマップのビットマップ画像
    private int[] histogram;  // ヒストグラム用の配列

    public MainWindow()
    {
      InitializeComponent();

      // サンプル用の構成ファイルからコンテキストを作成
      this.context = new Context(SAMPLE_XML_FILE);
      // 奥行き画像生成(DepthGenerator)ノードを作成
      this.depth =
        context.FindExistingNode(NodeType.Depth) as DepthGenerator;
      if (this.depth == null)
      {
        throw new Exception("Viewer must have a depth node!");
      }

      // ヒストグラム用の配列を作成
      this.histogram = new int[this.depth.DeviceMaxDepth];

      // 奥行きマップのビットマップ画像のオブジェクトを準備
      MapOutputMode mapMode = this.depth.MapOutputMode;
      this.bitmap = new Bitmap(
        (int)mapMode.XRes, (int)mapMode.YRes,
        System.Drawing.Imaging.PixelFormat.Format24bppRgb);

      // 実行中かのフラグをオン
      this.shouldRun = true;

      // 10msごとに描画更新するためのタイマーを開始
      dispatcherTimer = new DispatcherTimer();
      dispatcherTimer.Tick += new EventHandler(ReaderDepthPicture);
      dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 10);
      dispatcherTimer.Start();
    }

    private void Window_Closing(object sender, CancelEventArgs e)
    {
      // 実行中かのフラグをオフ
      this.shouldRun = false;

      // 描画更新用のタイマーを停止
      dispatcherTimer.Stop();
    }

    private void Window_KeyDown(object sender, KeyEventArgs e)
    {
      // [Esc]キーでアプリケーションを終了
      if (e.Key == Key.Escape)
      {
        this.Close();
      }
    }

    // 10msごとに呼び出される描画更新用のメソッド
    private unsafe void ReaderDepthPicture(object sender, EventArgs e)
    {
      if (this.shouldRun == false)
      {
        return;
      }

      // 奥行きマップ生成ノードで新規データ利用可能になるのを待つ
      try
      {
        shouldRun = false;
        this.context.WaitOneUpdateAll(this.depth);
        shouldRun = true;
      }
      catch (Exception)
      {
      }

      // 奥行きマップの属性情報をまとめたメタデータを取得
      DepthMetaData depthMD = new DepthMetaData();
      this.depth.GetMetaData(depthMD);

      // 奥行きマップのヒストグラムを計算
      CalcHist(depthMD);

      // 奥行きマップのビットマップ画像を作成
      lock (this)
      {
        Rectangle rect = new Rectangle(
          0, 0, this.bitmap.Width, this.bitmap.Height);
        BitmapData data = this.bitmap.LockBits(rect,
          ImageLockMode.WriteOnly,
          System.Drawing.Imaging.PixelFormat.Format24bppRgb);

        ushort* pDepth =
          (ushort*)this.depth.DepthMapPtr.ToPointer();

        // ビットマップ画像のピクセルを設定
        for (int y = 0; y < depthMD.YRes; ++y)
        {
          byte* pDest =
            (byte*)data.Scan0.ToPointer() + y * data.Stride;
          for (int x = 0; x < depthMD.XRes;
                                        ++x, ++pDepth, pDest += 3)
          {
            byte pixel = (byte)this.histogram[*pDepth];
            pDest[0] = 0;
            pDest[1] = pixel;
            pDest[2] = pixel;
          }
        }

        this.bitmap.UnlockBits(data);
      }

      // Imageコントロールのソースにビットマップ画像を指定
      MemoryStream ms = new MemoryStream();
      bitmap.Save(ms, ImageFormat.Png);
      ms.Position = 0;
      BitmapImage bi = new BitmapImage();
      bi.BeginInit();
      ms.Seek(0, SeekOrigin.Begin);
      bi.StreamSource = ms;
      bi.EndInit();
      this.image1.Source = bi;
    }

    // 奥行きマップのヒストグラムを計算するメソッド
    private unsafe void CalcHist(DepthMetaData depthMD)
    {
      // ヒストグラム用配列を初期化
      for (int i = 0; i < this.histogram.Length; ++i)
        this.histogram[i] = 0;

      ushort* pDepth = (ushort*)depthMD.DepthMapPtr.ToPointer();

      int points = 0;
      for (int y = 0; y < depthMD.YRes; ++y)
      {
        for (int x = 0; x < depthMD.XRes; ++x, ++pDepth)
        {
          ushort depthVal = *pDepth;
          if (depthVal != 0)
          {
            this.histogram[depthVal]++;
            points++;
          }
        }
      }

      for (int i = 1; i < this.histogram.Length; i++)
      {
        this.histogram[i] += this.histogram[i - 1];
      }

      if (points > 0)
      {
        for (int i = 1; i < this.histogram.Length; i++)
        {
          this.histogram[i] = (int)(256 * (1.0f - (this.histogram[i] / (float)points)));
        }
      }
    }
  }
}
Kinectから画像を取得して描画するサンプル・コード(MainWindow.xaml.cs)

 以上で開発は完了だ。

 このアプリケーションを実行すると、OpenNIの「SimpleViewer.net」のように奥行き画像が表示される(次の画面はその例である)。

自作したKinectハックのサンプルを実行した例

 もちろん、これはまだ画像が表示できただけという何の意味もないものだが、ここから面白いアプリケーションに改めて挑戦していくことができるだろう。何よりもKinectハック・プログラミングは面白い。

 以上、「Kinectハックは面白い」と感じていただけただろうか?

 .NET開発者であるならば、Kinectハック(もしくはKinectプログラミング)を行わないのは、宝の持ち腐れだ。しかも「ダイアモンド」クラスの宝なのに、タンスの中に一生しまい込んでいるようなものである。できるだけ多くの日本の.NET開発者で積極的に未来のIT技術を開発し、非マイクロソフト系の技術者や世界を驚かせていこうではないか。

 ここで筆者が気に入っている名言をご紹介しよう。

「アップルで働くまで、イノベーションというのは『今にない、新しいものを作ること』だと思っていた。でもそれは違って、イノベーションというのは『未来にある普通のものを作ること』なのです。この違いを理解できるまでかなり時間がかかった。」(@hu uesugi氏のTwitterでのつぶやきより引用)

 いま.NET開発者は、「未来のIT技術」という視点で、「最も有利な位置にいる」といえる。.NET開発者にはぜひ、「未来にある普通のものを作ること」に、いまから励んでほしい。もちろん、非マイクロソフト系の技術者がいまから.NET開発者に転向するのも大歓迎である。 end of article

 

 INDEX
  特集:Kinectセンサーの可能性
  C#開発者が“Kinectハック”に挑戦してみた
    1.Kinectセンサーの概要
    2.Kinectハックの可能性/KinectハックのためのOpenNI環境の準備
  3.Kinectハックのサンプルを動かす/C#+OpenNIフレームワークでKinectハックを試す


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間