ブログ@kaorun55

HoloLensやKinectなどのDepthセンサーを中心に書いています。

Kinect for Windows SDK v2.0 でBodyIndex(人の検出)を取得する

Kinect for Windows SDK v2.0入門 目次

BodyIndex(人の検出)を取得して表示する方法について解説します。Kinect for Windows v1での人の検出はDepthデータに混ざって取得していましたが、v2では単独のデータとして扱うようになっています。解像度はDepthと同じ512x424、検出数はv1と同じ6人です。

環境

筆者の環境は次の通りです。

実行結果

BodyIndexを表示します。

キャプチャ

解説

BodyIndexもカラー画像、Depthと同じようにデータを取得します。

KinectSensor(クラス)を起点に、BodyIndexFrameSourceを取得、そこからBodyIndexFrameReaderを開きます。BodyIndexFrameReaderのデータが更新されるとBodyIndexFrameを取得でき、そこからBodyIndexを取得します。なお関節を検出するv1のSkeletonStream相当はBodyFrameSourceとなっているので、ややこしいですが注意してください。

解像度(幅、高さなど)もBodyIndexFrameSourceからFrameDescriptionで取得できます。BodyIndexの取得設定に先だって、使用するバッファの初期化などをFrameDescriptionを使って作成しています。

初期化

初期化の手順は次の通りです

  1. Kinectを開く
  2. BodyIndexデータの解像度を取得し、バッファなどを作成する
  3. 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#ではイベントハンドラになりますが、大きな流れは同じです。

  1. BodyIndexフレームを取得する
  2. BodyIndexデータを取得する
  3. 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の座標の変換が必要です。これについてはあとで解説します。