2011/08/03

Kinect SDK(3) ~深度センサ利用編~

前回はRGBカメラを利用しました。
今回は深度センサの値を画像として表示してみたいと思います。
まず、サンプルプログラムを以下に示します。

/*
Kinect の 深度センサを表示するプログラム
*/

#include<Windows.h>
#include<MSR_NuiApi.h>//KinectSDK利用時にinclude
#include<opencv2opencv.hpp>//今回はOpenCVを利用します。


//各種libファイルの設定
#pragma comment(lib,"MSRKinectNUI.lib")

#ifdef _DEBUG
    #pragma comment(lib,"opencv_core220d.lib") 
    #pragma comment(lib,"opencv_highgui220d.lib") 
#else
    //Releaseモードの場合
    #pragma comment(lib,"opencv_core220.lib")  
    #pragma comment(lib,"opencv_highgui220.lib")
#endif

//ハンドル
HANDLE m_hNextDepthFrameEvent;
HANDLE m_pDepthStreamHandle;

//RGBデータ
cv::Ptr<IplImage> DepthImage;

//RGBデータを取得する関数
int GetDepthImage();
RGBQUAD Nui_ShortToQuad_Depth( USHORT s );


int main(){
 //Kinectの初期化関数
 NuiInitialize( NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX | NUI_INITIALIZE_FLAG_USES_SKELETON );

 //各ハンドルの設定
 m_hNextDepthFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
 m_pDepthStreamHandle   = NULL;

 //深度センサストリームの設定
 HRESULT hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX , NUI_IMAGE_RESOLUTION_320x240 , 0 , 2 , m_hNextDepthFrameEvent , &m_pDepthStreamHandle );
 if( FAILED( hr ) ) return -1;//取得失敗
 
 //OpenCVの設定
 cvNamedWindow("Kinect Depth and Player Image",CV_WINDOW_AUTOSIZE);
 DepthImage = cvCreateImage( cvSize (320, 240) , IPL_DEPTH_8U , 3);


 while(cvWaitKey(10)!='q'){
  //次のRGBフレームが来るまで待機
  WaitForSingleObject(m_hNextDepthFrameEvent,INFINITE);

  //取得をする
  if(GetDepthImage()==-1)return -2;

  //表示
  cvShowImage ("Kinect Depth and Player Image",DepthImage);
 }

 cvDestroyAllWindows();
 NuiShutdown();

 return 0;
}

int GetDepthImage(){
 //フレームを入れるクラス
 const NUI_IMAGE_FRAME * pImageFrame = NULL;

    HRESULT hr = NuiImageStreamGetNextFrame(m_pDepthStreamHandle, 30 , &pImageFrame );
    if( FAILED( hr ) ) return -1;//取得失敗

 //フレームから画像データの取得
    NuiImageBuffer * pTexture = pImageFrame->pFrameTexture;
    
 //ここからオマジナイ
 KINECT_LOCKED_RECT LockedRect;
    pTexture->LockRect( 0, &LockedRect, NULL, 0 );
    
 if( LockedRect.Pitch != 0 ){
  //pBitsに画像データが入っている
        BYTE * pBuffer = (BYTE*) LockedRect.pBits;
  USHORT * pBufferRun = (USHORT*) pBuffer;
  char* depth_image;
  depth_image = DepthImage->imageData;
  int i=0;               
        for( int y = 0 ; y < 240 ; y++ )
        {
            for( int x = 0 ; x < 320 ; x++ )
            {
    RGBQUAD quad = Nui_ShortToQuad_Depth( *pBufferRun );
                pBufferRun++;
    
    depth_image[i]=(char)quad.rgbRed;
    i++;
    depth_image[i]=(char)quad.rgbBlue;
    i++;
    depth_image[i]=(char)quad.rgbGreen;
    i++;
            }
        }
 }

    hr = NuiImageStreamReleaseFrame( m_pDepthStreamHandle, pImageFrame );
 if( FAILED( hr ) ) return -1;//取得失敗

 return 0;
}


