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

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

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

2014年12月24日 改訂 (初版:2014/02/21)

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

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

 前回は、Kinect for Windows v2(以下、Kinect v2)からKinect for Windows SDK v2(以下、Kinect SDK v2)を用いてColor画像を取得する方法を紹介した。

 今回は、KinectからDepthデータを取得する方法を紹介する。

Depthセンサー

 KinectにはDepthセンサーが搭載されており、Depthデータ(=センサー面からの距離情報)を取得できる。

 Kinect v1では、投光した赤外線パターンを読み取り、パターンのゆがみからDepth情報を得る「Light Coding」という方式のDepthセンサーが搭載されていた*1

 Kinect v2では、投光した赤外線パルスが反射して戻ってくるまでの時間からDepth情報を得る「Time of Flight(ToF)」という方式のDepthセンサーに変更されている*2

    • *1 イスラエルのPrimeSense社のDepthセンサー技術。詳しくは特許情報を参照してください。
    • 米国特許出願公開(US 2010/0118123 A1) - Depth Mapping using Projected Patterns

  • *2 米国のマイクロソフト社は過去にTime of Flight(ToF)方式のDepthセンサー技術を持つ会社(3DV Systems社、Canesta社)を買収しており、この技術が使われているものと思われる。
図1 「Light Coding」方式と「Time of Flight(ToF)」方式の違い(Depthセンサーの動作原理のイメージ)

 Depthデータの解像度は、Kinect v1では320×240であったが、Kinect v2では512×424に向上している。また、奥行き方向の分解能も向上している。

 Depthデータを取得可能な範囲は、Kinect v1では0.8~4.0[m]の範囲であったが、Kinect v2では0.5~8.0[m]の範囲で取得できる*3

    • *3 Default ModeのDepthデータの取得可能な範囲。Kinect v1ではこの他に近距離のDepthデータを取得可能なNear Mode(0.4~3.0[m])や遠距離のDepthデータまで取得可能なExtended Depth(~約10.0[m])といった機能が提供されている。Kinect v2ではこのような設定を変更することなく広い範囲のDepthデータを取得できる。
    • ただし、Kinect v1、Kinect v2ともに人物を取得できる範囲から外れるほどDepthデータの精度は落ちる。

 今回は、DepthセンサーからDepthデータを取得する方法を紹介する。

サンプルプログラム

 Kinect SDK v2でDepthデータを取得、可視化して表示するサンプルプログラムを示す。連載(2)で紹介したデータを取得するステップごとに抜粋して解説する。このサンプルプログラムの全容は、以下のページで公開している。

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

 また、Kinect SDK v1で同様にDepthデータを取得、可視化して表示するサンプルプログラムは、筆者の近著『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 図2の「Sensor」に該当する部分(再掲)
  • 1Kinect v2を扱うためのSensorインターフェース。
  • 2デフォルトのSensorを取得する。
  • 3Sensorを開く。

「Source」

 SensorからSourceを取得する。

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

 Kinect SDK v1では、Depthデータを取得するには主にDepthとPlayer(人物領域)を同時に取得するStreamを利用していた*4。そのため、DepthデータとPlayerデータに分割する処理が必要であった。

 Kinect SDK v2では、DepthとBodyIndex(Kinect SDK v1のPlayerに該当する)は別々のSourceとして取得するようになった。BodyIndexについては次回紹介する。

  • *4 Kinect SDK v1ではDepthのみを取得する「Stream」も用意されているが、PlayerやSkeleton(人物姿勢)といったデータを必要とすることが多いため、DepthとPlayerを同時に取得する「Stream」が主に使われている。

「Reader」

 SourceからReaderを開く。

C++
// Reader
IDepthFrameReader* pDepthReader;  //……1
hResult = pDepthSource->OpenReader( &pDepthReader );  //……2
if( FAILED( hResult ) ){
  std::cerr << "Error : IDepthFrameSource::OpenReader()" << std::endl;
  return -1;
}
リスト1.3 図2の「Reader」に該当する部分
  • 1DepthフレームのためのReaderインターフェース。
  • 2SourceからReaderを開く。

「Frame」~「Data」

 Readerから最新のFrameを取得する。

