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

查志強發表於2016-08-02

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

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

zouxy09@qq.com

http://blog.csdn.net/zouxy09

 

我的Kinect開發平臺是:

Win7x86 + VS2010 + Kinect for Windows SDK v1.6 + OpenCV2.3.0

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

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

 

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

 

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

 

一、程式設計前期分析

      我們在上一文中提到的是不帶遊戲者ID的深度資料的提取,具體見下面:

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

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

       首先,Kinect感測器核心是發射紅外結構光,並探測紅外光反射,從而可以計算出視場範圍內每一個畫素的深度值。從深度資料中最先提取出來的是物體主體和形狀,以及每一個畫素點的遊戲者索引資訊。然後用這些形狀資訊來匹配人體的各個部分,最後計算匹配出來的各個關節在人體中的位置。而Kinect具有一次識別多達6個遊戲者的能力,並能跟蹤最多兩個人的骨骼(對於XBOX360來說,就是可以同時兩個人玩遊戲了)。

      可能有點奇怪哦,這一個帶遊戲者ID,一個不帶,還得那麼嚴肅地給它單獨開一文來學習。究竟啥來頭啊。呵呵,實際上,既然微軟提供了這種差別,那麼它的存在肯定是有意義的,所謂存在即合理嘛。多個選擇嘛。需要用到遊戲者ID的時候就用,不需要的時候就不用費那麼大勁。也不能說費勁,就是使用遊戲者ID的時候,我們需要再做一些工作,去把不同遊戲者的輪廓找出來,然後為了區別,標上不同的顏色,這就是本文想實現的。有點囉嗦了。


      上一文中,我們講到,Kinect的深度影象資料有兩種格式,一種是帶遊戲者ID的,一種是不帶的。兩種格式都是用兩個位元組來儲存一個畫素的深度值,而兩方式的差別在於:

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

(2)既表示深度值又含有遊戲者ID:Kinect為每一個追蹤到的遊戲者編號作為索引。而這個方式中,畫素值的高13位儲存了深度值,低三位儲存使用者序號,7 (0000 0111)這個位掩碼能夠幫助我們從深度資料中獲取到遊戲者索引值。

       要注意的是,不要對特定的遊戲者索引位進行編碼,因為他們是會變化的。實際的遊戲者索引位並不總是和Kinect前面的遊戲者編號一致。啥意思呢?例如,Kinect視野中只有一個遊戲者,但是返回的遊戲者索引位值可能是3或者4。也就是說有時候第一個遊戲者的遊戲者索引位可能不是1。還有,如果走進Kinect視野再走出去,然後再走進來,雖然你還是你,但是Kinect給你的索引ID可能就和原來的不一樣了,例如之前返回的索引位是1,走出去後再次走進,可能索引位變為其他值了。所以開發Kinect應用程式的時候應該注意到這一點。

      說得有點亂哦,我們們還是看程式碼吧。

 

二、程式碼與註釋

