一、寫在前面的話
圖像處理小碩一枚,剛入坑一個月,老闆交付任務讓用Kinect完成圖像的採集(彩色圖和深度圖),然後在PCL點雲環境下生成.PCD文件,這樣一個小demo可是難不倒我的,哼。現將編程思路和核心代碼提供給你們,希望各位前輩老師們能夠給出一些改進意見,也希望自己的探索能夠幫助後來人。嗯,就醬,咱們開始吧!
二、Kinect的安裝,PCL的安裝,OpenCV2.4.9+VS2013的安裝
開始之前,環境一定要搭建好,切忌一味追求要源碼,系統變量之類的配置不好,再好的代碼也運行不起來呀!首先,Kinect for Windows的安裝包以及官方ToolKit之類的東西我發百度雲盤鏈接,
Kinect for Windows,假使失效了,你們可以聯繫我。我會發給你們,如果你們找不到的話。Kinect的安裝時傻瓜式的,這裏就不再介紹了,至於PCL的安裝,我也是借鑑博客裏前輩的安裝方法,鏈接如下:http://blog.csdn.net/u011197534/article/details/52960394。感謝這位博主的文章,讓我減少了很多時間上的浪費。安裝PCL完成之後切記:運行示例程序出現OpenGL窗口顯示如下圖片就是安裝正確,你可以調動一下滾輪,三個顏色就是一個三維座標軸無限放大形成的,縮小一點你就可以看見點雲了。
沒錯,就是上面這個花花綠綠的東西,本寶寶可是配置完了之後覺得不對,卸載安裝再三才終於搞明白,這是要滑動滾輪進行所轄,就能看到一個構建的三維球體。(這個是比較經典的測試程序),至於OpenCV的安裝,請借鑑淺墨老師的博客 OpenCV入門系列。
三、程序設計思想和具體代碼執行
1.實現Kinect提取圖片
爲什麼要用Kinect提取圖片呢?爲什麼不直接用手機或者照相機來拍攝呢?原因如下:Kinect是帶有彩色色相頭和深度圖採集攝像頭的,也就是說,Kinect拍攝出來的有平面圖,也有立體圖,二者合併就是可以構建一個三維立體了,當然,Kinect在本程序中只是一個採集的工具了。首先,需要創建兩個Mat型的變量存放攝取的彩色圖和深度圖。
colorImage.create(480, 640, CV_8UC3);
depthImage.create(480, 640, CV_8UC1);
注意建立的像素是480*640。之後進行Kinect的初始化,初始化代碼如下:
//1、初始化NUI
HRESULT hr = NuiInitialize(NUI_INITIALIZE_FLAG_USES_COLOR | NUI_INITIALIZE_FLAG_USES_DEPTH);
//2、定義事件句柄
//創建讀取下一幀的信號事件句柄,控制KINECT是否可以開始讀取下一幀數據
HANDLE nextColorFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE colorStreamHandle = NULL; //保存彩色圖像數據流的句柄,用以提取數據
HANDLE nextDepthFrameEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE depthStreamHandle = NULL;//保存深度圖像數據流的句柄,用以提取數據
//3、打開KINECT設備的彩色圖信息通道,並用colorStreamHandle保存該流的句柄,以便於以後讀取
hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_COLOR, NUI_IMAGE_RESOLUTION_640x480,
0, 2, nextColorFrameEvent, &colorStreamHandle);
namedWindow("colorImage", CV_WINDOW_AUTOSIZE);
hr = NuiImageStreamOpen(NUI_IMAGE_TYPE_DEPTH, NUI_IMAGE_RESOLUTION_640x480,
0, 2, nextDepthFrameEvent, &depthStreamHandle);
namedWindow("depthImage", CV_WINDOW_AUTOSIZE);
//4、開始讀取彩色圖數據
while (1)
{
const NUI_IMAGE_FRAME * pColorImageFrame = NULL;
const NUI_IMAGE_FRAME * pDepthImageFrame = NULL;
//4.1、無限等待新的彩色圖像數據,等到後返回
if (WaitForSingleObject(nextColorFrameEvent, INFINITE) == 0)
{
//4.2、從剛纔打開數據流的流句柄中得到該幀數據,讀取到的數據地址存於pColorImageFrame
hr = NuiImageStreamGetNextFrame(colorStreamHandle, 0, &pColorImageFrame);
INuiFrameTexture * pTexture = pColorImageFrame->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<colorImage.rows; i++)
{
uchar *ptr = colorImage.ptr<uchar>(i); //第i行的指針
//每個字節代表一個顏色信息,直接使用uchar
uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
for (int j = 0; j<colorImage.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", colorImage);
}
//5、這幀已經處理完了,所以將其解鎖
pTexture->UnlockRect(0);
//6、釋放本幀數據,準備迎接下一幀
NuiImageStreamReleaseFrame(colorStreamHandle, pColorImageFrame);
}
//7.1、無限等待新的深度數據,等到後返回
if (WaitForSingleObject(nextDepthFrameEvent, INFINITE) == 0)
{
//7.2、從剛纔打開數據流的流句柄中得到該幀數據,讀取到的數據地址存於pImageFrame
hr = NuiImageStreamGetNextFrame(depthStreamHandle, 0, &pDepthImageFrame);
INuiFrameTexture * pTexture = pDepthImageFrame->pFrameTexture;
NUI_LOCKED_RECT LockedRect;
//7.3、提取數據幀到LockedRect,它包括兩個數據對象:pitch每行字節數,pBits第一個字節地址
//並鎖定數據,這樣當我們讀數據的時候,kinect就不會去修改它
pTexture->LockRect(0, &LockedRect, NULL, 0);
//7.4、確認獲得的數據是否有效
if (LockedRect.Pitch != 0)
{
//7.5、將數據轉換爲OpenCV的Mat格式
for (int i = 0; i<depthImage.rows; i++)
{
uchar *ptr = depthImage.ptr(i); //第i行的指針
//每個字節代表一個顏色信息,直接使用uchar
uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
USHORT *pBufferRun = (USHORT *)pBuffer;//這裏需要轉換,因爲每個深度數據是2個字節,應將BYTE轉成USHORT
for (int j = 0; j<depthImage.cols; j++)
{
ptr[j] = 255 - (BYTE)(256 * pBufferRun[j] / 0x1fff); //將數據歸一化處理*
}
}
imshow("depthImage", depthImage); //顯示圖像
}
//8、這幀已經處理完了,所以將其解鎖
pTexture->UnlockRect(0);
//9、釋放本幀數據,準備迎接下一幀
NuiImageStreamReleaseFrame(depthStreamHandle, pDepthImageFrame);
if (cvWaitKey(20) == 'q')
{
imwrite("2.jpg", depthImage);
imshow("截取的深度圖", depthImage);
imwrite("1.jpg", colorImage);
imshow("截取的彩色圖", colorImage);
//Sleep(10000);
break;
}
}
在while(1)循環中實現圖像採集,也就是說,不按下‘q’鍵採集到圖片或者‘ESC’是不會停止的,在OpenCV的窗口下是一直能看到Kinect攝取的圖像流的,攝取的圖像還要存儲到本工程目錄之下,以便下一步的使用。
2.從圖像中進行點雲提取並採集存儲
這一步就比較簡單了,前提是點雲環境配置一切ok,套路就是從工程目錄下讀取兩張圖片,一張彩色圖,一張深度圖,之後通過整合加權將二者生成點雲到一個目錄文件下。代碼操作如下:
cv::Mat color = cv::imread("1.jpg");
cv::Mat depth = cv::imread("2.jpg");
int rowNumber = color.rows;
int colNumber = color.cols;
cloud_a.height = rowNumber;
cloud_a.width = colNumber;
cloud_a.points.resize(cloud_a.width * cloud_a.height);
for (unsigned int u = 0; u < rowNumber; ++u)
{
for (unsigned int v = 0; v < colNumber; ++v)
{
unsigned int num = u*colNumber + v;
double Xw = 0, Yw = 0, Zw = 0;
Zw = ((double)depth.at<uchar>(u, v)) / 255.0 * 10001.0;
Xw = (u - u0) * Zw / fx;
Yw = (v - v0) * Zw / fy;
cloud_a.points[num].b = color.at<cv::Vec3b>(u, v)[0];
cloud_a.points[num].g = color.at<cv::Vec3b>(u, v)[1];
cloud_a.points[num].r = color.at<cv::Vec3b>(u, v)[2];
cloud_a.points[num].x = Xw;
cloud_a.points[num].y = Yw;
cloud_a.points[num].z = Zw;
}
}
*cloud = cloud_a;
pcl::io::savePCDFile("colorImage.pcd", *cloud);
/*pcl::visualization::CloudViewer viewer("Cloud Viewer");
viewer.showCloud(cloud);
viewer.runOnVisualizationThreadOnce(viewerOneOff);
while (!viewer.wasStopped())
{
user_data = 9;
}*/
printf("點雲生成完畢\n");
return 0;
最後在控制檯上顯示出存儲完畢,提醒存儲已經完成了。
3.點雲驗證和主函數調用
按照老闆佈置的任務來說,已經完成了,而且還不錯,添加了拍照功能,但是問題來了,點雲的提取是一個看不見摸不着的過程,輸出的也是一個.pcd文件,打開是一串串的數字,並不知道對與不對,所以,自己又加了一個驗證功能,怎麼驗證呢?當然是PCL下從本工程提取一個點雲文件的數據並顯示到OpenGL上去。嘻嘻,代碼很簡單,如下所示:
kinect_init();//攝像
pcl_init();//點雲提取
pcl::PointCloud<pcl::PointXYZRGBA>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGBA>);
pcl::io::loadPCDFile("colorImage.pcd", *cloud);//加載點雲文件
pcl::visualization::CloudViewer viewer("Cloude Viewer");//創建viewer對象
viewer.showCloud(cloud);
//運行一次這個函數
viewer.runOnVisualizationThreadOnce(viewerOneOff);
//每次可視化迭代都要調用一下
viewer.runOnVisualizationThread(viewerPsycho);
while (!viewer.wasStopped())
{
user_data1++;
}
printf("程序運行完畢\n");
四、總結與感想
第一次在CSDN上面發帖,格式排版欠缺很大,以後會慢慢熟悉的,希望大家能夠諒解,如果有人看的話,哈哈。裏面的內容也各有借鑑,但是並沒有生搬硬套,而是
整合了各個代碼,如果你們也需要這樣的小demo或者上面的代碼不足以提供你跑下去,你們可以留消息給我,我會發郵件給你們全部的工程代碼,此外,因爲本科是
學控制的,微控制器會了幾款,基於它們的編程風格,力求主函數簡單,就分別將Kinect和PCL的部分封裝到了其他的.cpp函數之中,看着很簡練吧。就這樣吧,有什
麼問題你們隨時可以聯繫我,看到這篇文章的你,祝福你~