基于PCL和Kinect的图像采集和点云生成

来源:互联网 发布:html5 css3 js pdf 编辑:程序博客网 时间:2024/06/16 00:12

一、写在前面的话

图像处理小硕一枚,刚入坑一个月,老板交付任务让用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函数之中,看着很简练吧。就这样吧,有什
么问题你们随时可以联系我,看到这篇文章的你,祝福你~



阅读全文
1 0
原创粉丝点击