Kinect学习(三):获取RGB颜色数据
前言
在前面的文章中介紹了如何搭建Kinect開發環境:Kinect學習(一):開發環境搭建。搭建好環境后,首先要做的當然就是試著讀取Kinect中的數據了。
Kinect有三個鏡頭,中間的是RGB攝像頭,左邊的是紅外線發射器,右邊的是紅外線CMOS攝像頭構成的3D結構光攝像頭,用來采集深度數據。彩色攝像頭最大支持1280*960分辨率成像,紅外攝像頭最大支持640*480成像。
接下來就要通過微軟提供的SDK來讀取Kinect中的彩色攝像頭的數據了。
代碼
先上代碼,里面有注釋,后面再詳細介紹。
#include <windows.h> #include <NuiApi.h> #include <iostream> #include <opencv2/opencv.hpp>/* 幾個常用的頭文件: 1、NuiApi.h ---包含所有的NUI(自然用戶界面) API頭文件和定義基本的初始化和函數訪問入口。這是我們C++工程的主要頭文件,它已經包含了NuiImageCamera.h 和 NuiSkeleton.h。 2、NuiImageCamera.h ---定義了圖像和攝像頭服務的API,包括調整攝像頭的角度和仰角,打開數據流和讀取數據流等。 3、NuiSkeleton.h ---骨架有關的API,包括使能骨架跟蹤,獲取骨架數據,骨架數據轉換和平滑渲染等。 4、NuiSensor.h ---音頻API,包括ISoundSourceLocalizer接口,用于返回聲源的方向(波束形成)和音頻的位置。 */using namespace std; using namespace cv;int main(int argc, char* argv[]) {cv::Mat img;img.create(480, 640, CV_8UC3);//1、初始化NUIHRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);if (FAILED(hr)){cout << "NuiInitialize failed" << endl;return hr;}//2、定義事件句柄//創建讀取下一幀的信號事件句柄,控制KINECT是否可以開始讀取下一幀數據HANDLE nextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);HANDLE colorStreamHandle = NULL;//保存圖像數據流的句柄,用于提取數據//3、打開KINECT設備的彩色信息通道,并用colorStreamHandle保存該流的句柄,以便于以后讀取hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, nextColorFrameEvent, &colorStreamHandle);if (FAILED(hr)){cout << "Could not open color image stream video" << endl;NuiShutdown();return hr;}cv::namedWindow("colorImage", CV_WINDOW_AUTOSIZE);//4、開始讀取彩色圖數據while (1){const NUI_IMAGE_FRAME * pImageFrame = NULL;//4.1、無線等待新的數據,等到就返回if (WaitForSingleObject(nextColorFrameEvent, INFINITE) == 0){//4.2、從剛才打開數據流的流句柄中得到該幀的數據,讀取到的數據地址存于pImageFrame中hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);if (FAILED(hr)){cout << "Could not get color 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 < img.rows; i++){uchar *ptr = img.ptr<uchar>(i); //第i行的指針//每個字節代表一個顏色信息,直接使用ucharuchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;for (int j = 0;j < img.cols;j++){//內部數據是4個字節,0-1-2是BGR,第4個現在未使用ptr[3 * j] = pBuffer[4 * j];ptr[3 * j + 1] = pBuffer[4 * j + 1];ptr[3 * j + 2] = pBuffer[4 * j + 2];}}cv::imshow("colorImage", img); //顯示圖像}else{cout << "Buffer length of received texture is bogus\r\n" << endl;}//5、這幀已經處理完了,所以將其解鎖pTexture->UnlockRect(0);//6、釋放本幀數據,準備獲取下一幀NuiImageStreamReleaseFrame(colorStreamHandle, pImageFrame);}if (cv::waitKey(20) == 27)break;}//7、關閉NUI連接NuiShutdown();return 0; }運行結果
說明
整個程序可以分為以下流程:
1、初始化NUI接口
//1、初始化NUI HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);要使用微軟提供的SDK中的SDK來操作Kinect,必須先調用NUI初始化函數。
函數原型為:
dwFlags表示標志位,有以下幾種情況:
- NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX: 提供帶用戶信息的深度圖數據;
- NUI_INITIALIZE_FLAG_USES_COLOR:提供RGB彩色圖像數據;
- NUI_INITIALIZE_FLAG_USES_SKELETON:提供骨骼點數據;
- NUI_INITIALIZE_FLAG_USES_DEPTH:提供深度圖像數據;
- NUI_INITIALIZE_FLAG_USES_AUDIO:提供聲音數據;
- NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD:初始化默認的硬件線程;
以上的都各自對應一個標志位,使用時可以使用|將它們組合起來。
注意到,它還返回了一個HRESULT類型的參數,通過它可以判斷初始化函數是否執行成功。
if (FAILED(hr)) {cout << "NuiInitialize failed" << endl;return hr; }或者判斷是否等于S_OK:
if(hr == S_OK) {cout << "NuiInitialize successfully" << endl; }2、定義事件句柄
//2、定義事件句柄 //創建讀取下一幀的信號事件句柄,控制KINECT是否可以開始讀取下一幀數據 HANDLE nextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);該函數會創建一個windows事件對象,創建成功則返回事件的句柄。這里的這個事件是用來判斷是否有新數據的。
其中有四個參數:
- 第一個是安全屬性,設定為NULL的安全描述符;
- 第二個表示設置信號復位方式為自動恢復為無信號狀態(FALSE)還是手動恢復為無信號狀態(TRUE),設為TRUE,因為后面應用程序會重置事件消息;
- 第三個是事件消息初始狀態的布爾值,為FALSE;
最后一個是信號名稱,可以直接設置為NULL;
3、打開Kinect設備的彩色圖像數據流
使用這個函數可以打開Kinect設備的彩色圖或是深度圖的訪問通道。也可以理解為,創建一個訪問彩色圖或深度圖的數據流。
函數原型:
_Check_return_ HRESULT NUIAPI NuiImageStreamOpen(_In_ NUI_IMAGE_TYPE eImageType,_In_ NUI_IMAGE_RESOLUTION eResolution,_In_ DWORD dwImageFrameFlags,_In_ DWORD dwFrameLimit,_In_opt_ HANDLE hNextFrameEvent,_Out_ HANDLE *phStreamHandle);參數說明:
4、等待數據更新,若更新完成則進行下一步;
//4.1、無線等待新的數據,等到就返回 if (WaitForSingleObject(nextColorFrameEvent, INFINITE) == 0) { ... }前面也提到了這個函數,如果事件(對應nextColorFrameEvent)有信號,即有數據,那么返回值為0,程序也會往下執行;如果沒有數據,就會等待。函數的第二個參數表示等待時間,單位為ms,這里設為INFINITE,表示一直等待。
5、從數據流中拿出圖像數據;
//4.2、從剛才打開數據流的流句柄中得到該幀的數據,讀取到的數據地址存于pImageFrame中 hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);colorStreamHandle為前面保存了Kinect設備的彩色信息通道的句柄,這個函數會從colorStreamHandle中取出RGB圖像數據,并保存在pImageFrame中。第二個參數,表示延時多久獲取數據,直接取為0,就是不等待直接取數據。
成功調用完這個函數之后,從Kinect捕獲到的一幀圖像,會保存在一個NUI_IMAGE_FRAME結構體中,pImageFrame為指向那個結構體的指針,其中包含了很多信息,如:圖像類型,分辨率,圖像緩沖區,時間戳等等。
6、提取數據幀并鎖定數據;
INuiFrameTexture * pTexture = pImageFrame->pFrameTexture; NUI_LOCKED_RECT LockedRect;//4.3、提取數據幀到LockedRect(它包括兩個數據對象:pitch每行字節數,pBits第一個字節地址) //并鎖定數據,這樣當我們讀取數據的時候,kinect就不會去修改它 pTexture->LockRect(0, &LockedRect, NULL, 0);INuiFrameTexture是一個保存圖像幀數據的對象,主要要用到他的下面兩個共有方法:
- LockRect:給緩沖區上鎖;
- UnlockRect:給緩沖區解鎖;
因為圖像幀是保存在緩沖區的,如果不上鎖的話,緩沖區中還有的圖像可能會導致Kinect修改要取出的圖像。
提取數據幀到LockedRect后,它包含兩個數據對象:pitch,每行字節數;pBits,第一個字節地址。
7、將數據轉換為OpenCV的Mat格式。
//4.4、確認獲得的數據是否有效 if (LockedRect.Pitch != 0) {//4.5、將數據轉換為OpenCV的Mat格式for (int i = 0; i < img.rows; i++){uchar *ptr = img.ptr<uchar>(i); //第i行的指針//每個字節代表一個顏色信息,直接使用ucharuchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;for (int j = 0;j < img.cols;j++){//內部數據是4個字節,0-1-2是BGR,第4個現在未使用ptr[3 * j] = pBuffer[4 * j];ptr[3 * j + 1] = pBuffer[4 * j + 1];ptr[3 * j + 2] = pBuffer[4 * j + 2];}}這一部分沒什么說的了,就是把LockedRect中的數據取出來,保存為OpenCV支持的Mat格式。
參考資料
后記
這個筆記總體來說不難,主要是套路,微軟官網的文檔早就撤了,畢竟用的還是Kinect v1.0的,靠著博客和看看源碼大概還能用用。
前段時間直到最近感覺都挺多事情的,很多筆記和寫好的代碼都沒時間去整理,還要加把勁了。這段時間又有世界杯,熬夜看球什么的真的挺“傷”的。
總結
以上是生活随笔為你收集整理的Kinect学习(三):获取RGB颜色数据的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kinect学习(二):学习资源整理(转
- 下一篇: Kinect学习(四):提取深度数据