Kinect開發學習筆記之(五)不帶遊戲者ID的深度資料的提取
【原文:http://blog.csdn.net/zouxy09/article/details/8146719】
Kinect開發學習筆記之(五)不帶遊戲者ID的深度資料的提取
我的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中深度值最大為4096mm,0值通常表示深度值不能確定,一般應該將0值過濾掉。微軟建議在開發中使用1220mm~3810mm範圍內的值。在進行其他深度影象處理之前,應該使用閾值方法過濾深度資料至1220mm-3810mm這一範圍內。
下圖顯示了Kinect Sensor的感知範圍,其中的default range對Xbox 360和Kinect for Windows都適用,而near range僅對後者適用:
深度資料的儲存:
Kinect的深度影象資料含有兩種格式,兩種格式都是用兩個位元組來儲存一個畫素的深度值,而兩方式的差別在於:
(1)唯一表示深度值:那麼畫素的低12位表示一個深度值,高4位未使用;
(2)既表示深度值又含有遊戲者ID:Kinect SDK具有分析深度資料和探測人體或者遊戲者輪廓的功能,它一次能夠識別多達6個遊戲者。SDK為每一個追蹤到的遊戲者編號作為索引。而這個方式中,畫素值的高13位儲存了深度值,低三位儲存使用者序號,7 (0000 0111)這個位掩碼能夠幫助我們從深度資料中獲取到遊戲者索引值(這個程式設計將在下一節)。
應用程式可以使用深度資料流中的深度資料來支援各種各樣的使用者特性,如追蹤使用者的運動並在程式中識別和忽略背景物體的資訊等。
二、程式碼與註釋
- #include <windows.h>
- #include <iostream>
- #include <NuiApi.h>
- #include <opencv2/opencv.hpp>
- using namespace std;
- using namespace cv;
- int main(int argc, char *argv[])
- {
- Mat image;
- //這裡我們用灰度圖來表述深度資料,越遠的資料越暗。
- image.create(240, 320, CV_8UC1);
- //1、初始化NUI,注意:這裡傳入的引數就不一樣了,是DEPTH
- HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_DEPTH);
- if (FAILED(hr))
- {
- cout<<"NuiInitialize failed"<<endl;
- return hr;
- }
- //2、定義事件控制程式碼
- //建立讀取下一幀的訊號事件控制程式碼,控制KINECT是否可以開始讀取下一幀資料
- HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
- HANDLE depthStreamHandle = NULL; //儲存影象資料流的控制程式碼,用以提取資料
- //3、開啟KINECT裝置的深度圖資訊通道,並用depthStreamHandle儲存該流的控制程式碼,以便於以後讀取
- hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH, NUI_IMAGE_RESOLUTION_320x240,
- 0, 2, nextColorFrameEvent, &depthStreamHandle);
- if( FAILED( hr ) )//判斷是否提取正確
- {
- cout<<"Could not open color image stream video"<<endl;
- NuiShutdown();
- return hr;
- }
- namedWindow("depthImage", CV_WINDOW_AUTOSIZE);
- //4、開始讀取深度資料
- while(1)
- {
- const NUI_IMAGE_FRAME * pImageFrame = NULL;
- //4.1、無限等待新的資料,等到後返回
- if (WaitForSingleObject(nextColorFrameEvent, INFINITE)==0)
- {
- //4.2、從剛才開啟資料流的流控制程式碼中得到該幀資料,讀取到的資料地址存於pImageFrame
- hr = NuiImageStreamGetNextFrame(depthStreamHandle, 0, &pImageFrame);
- if (FAILED(hr))
- {
- cout<<"Could not get depth image"<<endl;
- NuiShutdown();
- return -1;
- }
- INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;
- NUI_LOCKED_RECT LockedRect;
- //4.3、提取資料幀到LockedRect,它包括兩個資料物件:pitch每行位元組數,pBits第一個位元組地址
- //並鎖定資料,這樣當我們讀資料的時候,kinect就不會去修改它
- pTexture->LockRect(0, &LockedRect, NULL, 0);
- //4.4、確認獲得的資料是否有效
- if( LockedRect.Pitch != 0 )
- {
- //4.5、將資料轉換為OpenCV的Mat格式
- for (int i=0; i<image.rows; i++)
- {
- uchar *ptr = image.ptr<uchar>(i); //第i行的指標
- //深度影象資料含有兩種格式,這裡畫素的低12位表示一個深度值,高4位未使用;
- //注意這裡需要轉換,因為每個資料是2個位元組,儲存的同上面的顏色資訊不一樣,
- uchar *pBufferRun = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
- USHORT * pBuffer = (USHORT*) pBufferRun;
- for (int j=0; j<image.cols; j++)
- {
- ptr[j] = 255 - (uchar)(256 * pBuffer[j]/0x0fff); //直接將資料歸一化處理
- }
- }
- imshow("depthImage", image); //顯示影象
- }
- else
- {
- cout<<"Buffer length of received texture is bogus\r\n"<<endl;
- }
- //5、這幀已經處理完了,所以將其解鎖
- pTexture->UnlockRect(0);
- //6、釋放本幀資料,準備迎接下一幀
- NuiImageStreamReleaseFrame(depthStreamHandle, pImageFrame );
- }
- if (cvWaitKey(20) == 27)
- break;
- }
- //7、關閉NUI連結
- NuiShutdown();
- return 0;
- }
三、程式碼解析
首先,這裡基本上和彩色影象資料的獲取的流程和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再增加,就會導致陣列訪問越界了。導致的結果是,我們的深度影象顯示都一半的時候,程式就死掉了。
至此,目標達成。
相關文章
- Kinect開發學習筆記之(六)帶遊戲者ID的深度資料的提取筆記遊戲
- Kinect開發學習筆記之(七)骨骼資料的提取筆記
- Kinect開發學習筆記之(二)Kinect開發學習資源整理筆記
- Kinect開發學習筆記之(四)提取顏色資料並用OpenCV顯示筆記OpenCV
- Kinect開發學習筆記之(三)Kinect開發環境配置筆記開發環境
- Kinect開發學習筆記之(八)彩色、深度、骨骼和使用者摳圖結合筆記
- Kinect開發學習筆記之(一)Kinect介紹和應用筆記
- 大資料學習筆記(五)大資料筆記
- android學習筆記五Android筆記
- Android 開發學習筆記Android筆記
- Android學習筆記(五)——FragmentAndroid筆記Fragment
- 《Android藝術開發探索》學習筆記之View的工作原理Android筆記View
- 《Android藝術開發探索》學習筆記之IPCAndroid筆記
- 遊戲資料的捕捉(鬱金香學習筆記)遊戲筆記
- 深度學習讀書筆記之RBM深度學習筆記
- Android 學習筆記五:支援不同的裝置Android筆記
- 《Android藝術開發探索》學習筆記之Activity的生命週期Android筆記
- 深度學習——loss函式的學習筆記深度學習函式筆記
- hive學習筆記之五:分桶Hive筆記
- TypeScript學習筆記之五類(Class)TypeScript筆記
- Delphi5學習筆記之五筆記
- Dubbo 學習筆記(五) 開發環境常用技巧筆記開發環境
- Python深度學習(處理文字資料)--學習筆記(十二)Python深度學習筆記
- 《Android藝術開發探索》學習筆記之View的事件體系(一)Android筆記View事件
- 飛機的 PHP 學習筆記之資料庫篇PHP筆記資料庫
- 學習筆記:深度學習中的正則化筆記深度學習
- Java IO學習筆記一:為什麼帶Buffer的比不帶Buffer的快Java筆記
- swoft 學習筆記之資料庫操作筆記資料庫
- 大資料之 Hadoop學習筆記大資料Hadoop筆記
- 安卓開發學習-Intent攜帶資料安卓Intent
- 深度學習keras筆記深度學習Keras筆記
- 深度學習 筆記一深度學習筆記
- laravel學習筆記之開發環境搭建Laravel筆記開發環境
- Django之“學習筆記”網站開發1Django筆記網站
- Open CV 學習開發筆記之概述(一)筆記
- Deep Learning(深度學習)學習筆記整理系列之(一)深度學習筆記
- Android學習筆記之IntentAndroid筆記Intent
- 【深度學習】大牛的《深度學習》筆記,Deep Learning速成教程深度學習筆記