【Kinect】1st-提取顏色代碼分析

提取顏色數據並用OpenCV顯示

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

#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(480, 640, CV_8UC3);

//1、初始化NUI 
HRESULT 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; 
}
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<image.rows; i++) 
            {
                uchar *ptr = image.ptr<uchar>(i);  //第i行的指針

                //每個字節代表一個顏色信息,直接使用uchar
                uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
                for (int j=0; j<image.cols; j++) 
                { 
                    ptr[3*j] = pBuffer[4*j];  //內部數據是4個字節,0-1-2是BGR,第4個現在未使用 
                    ptr[3*j+1] = pBuffer[4*j+1]; 
                    ptr[3*j+2] = pBuffer[4*j+2]; 
                } 
            } 
            imshow("colorImage", image); //顯示圖像 
        } 
        else 
        { 
            cout<<"Buffer length of received texture is bogus\r\n"<<endl; 
        }

        //5、這幀已經處理完了,所以將其解鎖
        pTexture->UnlockRect(0);
        //6、釋放本幀數據,準備迎接下一幀 
        NuiImageStreamReleaseFrame(colorStreamHandle, pImageFrame ); 
    } 
    if (cvWaitKey(20) == 27) 
        break; 
} 
//7、關閉NUI鏈接 
NuiShutdown(); 
return 0;
}

首先,對Kinect,我們必須要包含下面兩個頭文件:

#include <windows.h>

#include <NuiApi.h>

1、初始化NUI

HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR);

任何想使用微軟提供的API來操作KINECT,都必須在所有操作之前,調用NUI的初始化函數:
HRESULT NuiInitialize(DWORD dwFlags);

dwFlags參數是以標誌位的含義存在的。可以使用下面幾個值來指定你打算使用NUI中的哪些內容。

NUI_INITIALIZE_FLAG_USES_DEPTH_AND_PLAYER_INDEX 提供帶用戶信息的深度圖數據;
NUI_INITIALIZE_FLAG_USES_COLOR 提供色彩圖像數據;
NUI_INITIALIZE_FLAG_USES_SKELETON 提供骨骼點數據;
NUI_INITIALIZE_FLAG_USES_DEPTH 提供深度圖像數據.
NUI_INITIALIZE_FLAG_USES_AUDIO 提供聲音數據;
NUI_INITIALIZE_DEFAULT_HARDWARE_THREAD 初始化默認的硬件線程;

以上的標誌位,你可以使用一個,也可以用 | 操作符將它們組合在一起。

另外,Kinect提供了兩種處理返回值的方式,就是判斷上面的函數是否執行成功。

//這是一種處理返回值的方式
if( FAILED( hr ) )
  {
    cout<<"NuiInitialize failed"<<endl;
    return hr;
  }
//這是另一種處理返回值的方式
if(hr == S_OK)
  {
    cout<<"NuiInitialize successfully"<<endl;
  }

2、定義事件句柄

HANDLE nextColorFrameEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

CreateEvent()創建一個windows事件對象,創建成功則返回事件的句柄。事件有兩個狀態,有信號和沒有信號!上面說到了。就是拿來等待新數據的。

CreateEvent函數需要4個參數: 設定爲NULL的安全描述符; 一個設定爲true的布爾值,因爲應用程序將重置事件消息;
一個未指定的事件消息初始狀態的布爾值; 一個空字符串,因爲事件未命名。

HANDLE colorStreamHandle = NULL; //保存圖像數據流的句柄,用以提取數據

3、打開KINECT設備的彩色數據流

hr=NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480,0,2,nextColorFrameEvent,&colorStreamHandle);

我們使用這個函數來打開kinect彩色或者深度圖的訪問通道,當然,其內部原理是通過”流”來實現的,因此,你也可以把這個函數理解爲,創建一個訪問彩色或者深度圖的數據流。

參數:
eImageType [in]
這是一個 NUI_IMAGE_TYPE 枚舉類型的值,用來詳細指定你要創建的流類型。
比如你要打開彩色圖,就使用 NUI_IMAGE_TYPE_COLOR。 要打開深度圖,就使用 NUI_IMAGE_TYPE_DEPTH。
能打開的圖像類型,必須是你在初始化的時候指定過的。

