Kinect開發學習筆記之(五)不帶遊戲者ID的深度資料的提取

查志強發表於2016-08-02

【原文:http://blog.csdn.net/zouxy09/article/details/8146719

Kinect開發學習筆記之(五)不帶遊戲者ID的深度資料的提取

zouxy09@qq.com

http://blog.csdn.net/zouxy09

 

我的Kinect開發平臺是:

Win7 x86 + VS2010 + Kinect for Windows SDK v1.6 + OpenCV2.3.0

開發環境的搭建見上一文:

http://blog.csdn.net/zouxy09/article/details/8146055

 

本學習筆記以下面的方式組織:程式設計前期分析、程式碼與註釋和重要程式碼解析三部分。

 

要實現目標:通過微軟的SDK提取不帶遊戲者ID的深度資料並用OpenCV顯示

 

一、程式設計前期分析

      深度資料的獲取和彩色影象資料的獲取基本上是一樣的,所以關於採集過程就不贅述了,具體見:

Kinect開發學習筆記之(四)提取顏色資料並用OpenCV顯示

http://blog.csdn.net/zouxy09/article/details/8146266

       這裡需要了解下深度資料:

       深度資料流所提供的影象幀中,每一個畫素點代表的是在深度感應器的視野中,該特定的(x, y)座標處物體到離攝像頭平面最近的物體到該平面的距離(以毫米為單位)。

        Kinect中深度值最大為4096mm0值通常表示深度值不能確定,一般應該將0值過濾掉。微軟建議在開發中使用1220mm~3810mm範圍內的值。在進行其他深度影象處理之前,應該使用閾值方法過濾深度資料至1220mm-3810mm這一範圍內。

       下圖顯示了Kinect Sensor的感知範圍,其中的default rangeXbox 360Kinect for Windows都適用,而near range僅對後者適用:

深度資料的儲存:

       Kinect的深度影象資料含有兩種格式,兩種格式都是用兩個位元組來儲存一個畫素的深度值,而兩方式的差別在於:

(1)唯一表示深度值:那麼畫素的低12位表示一個深度值,高4位未使用;

(2)既表示深度值又含有遊戲者ID:Kinect SDK具有分析深度資料和探測人體或者遊戲者輪廓的功能,它一次能夠識別多達6個遊戲者。SDK為每一個追蹤到的遊戲者編號作為索引。而這個方式中,畫素值的高13位儲存了深度值,低三位儲存使用者序號,7 (0000 0111)這個位掩碼能夠幫助我們從深度資料中獲取到遊戲者索引值(這個程式設計將在下一節)。

        應用程式可以使用深度資料流中的深度資料來支援各種各樣的使用者特性,如追蹤使用者的運動並在程式中識別和忽略背景物體的資訊等。

 

二、程式碼與註釋

