Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
Kinect v2プログラミング(C++) - Body編
Kinect SDK v2に実装されている主要な機能の紹介は、一通り完了。今回は、Body(人物姿勢)を取得する方法を説明する(正式版に合わせて改訂)。
前回は、Kinect for Windows v2(以下、Kinect v2)からKinect for Windows SDK v2(以下、Kinect SDK v2)を用いてBodyIndex(=人物領域)を取得する方法を紹介した。
今回は、KinectからBody(=人物姿勢)を取得する方法を紹介する。
Body
これまでに紹介したように、KinectはDepth(=センサー面からの距離情報)やBodyIndex(=人物領域)を取得できる。さらに、これらのデータを基にした人物姿勢を取得できる。
Kinectの人物姿勢は、膨大な数の姿勢情報を基に学習された識別器に人物領域の距離情報を入力することで推定している。詳しくはMicrosoft Researchの発表した論文を参照していただきたい*1。
- *1 この論文はIEEE CVPR 2011(コンピュータビジョンおよびパターン認識分野のトップカンファレンス)で発表され、Best Paperを受賞している。
背景となる技術は難しく感じるかもしれないが、開発者はKinect SDKを通じて簡単に人物姿勢を取得・利用できる。
人物姿勢データは、頭、手、足などの3次元位置を得ることができ、それを基にジェスチャー認識などを実装できる。Kinect v2 ではDepthセンサーの解像度や分解能が大幅に向上しているため、Kinect v1に比べ、指先や親指なども取得できるようになった。Kinect v2では、これらの詳細なデータを検出可能な6人全てで取得できる。
また、人物が検出可能な範囲がKinect v1は0.8~4.0mであったが、Kinect v2は0.5~4.5mと広くなっている(図1)*2。
- *2 Depthデータが取得可能な範囲(0.5~8.0m)と人物が検出できる範囲(0.5~4.5m)は異なる。
この人物領域は、Kinect SDK v1では「Skeleton」と呼ばれていたが、Kinect SDK v2では「Body」と名称が変更されている。
今回は、Bodyを取得する方法を紹介する。
サンプルプログラム
Kinect SDK v2でBodyを取得、Color画像に位置合わせして「●(丸)」を表示するサンプルプログラムを示す。また、Bodyデータを基にしたHand State(手の状態)も表示する。連載 第2回で紹介したデータを取得するステップごとに抜粋して解説する。このサンプルプログラムの全容は、以下のページで公開している。
また、Kinect SDK v1で同様にSkeletonを取得、表示するサンプルプログラムは、筆者の近著『Kinect for Windows SDK実践プログラミング』を参照していただきたい。
「Sensor」
Sensor
を取得する。
// Sensor
IKinectSensor* pSensor; //……1
HRESULT hResult = S_OK;
hResult = GetDefaultKinectSensor( &pSensor ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : GetDefaultKinectSensor" << std::endl;
return -1;
}
hResult = pSensor->Open(); //……3
if( FAILED( hResult ) ){
std::cerr << "Error : IKinectSensor::Open()" << std::endl;
return -1;
}
|
- 1Kinect v2を扱うためのSensorインターフェース。
- 2デフォルトのSensorを取得する。
- 3Sensorを開く。
「Source」
Sensor
からSource
を取得する。
// Source
IBodyFrameSource* pBodySource; //……1
hResult = pSensor->get_BodyFrameSource( &pBodySource ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : IKinectSensor::get_BodyFrameSource()" << std::endl;
return -1;
}
|
- 1BodyフレームのためのSourceインターフェース。
- 2SensorからSourceを取得する。
ここではBodyの取得に関するソースコードのみ解説しているが、サンプルプログラムでは表示のためにColorも同時に取得している。
「Reader」
Source
からReader
を開く。
// Reader
IBodyFrameReader* pBodyReader; //……1
hResult = pBodySource->OpenReader( &pBodyReader ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : IBodyFrameSource::OpenReader()" << std::endl;
return -1;
}
|
- 1BodyフレームのためのReaderインターフェース。
- 2SourceからReaderを開く。
「Frame」~「Data」
Reader
から最新のFrame
を取得する(リスト1.5)。
その前に、センサーから位置合わせのためのICoordinateMapper
インターフェースを取得しておく(リスト1.4)。ColorカメラとDepthセンサーの位置は離れているため、BodyデータとColor画像の位置合わせを行う必要があるからだ。
Kinect v2にはColor画像の座標系(ColorSapce)、Depthデータの座標系(DepthSpace)、Depthセンサーを原点とする3次元座標系(CameraSpace)があり、これらを相互に変換できる。
// Coordinate Mapper
ICoordinateMapper* pCoordinateMapper; //……1
hResult = pSensor->get_CoordinateMapper( &pCoordinateMapper ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : IKinectSensor::get_CoordinateMapper()" << std::endl;
return -1;
}
|
- 1フレーム間の位置合わせのためのインターフェース。
- 2Sensorから位置合わせのためのインターフェースを取得する。
次のリスト1.5は、連載 第2回と同じ方法で、Color画像のデータを取り出して表示している。
int width = 1920;
int height = 1080;
unsigned int bufferSize = width * height * 4 * sizeof( unsigned char );
cv::Mat bufferMat( height, width, CV_8UC4 );
cv::Mat bodyMat( height / 2, width / 2, CV_8UC4 );
cv::namedWindow( "Body" );
// Color Table
cv::Vec3b color[BODY_COUNT];
color[0] = cv::Vec3b( 255, 0, 0 );
color[1] = cv::Vec3b( 0, 255, 0 );
color[2] = cv::Vec3b( 0, 0, 255 );
color[3] = cv::Vec3b( 255, 255, 0 );
color[4] = cv::Vec3b( 255, 0, 255 );
color[5] = cv::Vec3b( 0, 255, 255 );
while( 1 ){
// Color Frame //……1
IColorFrame* pColorFrame = nullptr;
hResult = pColorReader->AcquireLatestFrame( &pColorFrame );
if( SUCCEEDED( hResult ) ){
hResult = pColorFrame->CopyConvertedFrameDataToArray( bufferSize, reinterpret_cast<BYTE*>( bufferMat.data ), ColorImageFormat_Bgra );
if( SUCCEEDED( hResult ) ){
cv::resize( bufferMat, bodyMat, cv::Size(), 0.5, 0.5 );
}
}
/* Body部分はリスト1.6 */
// Show Window
cv::imshow( "Body", bodyMat );
if( cv::waitKey( 10 ) == VK_ESCAPE ){
break;
}
}
|
- 1Bodyデータを書き込むためのColor画像を取得する。
詳しくは連載 第2回を参照してください。
リスト1.5の中のBody部分のコードは、次のリスト1.6のようになっている。
// Body Frame
IBodyFrame* pBodyFrame = nullptr; //……1
hResult = pBodyReader->AcquireLatestFrame( &pBodyFrame ); //……2
if( SUCCEEDED( hResult ) ){
IBody* pBody[BODY_COUNT] = { 0 }; //……3
hResult = pBodyFrame->GetAndRefreshBodyData( BODY_COUNT, pBody ); //……3
if( SUCCEEDED( hResult ) ){
for( int count = 0; count < BODY_COUNT; count++ ){
BOOLEAN bTracked = false; //……4
hResult = pBody[count]->get_IsTracked( &bTracked ); //……4
if( SUCCEEDED( hResult ) && bTracked ){
Joint joint[JointType::JointType_Count]; //……5
hResult = pBody[count]->GetJoints( JointType::JointType_Count, joint ); //……5
if( SUCCEEDED( hResult ) ){
// Left Hand State
HandState leftHandState = HandState::HandState_Unknown; //……6
hResult = pBody[count]->get_HandLeftState( &leftHandState ); //……6
if( SUCCEEDED( hResult ) ){
ColorSpacePoint colorSpacePoint = { 0 }; //……7
hResult = pCoordinateMapper->MapCameraPointToColorSpace( joint[JointType::JointType_HandLeft].Position, &colorSpacePoint ); //……7
if( SUCCEEDED( hResult ) ){
int x = static_cast<int>( colorSpacePoint.X );
int y = static_cast<int>( colorSpacePoint.Y );
if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){
if( leftHandState == HandState::HandState_Open ){ //……8
cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 0, 128, 0 ), 5, CV_AA );
}
else if( leftHandState == HandState::HandState_Closed ){ //……8
cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 0, 0, 128 ), 5, CV_AA );
}
else if( leftHandState == HandState::HandState_Lasso ){ //……8
cv::circle( bufferMat, cv::Point( x, y ), 75, cv::Scalar( 128, 128, 0 ), 5, CV_AA );
}
}
}
}
// Right Hand State
/* 左手と同様に右手のHand Stateを取得、状態を描画する。 */
// Joint //……9
for( int type = 0; type < JointType::JointType_Count; type++ ){
ColorSpacePoint colorSpacePoint = { 0 };
pCoordinateMapper->MapCameraPointToColorSpace( joint[type].Position, &colorSpacePoint );
int x = static_cast<int>( colorSpacePoint.X );
int y = static_cast<int>( colorSpacePoint.Y );
if( ( x >= 0 ) && ( x < width ) && ( y >= 0 ) && ( y < height ) ){
cv::circle( bufferMat, cv::Point( x, y ), 5, static_cast<cv::Scalar>( color[count] ), -1, CV_AA );
}
}
}
}
}
cv::resize( bufferMat, bodyMat, cv::Size(), 0.5, 0.5 );
}
for( int count = 0; count < BODY_COUNT; count++ ){ //……10
SafeRelease( pBody[count] );
}
}
SafeRelease( pColorFrame ); //……10
SafeRelease( pBodyFrame ); //……10
|
- 1BodyのためのFrameインターフェース。
- 2Readerから最新のFrameを取得する。
- 3FrameからBodyを取得する。
以降、人物からデータを取得していく。 - 4人物をトラッキングできているのかを確認する。
- 5人物からJoint(節)を取得する。
- 6Hand Stateを取得する。
- 7描画するためにJointのCamera座標系からColor座標系へ位置合わせする。
位置合わせした座標が描画領域(ここではColor画像のサイズ1920×1080)を超えないかチェックする。 - 8状態に対応する色の◯(丸)を描画してHand Stateを可視化する。
ここでは、Open(開く:パー)、Closed(閉じる:グー)、Lasso(投げ縄:ピース)をそれぞれ対応する色で描画する。状態を検出できない場合は描画しない。 - 9Jointを人物に対応するカラーテーブルの色を参照して描画する。
Hand Stateと同様にCamera座標系からColor座標系へ位置合わせして●(丸)描画する。 - 10IBodyおよびFrameを解放する。
内部バッファが解放されて次のデータを取得できる状態になる。
Kinect SDK v1では検出した人物領域の中から詳細な人物姿勢は2人までしか取得できなかった。Kinect SDK v2では検出した人物領域の全て(6人)で詳細な人物姿勢を取得できるようになった。
また、Kinect SDK v1では取得できるJointは全身で20カ所であったが、Kinect SDK v2では「首(=NECK)」、「指先(=HAND_TIP_LEFT、HAND_TIP_RIGHT)」、「親指(=THUMB_LEFT、THUMB_RIGHT)」の5カ所が追加され25カ所のJointを取得できる。
サンプルプログラムでは、Joint位置にカラーテーブルから参照した色で「●(丸)」を描画して可視化する。
Kinect SDK v1(Kinect Developer Toolkit/Kinect Interaction)で取得できるHand Stateは、「Open(=開いた)」と「Closed(=閉じた)」の2種類であった。
Kinect SDK v2では、「Open」「Closed」に加え、「Lasso(=投げ縄)」という状態が取得できる。「Lasso」は二本指を立てた状態を検出できる。こちらで解説されているように「じゃんけん(グー、チョキ、パー)」をイメージするといいだろう。
現在は、Bodyを取得できる6人のうち2人まで同時にHand Stateを取得できる。
サンプルプログラムでは、手のJoint位置に状態により色付けした「◯(丸)」を描画して可視化する。「Open」は緑色(=cv::Scalar( 0, 128, 0 )
)、「Closed」は赤色(=cv::Scalar( 0, 0, 128 )
)、「Lasso」は水色(=cv::Scalar( 128, 128, 0 )
)で表現する。
Kinect SDK v1 | Kinect SDK v2 | |
---|---|---|
名称 | Skeleton | Body |
検出可能範囲 | 0.8~4.0m (Near Mode 0.4~3.0m) |
0.5~4.5m |
取得可能人数 | 2人 | 6人 |
Joint(節) | 20カ所 | 25カ所 |
Hand State(手の状態) | 2種類 | 3種類 |
Hand Stateを取得可能な人数 | 2人 | 2人 |
実行結果
このサンプルプログラムを実行すると図3のようにKinect v2から取得した人物姿勢と手の状態が表示される。
まとめ
今回はKinect SDK v2でBodyを取得するサンプルプログラムを紹介した。次回はプログラムのデバッグなどに役立つ開発支援ツールKinect Studioを紹介する。
※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
4. Kinect v2プログラミング(C++) - BodyIndex編
Kinect SDK v2で、BodyIndex(人物領域)を取得する方法を、サンプルコードを示しながら説明する(正式版に合わせて改訂)。
5. 【現在、表示中】≫ Kinect v2プログラミング(C++) - Body編
Kinect SDK v2に実装されている主要な機能の紹介は、一通り完了。今回は、Body(人物姿勢)を取得する方法を説明する(正式版に合わせて改訂)。
7. Kinect v2プログラミング(C++) - AudioBeam編
Kinect v2では、Microphoneアレイにより水平面音源方向の推定(AudioBeam)や音声認識(Speech Recognition)などが行える。今回はAudioBeamを取得する方法を解説する(正式版に合わせて改訂)。