RGBQUAD Nui_ShortToQuad_Depth( USHORT s )
{
    USHORT RealDepth = (s & 0xfff8) >> 3;
    USHORT Player = s & 7;

    // transform 13-bit depth information into an 8-bit intensity appropriate
    // for display (we disregard information in most significant bit)
    BYTE l = 255 - (BYTE)(256*RealDepth/0x0fff);

    RGBQUAD q;
    q.rgbRed = q.rgbBlue = q.rgbGreen = 0;

    switch( Player )
    {
    case 0:
        q.rgbRed = l / 2;
        q.rgbBlue = l / 2;
        q.rgbGreen = l / 2;
        break;
    case 1:
        q.rgbRed = l;
        break;
    case 2:
        q.rgbGreen = l;
        break;
    case 3:
        q.rgbRed = l / 4;
        q.rgbGreen = l;
        q.rgbBlue = l;
        break;
    case 4:
        q.rgbRed = l;
        q.rgbGreen = l;
        q.rgbBlue = l / 4;
        break;
    case 5:
        q.rgbRed = l;
        q.rgbGreen = l / 4;
        q.rgbBlue = l;
        break;
    case 6:
        q.rgbRed = l / 2;
        q.rgbGreen = l / 2;
        q.rgbBlue = l;
        break;
    case 7:
        q.rgbRed = 255 - ( l / 2 );
        q.rgbGreen = 255 - ( l / 2 );
        q.rgbBlue = 255 - ( l / 2 );
    }
    return q;
}

実行結果はこのようになります。
図1.デプスマップの表示
図2.ユーザ情報も一緒に表示、プレイヤーが認識されていることがわかる。
プログラムのほうはどうでしょうか?実は前回のRGBカメラのプログラムが一部変わっただけだったりします。

では、前回のプログラムからどのような点を変更したか見ていきましょう。

1.初期化関数
初期化関数ですが、前回と比べて引数がかわったことがわかります。以前説明したとおり、使う機能によって引数を変える為です。今回は深度センサ(+プレイヤーの位置情報)を表示するのでNUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX 及び NUI_INITIALIZE_FLAG_USES_SKELETON のORを引数にとっています。


2.深度センサストリームの初期設定
深度センサのストリームの初期設定です。前回と違うのは第一引数がNUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEXに、第二引数がNUI_IMAGE_RESOLUTION_320x240になったことです。

第一引数は今回はRGBカメラでなく深度情報及びプレイヤー情報を表示するので変えたことがわかります。

また、第二匹数のNUI_IMAGE_RESOLUTION_320x240は取得する解像度になっております。RGBカメラの場合解像度は1280×1024または640×480ですが、深度情報の場合320×240または80×60となってます。

3.Nui_ShortToQuad_Depth関数と深度情報のビットの使い方
今回新しくNui_ShortToQuad_Depth関数について定義してますが、これはKinectSDKのサンプルプログラムからそのまま使わせて頂いたものです。何をしているかといいますと、前回のようにpBitsから手に入れた深度情報及びプレイヤー情報について解析して色に割り当てを行っています。実際に関数について見てみましょう。

では詳しくみてみましょう。

・深度情報:RealDepth = (s & 0xfff8) >> 3;
これはつまり16bit中先頭の13bitにデプス情報が入ってますよーって意味です。さらに言えばそのDepth情報をRealDepthに入れています。ちなみに深度情報は量子化1mmとなってます。Kinectはだいたい80cm~4m程度の値を取るみたいなので深度情報は800~4000の値が帰ってくることになります。


・プレイヤー情報:USHORT Player = s & 7;
これは後半の3bitで取得したプレイヤーの画素位置情報を入れています。つまりこの値が0以外の場合、その画素の範囲はプレイヤーですよって話です。
ちなみに3bitは全部で8パターンありますので
000(0):プレイヤーとは無関係の画素
001(1):プレイヤー1
010(2):プレイヤー2
011(3):プレイヤー3
...
のように表現されています。
今回はユーザの位置の色を変えるようにswitchで分岐させ設定しています。


あとは前回と同じようにOpenCVで表示するだけです。
次回はスケルトントラッキングか、三次元復元のどちらかをやってみようと思います。

0 件のコメント:

コメントを投稿