Kinect V1读取图像数据(For Windows)
Kinect V1讀取圖像數據(For Windows)
- 這篇博客
- Kinect V1介紹
- 數據讀取的基本流程
- 運行代碼和注釋
- 結尾
這篇博客
?剛好有一臺現成的Kinect V1相機,所以就拿過來學習一下它的數據讀取方式和編程方法,畢竟它還能用于跑RGBD-SLAM。Kinect V1,沒錯就是微軟早就停產的那個相機,它仍然能戰斗!下面對相機進行簡單的介紹。
Kinect V1介紹
?Kinect V1相機的長相如下:
?Kinect V1由彩色攝像頭,深度傳感器,和用于語音識別的多點陣列麥克風組成。它本來是給Xbox的體感游戲游戲服務的,但由于它能獲得深度圖和RGB圖,所以它也被研究員拿來做RGBD-SLAM的研究。關于Kinect V1的發展和更詳細的介紹,大家可以參考百度百科的內容。
?微軟為了方便研究人員對Kinect V1進行開發,提供了專門的Kinect for Windows SDK。這個SDK主要是針對Windows7設計(Windows10也能用),內含驅動程序、豐富的原始感測數據流程式開發接口(RawSensorStreamsAPI)、自然用戶接口、安裝文件以及參考數據。Kinect for Windows SDK可讓使用C++、C#或VisualBasic語言搭配MicrosoftVisualStudio2010(VS2019也可使用)工具的程序設計師輕易開發使用。
?所以想要開發Kinect V1程序就需要找到對應的SDK和DeveloperToolkit,我使用的是V1.7的版本(SDK下載和DeveloperToolkit下載)。這兩個開發工具的選擇一定要根據你實際使用的機器型號來選,Kinect V1就選擇V1.X版本,Kinect V2則是V2.X,不然驅動對不上。
數據讀取的基本流程
?下面介紹一些我自己總結的Kinect V1的圖像數據讀取的代碼流程:
(1)先定義一個能指向Kinect對象的指針(好像只能通過指針的形式調用Kinect中的參數),并通過SDK中的函數為指針創建一個Kinect實例對象,幫助代碼中的對象能夠和現實設備聯系上;
(2)然后就能夠通過指針調用對象內置的函數,完成初始化,并在初始化時設置你準備初始化Kinect的哪些功能。實驗中我初始化了RGB圖和深度圖兩個功能;
(3)完成設備的初始化后,我們通過打開數據流通道(Kinect V1采用的是數據流的形式來傳輸、讀取數據)的方式,開啟RGB圖和深度圖的數據流通道,以便之后的數據獲取。在開啟數據流通道時,還需要使用一些句柄和事件對象來共同完成對數據流通道的配置;
(4)此時可以從開通的數據流通道中獲取數據,并將此數據鎖住,防止Kinect修改此數據。獲取的數據會被放在一個數組中,需要一個一個地將它們賦值給OpenCV的Mat對象。這時圖像就已經被提取出來了;
(5)使用完數據后,一定要記住將數據解鎖,否則Kinect會卡住(因為它不能移除掉當前數據),無法傳輸下一個數據;
(如果還要進行RGB圖和深度圖的對齊,則再可以先不解鎖數據)
?上述就是Kinect V1讀取數據的大致流程,下面來看具體實現代碼。
運行代碼和注釋
?此代碼是在Windows上運行的。因為SDK提供的主要是一些頭文件,所以還是用C++實現起來方便些。
#include <iostream>
#include <opencv2/opencv.hpp>
//windows的頭文件,必須要,不然NuiApi.h用不了
#include <Windows.h>
//Kinect for windows 的一些頭文件
#include <NuiApi.h>
#include <d3d11.h>
//要注意頭文件的打開順序。Windows要放在NuiApi前面。因為NuiApi會用到Windows中的一部分信息//硬記
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0) //主要是用來實現按鍵退出的功能,不需要的話可以不寫
using namespace cv;
using namespace std;//深度相機能夠探測到的
//最遠距離(mm)
const int MAX_DISTANCE = 3500;
//最近距離(mm)
const int MIN_DISTANCE = 200;//彩圖和深度圖的圖像大小
const LONG m_depthWidth = 640;
const LONG m_depthHeight = 480;
const LONG m_colorWidth = 640;
const LONG m_colorHeight = 480;
const LONG cBytesPerPixel = 4;int main(int argc,char** argv)
{Mat image_rgb;Mat image_depth;image_rgb.create(480, 640, CV_8UC3);image_depth.create(480, 640, CV_8UC1);//一個Kinect實例指針,專門指向一個Kinect類對象INuiSensor* m_pNuiSensor = NULL;//記錄當前連接KINECT的數量。也可以不寫int iSensorCount=-1;//獲得當前KINECT的數量,數量值保存在iSensorCount變量中//返回的hr應該是某中狀態變量,用于判斷相關函數是否運行出錯HRESULT hr = NuiGetSensorCount(&iSensorCount);//給函數傳指針std::cout << "The number of Kinects are " << iSensorCount << endl;//按照序列(0開始)創建一系列KINETC實例//函數參數:初始化的Kinect的編號,將要指向該編號的實例的指針的地址!二維指針hr = NuiCreateSensorByIndex(iSensorCount - 1, &m_pNuiSensor);//初始化,讓其可以接收彩色和深度數據流(即函數傳遞過去的那個參數)//對每個創建的實例,使用其自帶的初始化函數完成初始化hr = m_pNuiSensor->NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH);//判斷是否出錯。所以hr應該是用來判斷函數運行情況的變量類型if (FAILED(hr))//主要是Debug時使用,自信的人從不Debug!!!!!!!!!!!!!!!!!!!!!!!!!{std::cout << "NuiInitialize failed" << endl;return hr;}//創建彩色圖像獲取下一幀事件.創建事件的函數,返回的是事件的句柄!//CreateEvent的參數可以按照下面這個作為標準HANDLE nextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//這個事件句柄是構建數據流通道時的輸入,可以用來判斷相應事件是否發生//彩色圖像數據流通道的事件句柄HANDLE colorStreamHandle = NULL;//這個事件更像是一種指針,即在構建好數據流通道后,該指針直接關聯上了數據流通道//創建(是否獲取到了下一幀深度圖)事件HANDLE nextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//深度圖像數據流的事件句柄HANDLE depthStreamHandle = NULL;//為實例構建其與現實設備的數據流通道,這里NUI_IMAGE_TYPE_COLOR表示彩色圖像(即構建的是彩圖數據流通道)。hr = m_pNuiSensor->NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, 0, 2, nextColorFrameEvent, &colorStreamHandle);//colorStreamHandle句柄會和這個構建好的流通道相關聯//Kinect實例構建與現實設備間深度圖像的數據流通道hr = m_pNuiSensor->NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH, NUI_IMAGE_RESOLUTION_640x480, 0, 2, nextDepthFrameEvent, &depthStreamHandle);//設置用OpenCV的窗口來顯示圖像cv::namedWindow("colorImage", CV_WINDOW_AUTOSIZE);cv::moveWindow("colorImage", 300, 600);cv::namedWindow("depthImage", CV_WINDOW_AUTOSIZE);cv::moveWindow("depthImage", 0, 200);//循環讀取圖像并顯示while (1){//這個用來保存Kinect數據流中的某一時刻的數據NUI_IMAGE_FRAME pImageFrame_rgb;NUI_IMAGE_FRAME pImageFrame_depth;//WaitForSingleObject是一種Windows API函數,接受(事件句柄,等待時間ms)兩個參數//該函數會在指定等待時間內,查看事件是否發生。在時間內事件發生,返回0;否則返回其他狀態值(依情況而定)if (WaitForSingleObject(nextColorFrameEvent, 0) == 0){//從指定的數據流通道(由句柄給定)中得到數據,用指針指向獲得的數據。0表示等待多久后再獲取數據(注意此時獲得的數據不只有幀數據)hr = m_pNuiSensor->NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pImageFrame_rgb);if (FAILED(hr)){std::cout << "Could not get color image" << endl;m_pNuiSensor->NuiShutdown();return -1;}//INuiFrameTexture一個容納圖像幀數據的對象(幀數據)INuiFrameTexture* pTexture = pImageFrame_rgb.pFrameTexture;NUI_LOCKED_RECT lockedRect;//提取數據幀到LockedRect,它包括兩個數據對象:pitch每行字節數,pBits第一個字節地址//鎖定數據,這樣當我們讀數據的時候,kinect就不會去修改它。之后要記住解鎖pTexture->LockRect(0, &lockedRect, NULL, 0);//確認獲得的數據是否有效:字節數不為0if (lockedRect.Pitch != 0){//將數據轉換成為OpenCV的Mat格式//轉換的方式可以用掃描來形容,即把相應位置的數據給Mat中相應像素點賦值for (int i = 0; i < image_rgb.rows; i++){//第i行指針uchar* ptr = image_rgb.ptr(i);//每個字節代表一個顏色信息,直接使用ucharuchar* pBuffer = (uchar*)(lockedRect.pBits) + i * lockedRect.Pitch;for (int j = 0; j < image_rgb.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];}}imshow("colorImage", image_rgb);//waitKey(1);//搞定后記得要給數據解除鎖定:pTexture->UnlockRect(0);//此外還要把獲取到的幀釋放掉。因為pImage是指針,實際數據還是在那個流通道內。不釋放可能會造成通道阻塞吧m_pNuiSensor->NuiImageStreamReleaseFrame(colorStreamHandle, &pImageFrame_rgb);}else{cout << "Buffer length of received texture is bogus\r\n" << endl;}//這兩個變量是在進行深度圖--彩圖對齊時會使用//如果要對齊的話,對齊部分的操作最好和深度圖處理放在一起進行,不然會出現一些奇怪的問題BOOL nearMode;INuiFrameTexture* pColorToDepthTexture;//下面是如何獲得深度圖if (WaitForSingleObject(nextDepthFrameEvent, 0) == 0){//從指定的數據流通道內得到數據,pImageFrame指向讀取到的數據hr = m_pNuiSensor->NuiImageStreamGetNextFrame(depthStreamHandle, 0, &pImageFrame_depth);//這句話主要是為了之后進行彩圖--深度圖對齊時使用。如果只是為了獲得深度圖可以不寫這句話//pColorToDepthTexture應該是彩圖和深度圖之間像素的偏移度的值。hr = m_pNuiSensor->NuiImageFrameGetDepthImagePixelFrameTexture(depthStreamHandle, &pImageFrame_depth, &nearMode, &pColorToDepthTexture);//以下部分和處理彩圖差不多INuiFrameTexture* pTexture = pImageFrame_depth.pFrameTexture;NUI_LOCKED_RECT lockedRect;NUI_LOCKED_RECT ColorToDepthLockRect;pTexture->LockRect(0, &lockedRect, NULL, 0);pColorToDepthTexture->LockRect(0, &ColorToDepthLockRect, NULL, 0);for (int i = 0; i < image_depth.rows; i++){uchar* prt = image_depth.ptr<uchar>(i);uchar* pBuffer = (uchar*)(lockedRect.pBits) + i * lockedRect.Pitch;//這里需要轉換,因為每個深度數據是2個字節,應將BYTE轉成USHORTUSHORT* pBufferRun = (USHORT*)pBuffer;for (int j = 0; j < image_depth.cols; j++){//先向,將數據歸一化處理,對深度距離在300mm-3500mm范圍內的像素,映射到【0—255】內,//超出范圍的,都去做是邊緣像素if (pBufferRun[j] << 3 > MAX_DISTANCE) prt[j] = 255;//這里的左移3不知道是為什么??else if (pBufferRun[j] << 3 < MIN_DISTANCE) prt[j] = 0;else prt[j] = (BYTE)(256 * (pBufferRun[j] << 3) / MAX_DISTANCE);}}imshow("depthImage", image_depth);//waitKey(1);//接下來是對齊部分,將前景摳出來//存放深度點的參數NUI_DEPTH_IMAGE_POINT* depthPoints = new NUI_DEPTH_IMAGE_POINT[640 * 480];if (ColorToDepthLockRect.Pitch != 0){HRESULT hrState = S_OK;//一個能在不同空間坐標轉變的類(包括:深度,彩色,骨骼)INuiCoordinateMapper* pMapper;//設置KINECT實例的空間坐標系hrState = m_pNuiSensor->NuiGetCoordinateMapper(&pMapper);if (FAILED(hrState)){return hrState;}//重要的一步:從顏色空間映射到深度空間。參數說明://【參數1】:彩色圖像的類型//【參數2】:彩色圖像的分辨率//【參數3】:深度圖像的分辨率//【參數4】:深度圖像像素點的個數//【參數5】:深度圖所有的像素點//【參數6】:取內存的大小,個數。類型為NUI_DEPTH_IMAGE_PIXEL//【參數7】:存放映射結果點的參數hrState = pMapper->MapColorFrameToDepthFrame(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480, NUI_IMAGE_RESOLUTION_640x480,640 * 480, (NUI_DEPTH_IMAGE_PIXEL*)ColorToDepthLockRect.pBits, 640 * 480, depthPoints);//該函數根據之前獲得的位移圖像(彩圖和深度圖間偏移量),將彩圖上的像素依次投影到深度圖上。//depthPoints保存了彩圖中某像素在深度圖上的坐標,以及相應的深度值。if (FAILED(hrState)){return hrState;}//顯示的圖像Mat show;show.create(480, 640, CV_8UC3);show = 0;//在知道彩圖中各像素點的深度值情況下,只顯示深度小于一定范圍的點for (int i = 0; i < image_rgb.rows; i++){for (int j = 0; j < image_rgb.cols; j++){uchar* prt_rgb = image_rgb.ptr(i);uchar* prt_show = show.ptr(i);//在內存中偏移量long index = i * 640 + j;//從保存了映射坐標的數組中獲取點NUI_DEPTH_IMAGE_POINT depthPointAtIndex = depthPoints[index];//邊界判斷if (depthPointAtIndex.x >= 0 && depthPointAtIndex.x < image_depth.cols &&depthPointAtIndex.y >= 0 && depthPointAtIndex.y < image_depth.rows){//深度判斷,在MIN_DISTANCE與MAX_DISTANCE之間的當成前景,顯示出來//這個使用也很重要,當使用真正的深度像素點再在深度圖像中獲取深度值來判斷的時候,會出錯if (depthPointAtIndex.depth >= MIN_DISTANCE && depthPointAtIndex.depth <= MAX_DISTANCE){prt_show[3 * j] = prt_rgb[j * 3];prt_show[3 * j + 1] = prt_rgb[j * 3 + 1];prt_show[3 * j + 2] = prt_rgb[j * 3 + 2];}}}}imshow("show", show);}delete[]depthPoints;pTexture->UnlockRect(0);m_pNuiSensor->NuiImageStreamReleaseFrame(depthStreamHandle, &pImageFrame_depth);}else{//這里是獲得的深度圖數據不好時的情況cout << "Buffer length of received texture is bogus\r\n" << endl;}}if (cvWaitKey(20) == 27)break;if (KEY_DOWN('Q'))//如果鍵盤輸入流'Q'則推出循環break;}m_pNuiSensor->NuiShutdown();destroyAllWindows();return 0;
}
結尾
?在知道Kinect V1的基礎使用后,就能自己編寫一些Demo來玩,比如通過OpenCV自帶人臉識別工具和Kinect相機,完成攝像頭人臉檢測跟蹤功能等。但這些都是在Windows上實現的,而SLAM在Ubuntu這類Linux系統上運行會更方便一些。所以下一步就是學習如何在Ubuntu和ROS上使用Kinect。
總結
以上是生活随笔為你收集整理的Kinect V1读取图像数据(For Windows)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: qq女生网名繁体字大全
- 下一篇: 现在苹果6s多少钱啊?