[cpp] view plain copy
  1. #include <windows.h>  
  2. #include <iostream>   
  3. #include <NuiApi.h>  
  4. #include <opencv2/opencv.hpp>  
  5.   
  6. using namespace std;  
  7. using namespace cv;  
  8.   
  9. int main(int argc, char *argv[])  
  10. {  
  11.     Mat image;  
  12.     //這裡我們用灰度圖來表述深度資料,越遠的資料越暗。  
  13.     image.create(240, 320, CV_8UC1);   
  14.    
  15.     //1、初始化NUI,注意:這裡傳入的引數就不一樣了,是DEPTH  
  16.     HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH);   
  17.     if (FAILED(hr))   
  18.     {   
  19.         cout<<"NuiInitialize failed"<<endl;   
  20.         return hr;   
  21.     }   
  22.   
  23.     //2、定義事件控制程式碼   
  24.     //建立讀取下一幀的訊號事件控制程式碼,控制KINECT是否可以開始讀取下一幀資料  
  25.     HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );  
  26.     HANDLE depthStreamHandle = NULL; //儲存影象資料流的控制程式碼,用以提取資料   
  27.    
  28.     //3、開啟KINECT裝置的深度圖資訊通道,並用depthStreamHandle儲存該流的控制程式碼,以便於以後讀取  
  29.     hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH, NUI_IMAGE_RESOLUTION_320x240,   
  30.                             0, 2, nextColorFrameEvent, &depthStreamHandle);   
  31.     if( FAILED( hr ) )//判斷是否提取正確   
  32.     {   
  33.         cout<<"Could not open color image stream video"<<endl;   
  34.         NuiShutdown();   
  35.         return hr;   
  36.     }  
  37.     namedWindow("depthImage", CV_WINDOW_AUTOSIZE);  
  38.    
  39.     //4、開始讀取深度資料   
  40.     while(1)   
  41.     {   
  42.         const NUI_IMAGE_FRAME * pImageFrame = NULL;   
  43.   
  44.         //4.1、無限等待新的資料,等到後返回  
  45.         if (WaitForSingleObject(nextColorFrameEvent, INFINITE)==0)   
  46.         {   
  47.             //4.2、從剛才開啟資料流的流控制程式碼中得到該幀資料,讀取到的資料地址存於pImageFrame  
  48.             hr = NuiImageStreamGetNextFrame(depthStreamHandle, 0, &pImageFrame);   
  49.             if (FAILED(hr))  
  50.             {  
  51.                 cout<<"Could not get depth image"<<endl;   
  52.                 NuiShutdown();  
  53.                 return -1;  
  54.             }  
  55.   
  56.             INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;  
  57.             NUI_LOCKED_RECT LockedRect;  
  58.   
  59.             //4.3、提取資料幀到LockedRect,它包括兩個資料物件:pitch每行位元組數,pBits第一個位元組地址  
  60.             //並鎖定資料,這樣當我們讀資料的時候,kinect就不會去修改它  
  61.             pTexture->LockRect(0, &LockedRect, NULL, 0);   
  62.             //4.4、確認獲得的資料是否有效  
  63.             if( LockedRect.Pitch != 0 )   
  64.             {   
  65.                 //4.5、將資料轉換為OpenCV的Mat格式  
  66.                 for (int i=0; i<image.rows; i++)   
  67.                 {  
  68.                     uchar *ptr = image.ptr<uchar>(i);  //第i行的指標  
  69.                       
  70.                     //深度影象資料含有兩種格式,這裡畫素的低12位表示一個深度值,高4位未使用;  
  71.                     //注意這裡需要轉換,因為每個資料是2個位元組,儲存的同上面的顏色資訊不一樣,  
  72.                     uchar *pBufferRun = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;  
  73.                     USHORT * pBuffer = (USHORT*) pBufferRun;  
  74.                        
  75.                     for (int j=0; j<image.cols; j++)   
  76.                     {   
  77.                         ptr[j] = 255 - (uchar)(256 * pBuffer[j]/0x0fff);  //直接將資料歸一化處理  
  78.                     }   
  79.                 }   
  80.                 imshow("depthImage", image); //顯示影象   
  81.             }   
  82.             else   
  83.             {   
  84.                 cout<<"Buffer length of received texture is bogus\r\n"<<endl;   
  85.             }  
  86.   
  87.             //5、這幀已經處理完了,所以將其解鎖  
  88.             pTexture->UnlockRect(0);  
  89.             //6、釋放本幀資料,準備迎接下一幀   
  90.             NuiImageStreamReleaseFrame(depthStreamHandle, pImageFrame );   
  91.         }   
  92.         if (cvWaitKey(20) == 27)   
  93.             break;   
  94.     }   
  95.     //7、關閉NUI連結   
  96.     NuiShutdown();   
  97.     return 0;  
  98. }  

 

三、程式碼解析

      首先,這裡基本上和彩色影象資料的獲取的流程和API都是一樣的,只有有幾個不同點:

(1) 因為我們需要的是深度資料,所以在NuiInitialize(DWORD dwFlags);初始化時,應告知Kinect我要的是深度資料:NUI_INITIALIZE_FLAG_USES_DEPTH;

(2)我們需要開啟的是Kinect裝置的深度資料流,所以呼叫NuiImageStreamOpen傳入了型別是NUI_IMAGE_TYPE_DEPTH, 另外,深度影象的解析度一般用NUI_IMAGE_RESOLUTION_320x240。

(3)另外,Kinect的深度影象資料含有兩種格式,其一是唯一表示深度值:那麼畫素的低12位表示一個深度值,高4位未使用;其二是既表示深度值又含有遊戲者ID,則畫素值的高13位儲存了深度值,低三位儲存使用者序號,其中序號為0則表示無使用者,1和2分別表示兩個不同的使用者(這個會在下一文中程式設計)。

        所以這裡深度資料轉換為OpenCV的Mat資料型別就和彩色的不一樣了。在顯示中,我們通過一個單通道的灰度影象來描述深度影象,畫素點越白表示場景中目標裡攝像頭越近。

       轉換時候有一個地方需要特別注意的,程式碼裡面用的是:

uchar *pBufferRun = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;

USHORT * pBuffer = (USHORT*) pBufferRun;

       那為什麼不直接下面這樣呢:

USHORT * pBuffer = (USHORT*) (LockedRect.pBits) + i * LockedRect.Pitch;

       因為每個深度資料是2個位元組,儲存的同上面的顏色資訊不一樣,而pitch是以位元組為單位的,然後地址的偏移是按LockedRect.pBits的地址型別來偏移的。也就是說假如按第二種方式來編寫,那麼當i等於影象的中間那一行的時候,pBuffer指標已經指向影象的最後一行了(因為ushort是兩個位元組,每偏移一個i,地址就偏移兩個位元組),這時候i再增加,就會導致陣列訪問越界了。導致的結果是,我們的深度影象顯示都一半的時候,程式就死掉了。

 

至此,目標達成。


相關文章