[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. //處理深度資料的每一個畫素,如果屬於同一個使用者的ID,那麼畫素就標為同種顏色,不同的使用者,  
  10. //其ID不一樣,顏色的標示也不一樣,如果不屬於某個使用者的畫素,那麼就採用原來的深度值  
  11. RGBQUAD shortDepth2RGBquad( USHORT depthID )  
  12. {   
  13.     //每畫素共16bit的資訊,其中最低3位是ID(所捕捉到的人的ID),剩下的13位才是資訊  
  14.     USHORT realDepth = (depthID & 0xfff8) >> 3; //提取距離資訊,高13位   
  15.     USHORT player =  depthID & 0x07 ;  //提取ID資訊 ,低3位  
  16.    
  17.     //因為提取的資訊是距離資訊,為了便於顯示,這裡歸一化為0-255  
  18.     BYTE depth = 255 - (BYTE)(256*realDepth/0x0fff);   
  19.    
  20.     RGBQUAD q;   
  21.     q.rgbRed = q.rgbBlue = q.rgbGreen = 0;   
  22.    
  23.     //RGB三個通道的值都是相等的話,就是灰度的  
  24.     //Kinect系統能夠處理辨識感測器前多至6個人物的資訊,但同一時刻最多隻有2個玩家可被追蹤(即骨骼跟蹤)  
  25.     switch( player )   
  26.     {   
  27.         case 0:    
  28.             q.rgbRed = depth / 2;   
  29.             q.rgbBlue = depth / 2;   
  30.             q.rgbGreen = depth / 2;   
  31.             break;   
  32.         case 1:   
  33.             q.rgbRed = depth;   
  34.             break;   
  35.         case 2:   
  36.             q.rgbGreen = depth;    
  37.             break;   
  38.         case 3:   
  39.             q.rgbRed = depth / 4;   
  40.             q.rgbGreen = depth;   
  41.             q.rgbBlue = depth;   
  42.             break;   
  43.         case 4:   
  44.             q.rgbRed = depth;   
  45.             q.rgbGreen = depth;   
  46.             q.rgbBlue = depth / 4;   
  47.             break;   
  48.         case 5:   
  49.             q.rgbRed = depth;   
  50.             q.rgbGreen = depth / 4;   
  51.             q.rgbBlue = depth;   
  52.             break;   
  53.         case 6:   
  54.             q.rgbRed = depth / 2;   
  55.             q.rgbGreen = depth / 2;   
  56.             q.rgbBlue = depth;   
  57.             break;   
  58.         case 7:   
  59.             q.rgbRed = 255 - ( depth / 2 );   
  60.             q.rgbGreen = 255 - ( depth / 2 );   
  61.             q.rgbBlue = 255 - ( depth / 2 );   
  62.         }   
  63.    
  64.     return q;   
  65. }  
  66.   
  67. int main(int argc, char *argv[])  
  68. {  
  69.     Mat image;  
  70.     image.create(240, 320, CV_8UC3);   
  71.    
  72.     //1、初始化NUI,注意這裡是DEPTH_AND_PLAYER_INDEX  
  73.     HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX);   
  74.     if (FAILED(hr))   
  75.     {   
  76.         cout<<"NuiInitialize failed"<<endl;   
  77.         return hr;   
  78.     }   
  79.   
  80.     //2、定義事件控制程式碼   
  81.     //建立讀取下一幀的訊號事件控制程式碼,控制KINECT是否可以開始讀取下一幀資料  
  82.     HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );  
  83.     HANDLE depthStreamHandle = NULL; //儲存影象資料流的控制程式碼,用以提取資料   
  84.    
  85.     //3、開啟KINECT裝置的彩色圖資訊通道,並用depthStreamHandle儲存該流的控制程式碼,以便於以後讀取  
  86.     hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH_AND_PLAYER_INDEX, NUI_IMAGE_RESOLUTION_320x240,   
  87.                             0, 2, nextColorFrameEvent, &depthStreamHandle);   
  88.     if( FAILED( hr ) )//判斷是否提取正確   
  89.     {   
  90.         cout<<"Could not open color image stream video"<<endl;   
  91.         NuiShutdown();   
  92.         return hr;   
  93.     }  
  94.     namedWindow("depthImage", CV_WINDOW_AUTOSIZE);  
  95.    
  96.     //4、開始讀取深度資料   
  97.     while(1)   
  98.     {   
  99.         const NUI_IMAGE_FRAME * pImageFrame = NULL;   
  100.   
  101.         //4.1、無限等待新的資料,等到後返回  
  102.         if (WaitForSingleObject(nextColorFrameEvent, INFINITE)==0)   
  103.         {   
  104.             //4.2、從剛才開啟資料流的流控制程式碼中得到該幀資料,讀取到的資料地址存於pImageFrame  
  105.             hr = NuiImageStreamGetNextFrame(depthStreamHandle, 0, &pImageFrame);   
  106.             if (FAILED(hr))  
  107.             {  
  108.                 cout<<"Could not get depth image"<<endl;   
  109.                 NuiShutdown();  
  110.                 return -1;  
  111.             }  
  112.   
  113.             INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;  
  114.             NUI_LOCKED_RECT LockedRect;  
  115.   
  116.             //4.3、提取資料幀到LockedRect,它包括兩個資料物件:pitch每行位元組數,pBits第一個位元組地址  
  117.             //並鎖定資料,這樣當我們讀資料的時候,kinect就不會去修改它  
  118.             pTexture->LockRect(0, &LockedRect, NULL, 0);   
  119.             //4.4、確認獲得的資料是否有效  
  120.             if( LockedRect.Pitch != 0 )   
  121.             {   
  122.                 //4.5、將資料轉換為OpenCV的Mat格式  
  123.                 for (int i=0; i<image.rows; i++)   
  124.                 {  
  125.                     uchar *ptr = image.ptr<uchar>(i);  //第i行的指標  
  126.                       
  127.                     //其二是既表示深度值又含有人物序號,則畫素值的高13位儲存了深度值,低三位儲存使用者序號,  
  128.                     //注意這裡需要轉換,因為每個資料是2個位元組,儲存的同上面的顏色資訊不一樣,  
  129.                     uchar *pBufferRun = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;  
  130.                     USHORT * pBuffer = (USHORT*) pBufferRun;  
  131.                        
  132.                     for (int j=0; j<image.cols; j++)   
  133.                     {  
  134.                         //對於每一個畫素,我們通過它的深度資料去修改它的RGB值;  
  135.                         RGBQUAD rgb = shortDepth2RGBquad(pBuffer[j]);  
  136.                         ptr[3*j] = rgb.rgbBlue;   
  137.                         ptr[3*j+1] = rgb.rgbGreen;   
  138.                         ptr[3*j+2] = rgb.rgbRed;   
  139.                     }   
  140.                 }   
  141.                 imshow("depthImage", image); //顯示影象   
  142.             }   
  143.             else   
  144.             {   
  145.                 cout<<"Buffer length of received texture is bogus\r\n"<<endl;   
  146.             }  
  147.   
  148.             //5、這幀已經處理完了,所以將其解鎖  
  149.             pTexture->UnlockRect(0);  
  150.             //6、釋放本幀資料,準備迎接下一幀   
  151.             NuiImageStreamReleaseFrame(depthStreamHandle, pImageFrame );   
  152.         }   
  153.         if (cvWaitKey(20) == 27)   
  154.             break;   
  155.     }   
  156.     //7、關閉NUI連結   
  157.     NuiShutdown();   
  158.     return 0;  
  159. }  

