Kinect for Windows v2入門 ― C++プログラマー向け連載(3)
Kinect v2プログラミング(C++) - Depth編
Kinect SDK v2プレビュー版で、Depthデータを取得する方法を説明する(改訂版)。
前回は、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社)を買収しており、この技術が使われているものと思われる。
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)で紹介したデータを取得するステップごとに抜粋して解説する。このサンプルプログラムの全容は、以下のページで公開している。
また、Kinect SDK v1で同様にDepthデータを取得、可視化して表示するサンプルプログラムは、筆者の近著『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
IDepthFrameSource* pDepthSource; //……1
hResult = pSensor->get_DepthFrameSource( &pDepthSource ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : IKinectSensor::get_DepthFrameSource()" << std::endl;
return -1;
}
|
- 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
を開く。
// Reader
IDepthFrameReader* pDepthReader; //……1
hResult = pDepthSource->OpenReader( &pDepthReader ); //……2
if( FAILED( hResult ) ){
std::cerr << "Error : IDepthFrameSource::OpenReader()" << std::endl;
return -1;
}
|
- 1DepthフレームのためのReaderインターフェース。
- 2SourceからReaderを開く。
「Frame」~「Data」
Reader
から最新のFrame
を取得する。
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;
}
}
|
- 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のようにKinect v2から取得したColor画像が表示される。
まとめ
今回は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画像を取得するサンプルプログラムを紹介する。正式版に合わせて改訂。
4. Kinect v2プログラミング(C++) - BodyIndex編
Kinect SDK v2で、BodyIndex(人物領域)を取得する方法を、サンプルコードを示しながら説明する(正式版に合わせて改訂)。
5. Kinect v2プログラミング(C++) - Body編
Kinect SDK v2に実装されている主要な機能の紹介は、一通り完了。今回は、Body(人物姿勢)を取得する方法を説明する(正式版に合わせて改訂)。