基于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窗口显示如下图片就是安装正确,你可以调动一下滚轮,三个颜色就是一个三维坐标轴无限放大形成的,缩小一点你就可以看见点云了。
基于PCL和Kinect的图像采集和点云生成_第1张图片
没错,就是上面这个花花绿绿的东西,本宝宝可是配置完了之后觉得不对,卸载安装再三才终于搞明白,这是要滑动滚轮进行所辖,就能看到一个构建的三维球体。(这个是比较经典的测试程序),至于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(i);  //第i行的指针  

					//每个字节代表一个颜色信息,直接使用uchar  
					uchar *pBuffer = (uchar*)(LockedRect.pBits) + i * LockedRect.Pitch;
					for (int j = 0; jUnlockRect(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; iUnlockRect(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(u, v)) / 255.0 * 10001.0;
			Xw = (u - u0) * Zw / fx;
			Yw = (v - v0) * Zw / fy;

			cloud_a.points[num].b = color.at(u, v)[0];
			cloud_a.points[num].g = color.at(u, v)[1];
			cloud_a.points[num].r = color.at(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::Ptr cloud(new pcl::PointCloud);
	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函数之中,看着很简练吧。就这样吧,有什
么问题你们随时可以联系我,看到 这篇文章的你,祝福你~



你可能感兴趣的:(PCL点云库,OpenCV2.0,Kinect)