三、程式碼解析

       首先,這裡基本上和上一文說的深度資料的獲取的流程和API都是一樣的,具體的話,參考上一文。只是有幾個點需要說明下:

(1)初始化和開啟深度資料流的時候傳入的引數是不同的,這個需要注意下,我們需要的是DEPTH_AND_PLAYER_INDEX資料;

(2)每個畫素的深度資料由兩個位元組來儲存,高13位儲存了深度值,低三位儲存使用者序號。

(3)具體顯示的時候我們是這樣處理的:

       對於每一個畫素,我們通過它的深度資料去修改它的RGB值,如果屬於同一個使用者的ID,那麼畫素就標為同種顏色,不同的使用者,其ID不一樣,顏色的標示也不一樣,如果不屬於某個使用者的畫素,那麼就採用原來的深度值。

首先,這裡涉及到了:

USHORTrealDepth = (depthID & 0xfff8) >> 3; //提取距離資訊,高13位

USHORTplayer =  depthID & 0x07 ;  //提取ID資訊,低3位

然後RGBQUAD是一個結構體,其儲存一個畫素點的RGB值,定義如下:

typedef struct tagRGBQUAD {

 BYTE    rgbBlue;

 BYTE    rgbGreen;

 BYTE    rgbRed;

 BYTE    rgbReserved;

} RGBQUAD;

 

      至此,目標達成。

     下面是結果,感覺似乎如果兩個人靠得太近的話,也會被識別為同一個使用者,標示同樣的顏色,這點感覺有點不太穩定,這種情況應該挺容易避免的啊,是我高估了Kinect,還是我高估了我。


相關文章