Deep Insider の Tutor コーナー
>>  Deep Insider は本サイトからスピンオフした姉妹サイトです。よろしく! 
Kinect for Windows v2入門 ― C++プログラマー向け連載(5)

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)

Kinect v2プログラミング(C++) - Body編

2014年12月26日 改訂 (初版:2014/03/17)

Kinect SDK v2に実装されている主要な機能の紹介は、一通り完了。今回は、Body(人物姿勢)を取得する方法を説明する(正式版に合わせて改訂)。

杉浦 司(Microsoft MVP for Kinect for Windows)
  • このエントリーをはてなブックマークに追加

 前回は、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

 背景となる技術は難しく感じるかもしれないが、開発者は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)は異なる。
図1 Kinect v2のDepthの取得範囲と人物の検出範囲(再掲)

 この人物領域は、Kinect SDK v1では「Skeleton」と呼ばれていたが、Kinect SDK v2では「Body」と名称が変更されている。

 今回は、Bodyを取得する方法を紹介する。

サンプルプログラム

 Kinect SDK v2でBodyを取得、Color画像に位置合わせして「●(丸)」を表示するサンプルプログラムを示す。また、Bodyデータを基にしたHand State(手の状態)も表示する。連載 第2回で紹介したデータを取得するステップごとに抜粋して解説する。このサンプルプログラムの全容は、以下のページで公開している。

図1 Kinect SDK v2のデータ取得の流れ(再掲)

 また、Kinect SDK v1で同様にSkeletonを取得、表示するサンプルプログラムは、筆者の近著『Kinect for Windows SDK実践プログラミング』を参照していただきたい。

「Sensor」

 Sensorを取得する。

C++
// 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;
}
リスト1.1 図1の「Sensor」に該当する部分(再掲)
  • 1Kinect v2を扱うためのSensorインターフェース。
  • 2デフォルトのSensorを取得する。
  • 3Sensorを開く。

「Source」

 SensorからSourceを取得する。

C++
// Source
IBodyFrameSource* pBodySource;  //……1
hResult = pSensor->get_BodyFrameSource( &pBodySource );  //……2
if( FAILED( hResult ) ){
  std::cerr << "Error : IKinectSensor::get_BodyFrameSource()" << std::endl;
  return -1;
}
リスト1.2 図1の「Source」に該当する部分
  • 1BodyフレームのためのSourceインターフェース。
  • 2SensorからSourceを取得する。

 ここではBodyの取得に関するソースコードのみ解説しているが、サンプルプログラムでは表示のためにColorも同時に取得している。

「Reader」

 SourceからReaderを開く。

C++
// Reader
IBodyFrameReader* pBodyReader;  //……1
hResult = pBodySource->OpenReader( &pBodyReader );  //……2
if( FAILED( hResult ) ){
  std::cerr << "Error : IBodyFrameSource::OpenReader()" << std::endl;
  return -1;
}
リスト1.3 図1の「Reader」に該当する部分
  • 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)があり、これらを相互に変換できる。

C++
// 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.4 位置合わせインターフェースの取得
  • 1フレーム間の位置合わせのためのインターフェース。
  • 2Sensorから位置合わせのためのインターフェースを取得する。

 次のリスト1.5は、連載 第2回と同じ方法で、Color画像のデータを取り出して表示している。

C++
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;
  }
}
リスト1.5 図1の「Frame」、「Data」に該当する部分(その1)
  • 1Bodyデータを書き込むためのColor画像を取得する。
    詳しくは連載 第2回を参照してください。

 リスト1.5の中のBody部分のコードは、次のリスト1.6のようになっている。

C++
  // 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
リスト1.6 図1の「Frame」、「Data」に該当する部分(その2)
  • 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人
表1 Kinect SDK v1とKinect SDK v2の人物姿勢(Skeleton、Body)の比較
図2 Kinect v1の取得できるJoint
図2 Kinect v2の取得できるJoint
図2 Kinect v1とKinect v2の取得できるJoint

実行結果

 このサンプルプログラムを実行すると図3のようにKinect v2から取得した人物姿勢と手の状態が表示される。

図3 実行結果

Jointを●、Hand Stateを◯で表示している。

図4 Hand Stateの認識結果

「Open」は緑色、「Closed」は赤色、「Lasso」は水色の◯で表示している。
手の状態が認識できていることが分かる。

まとめ

 今回はKinect SDK v2でBodyを取得するサンプルプログラムを紹介した。次回はプログラムのデバッグなどに役立つ開発支援ツールKinect Studioを紹介する。

※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
 連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
3. Kinect v2プログラミング(C++) - Depth編

Kinect SDK v2プレビュー版で、Depthデータを取得する方法を説明する(改訂版)。

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
4. Kinect v2プログラミング(C++) - BodyIndex編

Kinect SDK v2で、BodyIndex(人物領域)を取得する方法を、サンプルコードを示しながら説明する(正式版に合わせて改訂)。

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
5. 【現在、表示中】≫ Kinect v2プログラミング(C++) - Body編

Kinect SDK v2に実装されている主要な機能の紹介は、一通り完了。今回は、Body(人物姿勢)を取得する方法を説明する(正式版に合わせて改訂)。

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
6. Kinect Studioとは?

Kinectアプリ開発支援ツール「Kinect Studio」の機能や使い方を解説する。データを記録/再生できるので、開発&デバッグが簡単に。

Kinect for Windows v2入門 ― C++プログラマー向け連載(5)
7. Kinect v2プログラミング(C++) - AudioBeam編

Kinect v2では、Microphoneアレイにより水平面音源方向の推定(AudioBeam)や音声認識(Speech Recognition)などが行える。今回はAudioBeamを取得する方法を解説する(正式版に合わせて改訂)。

サイトからのお知らせ

Twitterでつぶやこう!