eResolution [in]
這是一個 NUI_IMAGE_RESOLUTION 枚舉類型的值,用來指定你要以什麼分辨率來打開eImageType(參數1)中指定的圖像類別。
假如你在參數eImageType中指定的是彩色圖NUI_IMAGE_TYPE_COLOR,那麼可以選擇2種分辨率:
NUI_IMAGE_RESOLUTION_1280x1024,NUI_IMAGE_RESOLUTION_640x480
如果你在參數eImageType中指定的是深度圖NUI_IMAGE_TYPE_DEPTH,那麼可以選擇3種分辨率
NUI_IMAGE_RESOLUTION_640x480,
NUI_IMAGE_RESOLUTION_320x240,
NUI_IMAGE_RESOLUTION_80x60

dwImageFrameFlags_NotUsed
[in] 這是個無用參數,隨便給個整數就行了。

dwFrameLimit
指定NUI運行時環境將要爲你所打開的圖像類型建立幾個緩衝。最大值是NUI_IMAGE_STREAM_FRAME_LIMIT_MAXIMUM(當前版本爲 4)對於大多數啊程序來說,2就足夠了。

hNextFrameEvent
[in, optional] 一個用來手動重置信號是否可用的事件句柄(event),該信號用來控制KINECT是否可以開始讀取下一幀數據。也就是說在這裏指定一個句柄後,隨着程序往後繼續推進,當你在任何時候想要控制kinect讀取下一幀數據時,都應該先使用WaitForSingleObject判斷一下該句柄,判斷是否有數據可拿。

phStreamHandle
[out]出參,指定一個句柄的地址。函數成功執行後,將會創建對應的數據訪問通道(流),並且讓該句柄保存這個通道的地址。也就是說,如果現在創建成功了。那麼以後你想讀取數據,就要通過這個句柄了。

返回值
只有S_OK表示成功打開,錯誤原因卻有很多,比如打開一個沒初始化過的數據流;打開一個已被使用的數據流;參數phStreamHandle爲NULL等等。自己查閱API手冊吧。

4、無限等待新的數據,等到後返回

WaitForSingleObject(nextColorFrameEvent, INFINITE)==0

和剛纔說的一樣,程序運行都這裏,這個事件有信號,就是說有數據,那麼程序往下執行,如果沒有數據,就會等待。函數第二個參數表示你願意等多久,具體的數據的話就表示你願意等多少毫秒,還不來,我就不要了,繼續往下走。如果是INFINITE的話,就表示無限等待新數據,直到海枯石爛,一定等到爲止。等到有信號後就返回0 。

5、從數據流中拿數據

hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame);

從剛纔打開數據流的流句柄中得到該幀數據,讀取到的數據地址存於pImageFrame。第二個參數表示你延時多少微秒拿數據,0表示,我立刻拿。

如果你沒有遇到什麼錯誤的話,那麼剛纔KINECT就捕獲了一副畫面,並將該畫面的信息保存在一個NUI_IMAGE_FRAME結構中,pImageFrame指向該結構的地址。

pImageFrame包含了很多有用信息,包括:圖像類型,分辨率,圖像緩衝區,時間戳等等。

6、INuiFrameTexture接口

INuiFrameTexture * pTexture = pImageFrame->pFrameTexture;

一個容納圖像幀數據的對象,類似於Direct3D紋理,但是隻有一層(不支持mip-maping)。

其公有方法包含以下:

AddRef—增加一個對象上接口的引用數目;該方法在每複製一個指向該對象上接口的指針時都要調用一次;

BufferLen—獲得緩衝區的字節長度;

GetLevelDesc—獲得緩衝區的描述;

LockRect—給緩衝區上鎖;

Pitch—返回一行的字節數;

QueryInterface—獲取指向對象所支持的接口的指針,該方法對其所返回的指針調用AddRef函數;

Release—減少一個對象上接口的引用計數;

UnlockRect—對緩衝區解鎖;

7、提取數據幀到LockedRect並鎖定數據

pTexture->LockRect(0, &LockedRect, NULL, 0);

提取數據幀到LockedRect,它包括兩個數據對象:pitch每行字節數,pBits第一個字節地址。另外,其還鎖定數據,這樣當我們讀數據的時候,kinect就不會去修改它

好了,現在真正保存圖像的對象LockedRect我們已經有了,並且也將圖像信息寫入這個對象了。

8、將數據轉換爲OpenCV的Mat格式

然後我們就將其保存圖像的對象LockedRect的格式,轉化爲OpenCV的Mat格式,便於我們處理和顯示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章