Kinect for Windows SDK v2.0入門 目次
BodyIndex(人の検出)を取得して表示する方法について解説します。Kinect for Windows v1での人の検出はDepthデータに混ざって取得していましたが、v2では単独のデータとして扱うようになっています。解像度はDepthと同じ512x424、検出数はv1と同じ6人です。
環境
筆者の環境は次の通りです。
- Windows 8.1 Pro Update1 64bit
- Visual Studio 2013 Ultimate(Express for Windows Desktopでも可)
- Kinect for Windows SDK v2.0-1409(リリース版)
- 32bitアプリケーション
- サンプルコードのリポジトリ
実行結果
BodyIndexを表示します。
解説
BodyIndexもカラー画像、Depthと同じようにデータを取得します。
KinectSensor(クラス)を起点に、BodyIndexFrameSourceを取得、そこからBodyIndexFrameReaderを開きます。BodyIndexFrameReaderのデータが更新されるとBodyIndexFrameを取得でき、そこからBodyIndexを取得します。なお関節を検出するv1のSkeletonStream相当はBodyFrameSourceとなっているので、ややこしいですが注意してください。
解像度(幅、高さなど)もBodyIndexFrameSourceからFrameDescriptionで取得できます。BodyIndexの取得設定に先だって、使用するバッファの初期化などをFrameDescriptionを使って作成しています。
初期化
初期化の手順は次の通りです
- Kinectを開く
- BodyIndexデータの解像度を取得し、バッファなどを作成する
- BodyIndexリーダーを開く
まずKinectを開きます。
続いてBodyIndexFrameSourceからFrameDescriptionを取得します。フォーマットなどは1つだけなので、規定のDescriptionを使ってビットマップやバッファの作成を行います。
最後にBodyIndexリーダーを作成し、データの読み込み準備を行います。データの読み込みは、C++ではポーリング、C#ではイベントハンドラの登録を行います。
BodyIndexは0-5の6人の検出ができますので、それに対応した色情報を作成します。インデックスによって色を変えられるようにします。
C++
void initialize(){
// デフォルトのKinectを取得する
ERROR_CHECK( ::GetDefaultKinectSensor( &kinect ) );
// Kinectを開く
ERROR_CHECK( kinect->Open() );
BOOLEAN isOpen = false;
ERROR_CHECK( kinect->get_IsOpen( &isOpen ) );
if ( !isOpen ){
throw std::runtime_error( "Kinectが開けません" );
}
// ボディインデックスリーダーを取得する
ComPtr<IBodyIndexFrameSource> bodyIndexFrameSource;
ERROR_CHECK( kinect->get_BodyIndexFrameSource( &bodyIndexFrameSource ) );
ERROR_CHECK( bodyIndexFrameSource->OpenReader( &bodyIndexFrameReader ) );
// ボディインデックスの解像度を取得する
ComPtr<IFrameDescription> bodyIndexFrameDescription;
ERROR_CHECK( bodyIndexFrameSource->get_FrameDescription( &bodyIndexFrameDescription ) );
bodyIndexFrameDescription->get_Width( &BodyIndexWidth );
bodyIndexFrameDescription->get_Height( &BodyIndexHeight );
// バッファーを作成する
bodyIndexBuffer.resize( BodyIndexWidth * BodyIndexHeight );
// プレイヤーの色を設定する
colors[0] = cv::Scalar( 255, 0, 0 );
colors[1] = cv::Scalar( 0, 255, 0 );
colors[2] = cv::Scalar( 0, 0, 255 );
colors[3] = cv::Scalar( 255, 255, 0 );
colors[4] = cv::Scalar( 255, 0, 255 );
colors[5] = cv::Scalar( 0, 255, 255 );
}
C#(デスクトップ)
private void Window_Loaded( object sender, RoutedEventArgs e ){
try {
kinect = KinectSensor.GetDefault();
if ( kinect == null ) {
throw new Exception("Kinectを開けません");
}
kinect.Open();
// 表示のためのデータを作成
bodyIndexFrameDesc = kinect.DepthFrameSource.FrameDescription;
// ボディインデックデータ用のバッファ
bodyIndexBuffer = new byte[bodyIndexFrameDesc.LengthInPixels];
// 表示のためのビットマップに必要なものを作成
bodyIndexColorImage = new WriteableBitmap( bodyIndexFrameDesc.Width, bodyIndexFrameDesc.Height,
96, 96, PixelFormats.Bgra32, null );
bodyIndexColorRect = new Int32Rect( 0, 0, bodyIndexFrameDesc.Width, bodyIndexFrameDesc.Height );
bodyIndexColorStride = (int)(bodyIndexFrameDesc.Width * bodyIndexColorBytesPerPixel);
// ボディインデックデータをBGRA(カラー)データにするためのバッファ
bodyIndexColorBuffer = new byte[bodyIndexFrameDesc.LengthInPixels * bodyIndexColorBytesPerPixel];
ImageBodyIndex.Source = bodyIndexColorImage;
// 色付けするために色の配列を作成する
bodyIndexColors = new Color[]{
Colors.Red, Colors.Blue, Colors.Green, Colors.Yellow, Colors.Pink, Colors.Purple,
};
// ボディーリーダーを開く
bodyIndexFrameReader = kinect.BodyIndexFrameSource.OpenReader();
bodyIndexFrameReader.FrameArrived += bodyIndexFrameReader_FrameArrived;
}
catch ( Exception ex ) {
MessageBox.Show( ex.Message );
Close();
}
}
C#(Windows ストアアプリ)
protected override void OnNavigatedTo( NavigationEventArgs e ){
base.OnNavigatedTo( e );
try {
kinect = KinectSensor.GetDefault();
if ( kinect == null ) {
throw new Exception( "Kinectを開けません" );
}
kinect.Open();
// 表示のためのデータを作成
bodyIndexFrameDesc = kinect.BodyIndexFrameSource.FrameDescription;
// ビットマップ
bodyIndexColorBitmap = new WriteableBitmap( bodyIndexFrameDesc.Width, bodyIndexFrameDesc.Height );
ImageBodyIndex.Source = bodyIndexColorBitmap;
// ボディインデックデータ用のバッファ
bodyIndexBuffer = new byte[bodyIndexFrameDesc.LengthInPixels];
// ボディインデックデータをBGRA(カラー)データにするためのバッファ
bodyIndexColorBuffer = new byte[bodyIndexFrameDesc.LengthInPixels * bodyIndexColorBytesPerPixels];
// 色付けするために色の配列を作成する
bodyIndexColors = new Color[]{
Colors.Red, Colors.Blue, Colors.Green, Colors.Yellow, Colors.Pink, Colors.Purple,
};
// ボディインデックスリーダーを開く
bodyIndexFrameReader = kinect.BodyIndexFrameSource.OpenReader();
bodyIndexFrameReader.FrameArrived += bodyIndexFrameReader_FrameArrived;
}
catch ( Exception ex ) {
MessageDialog dlg = new MessageDialog(ex.Message);
dlg.ShowAsync();
}
}
データの取得
続いてデータを取得し表示します。C++ではポーリング、C#ではイベントハンドラになりますが、大きな流れは同じです。
- BodyIndexフレームを取得する
- BodyIndexデータを取得する
- BodyIndexデータを表示する
BodyIndexフレーム、データを取得する
イベントが発生またはフレームが取得できたら、フレームからBodyIndexデータをコピーします。BodyIndexデータは1ピクセルあたり8bitのデータが512×424個ならんでいます。配列の要素にアクセスすることで、任意の座標のBodyIndexデータを取得できます。
C++
void updateBodyIndexFrame(){
// フレームを取得する
ComPtr<IBodyIndexFrame> bodyIndexFrame;
auto ret = bodyIndexFrameReader->AcquireLatestFrame( &bodyIndexFrame );
if ( ret == S_OK ){
// ボディインデックスを取得する
ERROR_CHECK( bodyIndexFrame->CopyFrameDataToArray( bodyIndexBuffer.size(), &bodyIndexBuffer[0] ) );
// スマートポインタを使ってない場合は、自分でフレームを解放する
// bodyIndexFrame->Release();
}
}
C#(デスクトップ)
private void UpdateBodyIndexFrame( BodyIndexFrameArrivedEventArgs e ){
using ( var bodyIndexFrame = e.FrameReference.AcquireFrame() ) {
if ( bodyIndexFrame == null ) {
return;
}
// ボディインデックスデータを取得する
bodyIndexFrame.CopyFrameDataToArray( bodyIndexBuffer );
}
}
C#(Windows ストアアプリ)
private void UpdateBodyIndexFrame( BodyIndexFrameArrivedEventArgs args ){
// ボディインデックスフレームを取得する
using ( var bodyIndexFrame = args.FrameReference.AcquireFrame() ) {
if ( bodyIndexFrame == null ) {
return;
}
// ボディインデックスデータを取得する
bodyIndexFrame.CopyFrameDataToArray( bodyIndexBuffer );
}
}
BodyIndexデータを表示する
BodyIndexデータは、その画素が人を検出していない場合には255(0xFF)、人を検出している場合には0-5の値がふられています。画素を走査して255(0xFF)ではないインデックスの場合には対応した色を塗ります。255(0xFF)の場合には黒を塗るようにします。
C++
void drawBodyIndexFrame(){
// ボディインデックスをカラーデータに変換して表示する
cv::Mat bodyIndexImage( BodyIndexHeight, BodyIndexWidth, CV_8UC4 );
for ( int i = 0; i < BodyIndexWidth * BodyIndexHeight; ++i ){
int index = i * 4;
// 人がいれば255以外
if ( bodyIndexBuffer[i] != 255 ){
auto color = colors[bodyIndexBuffer[i]];
bodyIndexImage.data[index + 0] = color[0];
bodyIndexImage.data[index + 1] = color[1];
bodyIndexImage.data[index + 2] = color[2];
}
else{
bodyIndexImage.data[index + 0] = 0;
bodyIndexImage.data[index + 1] = 0;
bodyIndexImage.data[index + 2] = 0;
}
}
cv::imshow( "BodyIndex Image", bodyIndexImage );
}
C#(デスクトップ)
private void DrawBodyIndexFrame(){
// ボディインデックスデータをBGRAデータに変換する
for ( int i = 0; i < bodyIndexBuffer.Length; i++ ) {
var index = bodyIndexBuffer[i];
var colorIndex = i * 4;
if ( index != 255 ) {
var color = bodyIndexColors[index];
bodyIndexColorBuffer[colorIndex + 0] = color.B;
bodyIndexColorBuffer[colorIndex + 1] = color.G;
bodyIndexColorBuffer[colorIndex + 2] = color.R;
bodyIndexColorBuffer[colorIndex + 3] = 255;
}
else {
bodyIndexColorBuffer[colorIndex + 0] = 0;
bodyIndexColorBuffer[colorIndex + 1] = 0;
bodyIndexColorBuffer[colorIndex + 2] = 0;
bodyIndexColorBuffer[colorIndex + 3] = 255;
}
}
// ビットマップにする
bodyIndexColorImage.WritePixels( bodyIndexColorRect, bodyIndexColorBuffer, bodyIndexColorStride, 0 );
}
C#(Windows ストアアプリ)
private void DrawBodyIndexFrame(){
// ボディインデックスデータをBGRAデータに変換する
for ( int i = 0; i < bodyIndexBuffer.Length; i++ ) {
var index = bodyIndexBuffer[i];
int colorindex = i * 4;
if ( index != 255 ) {
var color = bodyIndexColors[index];
bodyIndexColorBuffer[colorindex + 0] = color.B;
bodyIndexColorBuffer[colorindex + 1] = color.G;
bodyIndexColorBuffer[colorindex + 2] = color.R;
bodyIndexColorBuffer[colorindex + 3] = 255;
}
else {
bodyIndexColorBuffer[colorindex + 0] = 0;
bodyIndexColorBuffer[colorindex + 1] = 0;
bodyIndexColorBuffer[colorindex + 2] = 0;
bodyIndexColorBuffer[colorindex + 3] = 255;
}
}
// ビットマップにする
var stream = bodyIndexColorBitmap.PixelBuffer.AsStream();
stream.Write( bodyIndexColorBuffer, 0, bodyIndexColorBuffer.Length );
bodyIndexColorBitmap.Invalidate();
}
まとめ
v1と違いBodyIndexとして独立したデータになっているので、扱いが非常に楽になりました(v1はビットシフトが必要でした)。グリーンスクリーンなどプレイヤーの切り出しにはBodyIndexを利用します。カラー画像と解像度が異なるためBodyIndexの座標の変換が必要です。これについてはあとで解説します。