C#のプログラムでKinectセンサーを制御しよう! SDKの基本的な使い方を、サンプル・プログラムを通して理解する。
2回目となる今回は、前回に続き、C#で記述したプログラムでKinectセンサー(以下、単に「Kinect」)を制御することを目標に、Kinect for Windows SDK(ベータ版)について見ていく。
前回は、Kinect for Windows SDK(ベータ版)の特徴やインストールされたプログラム・ファイルなどについて紹介した。今回は、インストールされたC#版サンプル・プログラムについて解説し、Kinect for Windows SDK(ベータ版)の基本的な使い方を理解する。主な内容は、NUI(ナチュラル・ユーザー・インターフェイス)のカメラ・イメージの取得、深度情報の取得、骨格情報の取得である。ちなみに次回は、Audioの音源位置の推定と音声認識について説明する予定だ。
ここでは、Skeletal Viewer(C#版)をひもときながら、Kinect for Windows SDK(ベータ版)のNUIについて、理解を深めていく。
Skeletal Viewerは、カメラ・イメージ、深度センサーのイメージ、骨格情報、ならびに1秒間の描画回数であるFPS値を表示するサンプル・プログラムである。
まず、Skeletal Viewerのフォルダ構成を見てみたい。サンプル・プログラムは前回の記事で紹介したが、「C:\Users\Public\Documents\Microsoft Research KinectSDK Samples\」にある。Skeletal Viewerは、その中の「NUI」フォルダの中にある「SkeletalViewer」フォルダである。
このSkeletalViewerは、C++版とC#版の2種類があるが、C#版で解説する。C#版のフォルダとファイル構成は以下のとおりだ。
C:\Users\Public\Documents\Microsoft Research KinectSDK Samples\NUI\SkeletalViewer
└─ CS
│ app.config
│ app.xaml
│ app.xaml.cs
│ MainWindow.xaml
│ MainWindow.xaml.cs
│ ReadMe.txt
│ SkeletalViewer.csproj
│ SkeletalViewer.ico
│ SkeletalViewer.sln
│
└─ Properties
AssemblyInfo.cs
このSkeletalViewer(C#版)の中では、下記の3つのファイルが重要である。
「XAML」というキーワードが出ていることから分かるように、このプロジェクトは、WPF(Windows Presentation Foundation)で構成されている。サンプル・プログラムを理解するためには、WPFの知識が幾分必要である。WPFについて知りたい方は、「連載:WPF入門」や「WPF/Silverlight UIフレームワーク入門」を参照することをお勧めする。
なお、Kinect for Windows SDK(ベータ版)は、WPFが必須というわけではない。本解説を参考にWindowsフォームなど、ほかのUI技術で開発することも可能である。
では、実際にソリューション・ファイルを開いてみる(次の画面を参照)。
●アセンブリの参照
まず、参照設定を確認してみよう。
次の画面のように、この参照設定に、Kinect for Windows SDK(ベータ版)用に追加されているアセンブリ「Microsoft.Research.Kinect」がある。
今回は、サンプル・プログラムを開いたため、最初から登録されているが、新しくプロジェクトを始める場合は、[ソリューション エクスプローラー]の[参照設定]項目の右クリック・メニューから[追加]を実行して、これと同様の参照を追加しなければならない。
なお、Microsoft.Research.KinectアセンブリはKinect for Windows SDK(ベータ版)をインストールした際に、GAC(グローバル・アセンブリ・キャッシュ)に登録されているので、次の画面のように、[参照の追加]ダイアログの[.NET]タブに出てくる。
●XAMLデザイン
次に、MainWindow.xamlファイルの[デザイン]ビューについて見てみる(次の画面を参照)。
(1)深度センサーの情報を表示する領域
この領域は、以下のような宣言されたImageコントロール(以下、「“depth”コントロール」と表記)が配置されている。
<Image Width="400" Height="300" Name="depth" …… />
(2)骨格情報を表示する領域
この領域は、以下のような宣言されたCanvasコントロール(以下、「“skeleton”コントロール」と表記)が配置されている。
<Canvas Width="400" Height="300" Name="skeleton" …… />
(3)1秒間の描画回数であるFPSを表示する領域
この領域は、以下のような宣言されたTextBoxコントロール(以下、「“frameRate”コントロール」と表記)が配置されている。
<TextBox FontSize="72" Name="frameRate" Text="0 fps" …… />
(4)カメラ・イメージを表示する領域
この領域は、以下のような宣言されたImageコントロール(以下、「“video”コントロール」と表記)が配置されている。
<Image Width="400" Height="300" Name="video" …… />
●サンプル・コードについて
今回のサンプル・プログラムは、下記の5点を実現するコードを含んでいる。
(1) Kinectデバイスの利用を開始する
(2) Kinectのカメラ・センサーからカメラ・イメージを取得し、“video”コントロールにイメージをバインドする
(3) Kinectの深度センサーから深度情報を取得し、“depth”コントロールに深度情報をバインドする
(4) 深度情報の更新間隔から、1秒間の描画回数を計算し、fps値を描画する
(5) 骨格情報を取得し、“skeleton”コントロールに骨格情報をバインドし、骨格を描画する
実際のサンプルのコードを基に、上記の5点の手順について確認していく。
○名前空間
以下の説明で登場するKinect関連のクラスの名前空間は(基本的に)「Microsoft.Research.Kinect.Nui」である。今回のサンプル・プログラムは、型修飾を軽減するためにusingキーワードを用いている(MainWindow.xaml.csファイルの25行目付近)。
* わたしの感覚では、名前空間やアセンブリ名に「Research」といった名前が付いているのは違和感がある。もちろんベータ版であるために、将来名前が変わるであろうということは十分に考えられる。また、この後も出てくるが、クラス名に「Runtime」というような一般的な名前が利用されているというのも、違和感がある。
○(1)Kinectデバイスの利用を開始
Kinectのデバイスを利用するためには、Runtimeクラスのオブジェクトを準備することから始める。
Runtime nui = new Runtime();
次に、Runtimeオブジェクトを用いてKinectデバイスを初期化し、「どのセンサーからのデータを使用するか」を指定する。具体的には、以下のオプションを1つまたは複数指定する。
サンプル・プログラムでは、「深度情報とプレイヤー・インデックス」「骨格情報」「カメラ・イメージ」を取得するように初期化を行っている。
nui.Initialize(RuntimeOptions.UseDepthAndPlayerIndex
| RuntimeOptions.UseSkeletalTracking
| RuntimeOptions.UseColor);
なお、Runtimeオブジェクトの生成と初期化は1度行えば十分である。そのため、サンプル・プログラムでは、Windowがロードされたときに1度だけ処理されるイベント・ハンドラ(WPFのデフォルトでは、「void Window_Loaded(object sender, EventArgs e)」メソッド内)で行っている(サンプル・プログラムのMainWindow.xaml.csファイルの76行目以降)。
* 【TIPS】Windowsフォームでも同様の初期化を行う場合、上記を参考にFormクラスのLoadイベントでRuntimeオブジェクトの生成と初期化処理を行うコードを追加すればよい。
なお、Initializeメソッドと対となるUninitializeメソッドも用意されている。Uninitializeメソッドは、Runtimeオブジェクトが不要となった際に呼び出す。サンプル・プログラムでは、Windowが破棄されるときに発生するイベントのイベント・ハンドラ(WPFのデフォルトでは「void Window_Closed(object sender, EventArgs e)」メソッド内)で行っている(サンプル・プログラムのMainWindow.xaml.csファイルの269行目以降)。
○(2-1)Kinectからカメラ・イメージの取得
RuntimeオブジェクトのInitializeメソッドの引数に「RuntimeOptions.UseColor」(=カメラ・イメージ)を含めた場合、Kinectからカメラ・イメージを取得できる。
Kinect for Windows SDK(ベータ版)では、カメラ・イメージを取得する方式は下記の2種類がある。
実際にカメラ・イメージを取得する場合は、ポーリング・モデル、イベント・モデル、ともに「Runtime.VideoStream.Openメソッド」(=RuntimeオブジェクトのVideoStreamプロパティから得られるImageStreamオブジェクトのOpenメソッド)を呼び出し、カメラ・イメージ・ストリームを開かなければならない(サンプル・プログラムのMainWindow.xaml.csファイルの93行目付近)。
このOpenメソッドを呼び出すには、下記の4つの引数が必要となる。
・第1引数:ここにはストリームの種別(=ImageStreamType列挙体の値)を設定する。設定する値は以下のいずれかである。
カメラ・イメージを取得する場合、「ImageStreamType.Video」を指定する。
・第2引数:ここには、アプリケーションが保持する先読みバッファ数を指定する。「2」を指定すると、アプリケーションが描画に利用するバッファと、キャプチャ用に利用するバッファが用意される。数を増やすと遅延が発生しやすくなる可能性がある。
・第3引数:カメラ・イメージの解像度を指定する。解像度はImageResolution列挙体(enum)の値で指定し、以下の2つが利用可能である。
・第4引数:カメラ・イメージの種類(=ImageType列挙体の値)を指定する。
解像度やカメラ・イメージの種類により、フレームレート(FPS)や色再現性に制限が出てくることがあるので注意されたい。
例えばYUVカラー形式は、640×480の解像度で15fpsのみのサポートである。また、RGBカラー形式で1280×1024の解像度にした場合には30fpsの速度が出るが、USB接続の帯域制約があるため、画像を圧縮して転送してRuntimeオブジェクトで展開するので色再現性が悪くなる。詳しくは、『プログラミング・ガイド(Programming Guide: Getting Started with the Kinect for Windows SDK Beta)(英語)』を参照されたい。
Openメソッドを呼び出した後は、ポーリング・モデルまたはイベント・モデルでカメラ・イメージ取得APIの呼び出しシーケンスが異なる。今回解説しているサンプル・プログラムはイベント・モデルを利用しているため、イベント・モデルを中心に説明をする(サンプル・プログラムのMainWindow.xaml.csファイルの106行目付近)。
イベント・モデルの場合、Openメソッドを呼び出した後、RuntimeオブジェクトのVideoFrameReadyイベントにイベント・ハンドラを追加する。このイベント・ハンドラはカメラ・イメージの準備が整った後に呼び出される。
nui.VideoFrameReady +=
new EventHandler<ImageFrameReadyEventArgs>(nui_ColorFrameReady);
このイベント・ハンドラは、object型とImageFrameReadyEventArgs型の引数を持つメソッドで、実際には以下のようになる。
void nui_ColorFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
}
このイベント・ハンドラに引数として渡るImageFrameReadyEventArgs型のe変数には、ImageFrameプロパティがあり、そのプロパティにはPlanarImage構造体のオブジェクトであるImageフィールドがある。この構造体は、転送されたイメージ(=画像)の情報を持っており、「Width」「Height」「Bits」「BytesPerPixel」などのフィールドがある(下記のリストを参照)。
なお、非常に生々しく感じるかもしれないが、Bitsフィールドよりイメージのバイト配列(=RGB各色8bit)が取得できる。バイト配列であるため、そのままではイメージとして直接扱えない。
*1 わたしは、イメージをRGBA8888形式(=RGBA各色8bitの形式)やRGB565形式(=赤色5bit+緑色6bit+青色6bitの形式)といった、画像バイト配列として扱い、加工した経験も多いため、「生々しい」と感じる程度である。しかし、そういった経験がない方にとっては扱いにくいだろう。Kinect for Windows SDKはベータ版であるため、先ほどの名前空間も含め、荒削りな部分が多い。Kinect for Windows SDKをより良いものにしていくには、「より使いやすく、汎用(はんよう)的な方法をMicrosoft Researchにリクエストする必要があるのではないか」とわたしは考える。
○(2-2)“video”コントロールにイメージをバインド
サンプル・プログラムは、WPFを用いており、またカメラ・イメージを表示するImageコントロールには、「Name="video"」という属性指定で名前が付けられていたことを思い出してほしい。
先ほどのVideoFrameReadyイベント・ハンドラのコードを以下のように変更することで、カメラ・イメージを「video」と名の付いたImageコントロールにバインドできる。
void nui_ColorFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
video.Source = BitmapSource.Create(
Image.Width, Image.Height, 96, 96, PixelFormats.Bgr32, null,
Image.Bits, Image.Width * Image.BytesPerPixel);
}
「BitmapSource.Create」メソッドは、(WPF用の)PresentationCoreアセンブリにあるSystem.Windows.Media.Imaging名前空間にあるBitmapSourceクラスのオブジェクトを生成し、返却してくれる静的メソッドである(引数の内容など、詳しくはMSDNのページを参照されたい)。このメソッドを利用して、カメラ・イメージを表示するImageコントロールにバインドするBitmapSourceオブジェクトを生成する。
確かにイメージのバイト配列となると、扱いにくく取り扱いに悩む。だがWPFにはBitmapSource.Createメソッドがあり、これを使えばさまざまな画像バイト配列からBitmapSourceオブジェクトを作成できる。今回のフォーマットも、「PixelFormats.Bgr32」(=BPP:ピクセルごとのbit数が「32」のsRGB形式。以下、単に「Bgr32形式」)を指定することでバイト配列(上記のコードでは「Image.Bits」)からBitmapSourceオブジェクトを作成でき、Imageコントロールにバインドできる。
* 【TIPS】WindowsフォームなどでPresentationCoreアセンブリを参照せずに転送したい方もいるだろう。そこで変換の例を1つ掲載しておく。
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
void nui_VideoFrameReady(object sender, ImageFrameReadyEventArgs e)
{
PlanarImage Image = e.ImageFrame.Image;
byte[] colorFrame = Image.Bits;
// RGB 32bitのバイト配列からビットマップへ変換
Bitmap bitmap = new Bitmap(
Image.Width, Image.Height, PixelFormat.Format32bppRgb);
BitmapData bd
= bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height)
, ImageLockMode.WriteOnly
, PixelFormat.Format32bppRgb);
Marshal.Copy(colorFrame, 0, bd.Scan0, colorFrame.Length);
bitmap.UnlockBits(bd);
// 変換したビットマップをpictureBox1へ
pictureBox1.Image = bitmap;
}
次のページも引き続き、実際のサンプルのコードを基に、残り3点の手順について確認していく。
Copyright© Digital Advantage Corp. All Rights Reserved.