基於PCL和Kinect的圖像採集和點雲生成

一、寫在前面的話

圖像處理小碩一枚,剛入坑一個月,老闆交付任務讓用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函數之中,看着很簡練吧。就這樣吧,有什
麼問題你們隨時可以聯繫我,看到這篇文章的你,祝福你~



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