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