第2回 Kinect for Windows SDKでカメラ映像/深度情報/骨格情報の取得:連載:Kinect for Windows SDK(ベータ版)開発入門(1/2 ページ)
C#のプログラムでKinectセンサーを制御しよう! SDKの基本的な使い方を、サンプル・プログラムを通して理解する。
2回目となる今回は、前回に続き、C#で記述したプログラムでKinectセンサー(以下、単に「Kinect」)を制御することを目標に、Kinect for Windows SDK(ベータ版)について見ていく。
前回は、Kinect for Windows SDK(ベータ版)の特徴やインストールされたプログラム・ファイルなどについて紹介した。今回は、インストールされたC#版サンプル・プログラムについて解説し、Kinect for Windows SDK(ベータ版)の基本的な使い方を理解する。主な内容は、NUI(ナチュラル・ユーザー・インターフェイス)のカメラ・イメージの取得、深度情報の取得、骨格情報の取得である。ちなみに次回は、Audioの音源位置の推定と音声認識について説明する予定だ。
■サンプル・プログラム「Skeletal Viewer」
ここでは、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つのファイルが重要である。
- SkeletalViewer.sln:Visual Studio 2010のソリューション・ファイル。このファイルを開くと、SkeletalViewerプロジェクトの開発ができる。
- MainWindow.xaml:メイン・ウィンドウのレイアウトを記載したXAMLファイル。
- MainWindow.xaml.cs:NUI処理を行うプログラムが記載されている。
「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つまたは複数指定する。
- カメラ・イメージ(RuntimeOptions.UseColor):いわずとしれた、カメラ・イメージ画像を取得する。
- 深度情報(RuntimeOptions.UseDepth):深度情報を取得する。
- 深度情報とプレイヤー・インデックス(RuntimeOptions.UseDepthAndPlayerIndex):プレイヤーを識別するインデックス番号が付与された深度情報を取得する。
- 骨格情報(RuntimeOptions.UseSkeletalTracking):骨格情報を取得する。
サンプル・プログラムでは、「深度情報とプレイヤー・インデックス」「骨格情報」「カメラ・イメージ」を取得するように初期化を行っている。
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種類がある。
- ポーリング・モデル:カメラ・イメージ取得APIであるImageStreamクラスのGetNextFrameメソッドを呼び出し、その戻り値が返るまで(=カメラ・イメージ取得の準備ができるまで、またはタイムアウトするまで)、それ以降のコードの実行をブロックするモデルである。例えば無限ループ内でカメラ・イメージ取得APIを繰り返し呼び出すことで、新しいカメラ・イメージ取得のポーリングが行える。GetNextFrameメソッドの引数には、ミリ秒単位のタイムアウト値を設定可能である。もし引数に「0」を設定すれば、タイムアウトは発生しない。GetNextFrameメソッドの戻り値では、新しいフレームが返される。
- イベント・モデル:RuntimeオブジェクトのImageFrameReadyイベントにイベント・ハンドラを追加し、新しいフレーム・データの準備ができたら、イベント・ハンドラが呼び出されるモデルである。
実際にカメラ・イメージを取得する場合は、ポーリング・モデル、イベント・モデル、ともに「Runtime.VideoStream.Openメソッド」(=RuntimeオブジェクトのVideoStreamプロパティから得られるImageStreamオブジェクトのOpenメソッド)を呼び出し、カメラ・イメージ・ストリームを開かなければならない(サンプル・プログラムのMainWindow.xaml.csファイルの93行目付近)。
このOpenメソッドを呼び出すには、下記の4つの引数が必要となる。
・第1引数:ここにはストリームの種別(=ImageStreamType列挙体の値)を設定する。設定する値は以下のいずれかである。
- ImageStreamType.Video:カメラ・イメージ。
- ImageStreamType.Depth:深度情報。
カメラ・イメージを取得する場合、「ImageStreamType.Video」を指定する。
・第2引数:ここには、アプリケーションが保持する先読みバッファ数を指定する。「2」を指定すると、アプリケーションが描画に利用するバッファと、キャプチャ用に利用するバッファが用意される。数を増やすと遅延が発生しやすくなる可能性がある。
・第3引数:カメラ・イメージの解像度を指定する。解像度はImageResolution列挙体(enum)の値で指定し、以下の2つが利用可能である。
- ImageResolution.Resolution640x480:640×480。
- ImageResolution.Resolution1280x1024:1280×1024。
・第4引数:カメラ・イメージの種類(=ImageType列挙体の値)を指定する。
- ImageType.Color:sRGB色空間(=赤・緑・青という3色の組み合わせで表現される色空間で、主にPC向け)。RGB各色8bit。
- ImageType.ColorYUV:カメラ・デバイスはYUV(=輝度・青色との差・赤色との差という3色の組み合わせで表現される色空間で、主にTV放送やDVD映像向け)で取得し、RGB色空間に変換(YUV色空間で表現可能な明るい色や暗い色などの一部の情報が失われる)。
- ImageType.ColorYUVRaw:YUV色空間。
解像度やカメラ・イメージの種類により、フレームレート(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」などのフィールドがある(下記のリストを参照)。
- Width:転送されたイメージの幅(int型)。
- Height:転送されたイメージの高さ(int型)。
- Bits:イメージを含むバイト配列(byte[]型)。
- BytesPerPixel:転送されたイメージのバイト数(int型)。
なお、非常に生々しく感じるかもしれないが、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.