C++
int width = 512;  //……1
int height = 424;  //……1
unsigned int bufferSize = width * height * sizeof( unsigned short );  //……2
cv::Mat bufferMat( height, width, CV_16UC1 );  //……3
cv::Mat depthMat( height, width, CV_8UC1 );  //……3
cv::namedWindow( "Depth" );

while( 1 ){
  // Frame
  IDepthFrame* pDepthFrame = nullptr;  //……4
  hResult = pDepthReader->AcquireLatestFrame( &pDepthFrame );  //……5
  if( SUCCEEDED( hResult ) ){
    hResult = pDepthFrame->AccessUnderlyingBuffer( &bufferSize, reinterpret_cast<UINT16**>( &bufferMat.data ) );  //……6
    if( SUCCEEDED( hResult ) ){
      bufferMat.convertTo( depthMat, CV_8U, -255.0f / 8000.0f, 255.0f );  //……7
    }
  }
  SafeRelease( pDepthFrame );  //……8

  // Show Window
  cv::imshow( "Depth", depthMat );
  if( cv::waitKey( 30 ) == VK_ESCAPE ){
    break;
  }
}
リスト1.4 図2の「Frame」、「Data」に該当する部分
  • 1Depthデータのサイズ(512×424)。
    ここでは説明の簡素化のため画像サイズを決め打ちしているが、サンプルプログラムではSourceからフレーム情報を取得している。
  • 2Depthデータのデータサイズ。
  • 3Depthデータを扱うためにOpenCVのcv::Mat型を準備する。
    「bufferMat」は16bitの生のDepthデータ、「depthMat」は画像として表示するために8bitの範囲に収めたDepthデータを扱う。
    「CV_16UC1」は、符号無し16bit整数(16U)が1channel(C1)並んで1画素を表現するデータ形式。「CV_8UC1」は、符号無し8bit整数(8U)を表現するデータ形式。
  • 4Depthデータを取得するためのFrameインターフェース。
  • 5Readerから最新のFrameを取得する。
  • 6FrameからDepthデータを取得する。
    Depthデータが格納された配列のポインターを取得する。ここではDepthデータを可視化するための変換処理に便利なcv::Mat型で受け取っている。
  • 7Depthデータを画像として表示するため16bitから8bitに変換する。
  • 8Frameを解放する。
    内部バッファが解放されて次のデータを取得できる状態になる。

 Frameが取得できたらDepthのデータを取り出し、画像として可視化する。

 取り出したDepthデータは、図3のように16bit(0~8000)で1画素を構成している。

 このままでは画像として表示できないので、これを8bit(0~255)の範囲に収めるように変換する。サンプルプログラムでは、cv::Mat型の変換命令(cv::Mat::convertTo())を用いてセンサーから距離が近いほど白く(255)、遠いほど黒く(0)表示するように変換している。

図3 Depthデータの並び

実行結果

 このサンプルプログラムを実行すると図3のようにKinect v2から取得したColor画像が表示される。

図4 実行結果
図4 実行結果

まとめ

 今回はKinect SDK v2でDepthデータを取得するサンプルプログラムを紹介した。次回はBodyIndex(=人物領域)データを取得するサンプルプログラムを紹介する。

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

1. Kinect v1とKinect v2の徹底比較

Kinect for Windowsの旧版と、次世代型の新版を比較しながら、進化したハードウェア&ソフトウェアをC++開発者向けに紹介する(正式版に対応させた改訂連載スタート)。今回はセンサー仕様や動作要件を徹底的に比較する。

2. Kinect v2プログラミング(C++) - Color編

Kinect SDK v2で、データを取得する基本的な流れを説明。Color画像を取得するサンプルプログラムを紹介する。正式版に合わせて改訂。

3. 【現在、表示中】≫ Kinect v2プログラミング(C++) - Depth編

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

4. Kinect v2プログラミング(C++) - BodyIndex編

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

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

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

イベント情報(メディアスポンサーです)

Azure Central の記事内容の紹介

Twitterでつぶやこう!


Build Insider賛同企業・団体

Build Insiderは、以下の企業・団体の支援を受けて活動しています(募集概要)。

ゴールドレベル

  • 日本マイクロソフト株式会社
  • グレープシティ株式会社