前言
网上有不少使用Qt做界面,OpenNI为库来开发kinect。或许大家的第一个问题就是询问该怎样使用Kinect来获取颜色信息图和深度信息图呢?这一节就是简单来回答这个问题的。
开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2
实验说明:
在使用OpenNI来驱动读取kinect数据时,我们需要了解context object这个名词。查看了下OpenNI UserGuide文档,简单翻译下这个名词的意思:
Context是openNI中一个主要的object,它掌握了OpenNI使用过程中应用程序的全部状态,以及这些状态的prodection chains,一个应用程序有多个context,但是这些context之间不能共享信息。例如一个中间件节点不能使用另一个context的驱动节点。Context在使用前必须被立即初始化,因此此时所有嵌入的模块被下载和分析。为了释放context的内存,应用程序需调用shutdown程序。
虽然翻译得不准确,但是它的大概意思就是告诉我们在驱动kinect时,需要用到context这个类,且我们需要安装一定顺序去使用,这与一些常见的库驱动差不多,比如opengl,这些都需要什么初始化啊,设置属性啊等。因此我们只需要直接去看懂他人的一个工程实例就ok了。
好了,本文参考Heresy的教程中的源码写的。
在新建好工程文件后,需要包含XnCppWrapper头文件,且需在Qt工程中设置好头文件目录和库文件目录。
使用OpenNI读取颜色图和深度图的步骤如下(这个是程序的核心部分):
1. 定义一个Context对象,并 调用该对象的Init()方法来进行初始化。
2. 定义一个XnMapOutputMode格式对象,设置好分图像分辨率和帧率。
3. 定义颜色图和深度图的节点对象,并用其Create()方法来创建,参数为Context对象.
4. 设置颜色和深度图的输出模式,调用的方法是SetMapOutputMode();参数为步骤2中定义和设置好了的XnMapOutputMode对象。
6. 如果深度图和颜色图在一张图上显示,则必须对深度图像进行校正,校正的方法是调用深度图的如下方法:.GetAlternativeViewPointCap().SetViewPoint();
7. 调用context对象的StartGeneratingAll()来开启设备读取数据开关。
8. 调用context对象的更新数据方法,比如WaitAndupdateAll()方法。
9. 定义颜色图和色彩图的ImageMetaData对象,并利用对应的节点对象的方法GetMetaData(),将获取到的数据保存到对应的ImageMetaData对象中。
10. 如果需要将深度图转换成灰度图来显示,则需要自己将深度值转换成0~255的单通道或者多通道数据,然后直接用来显示。
注意如果没有设置视觉校正,则深度图的显示与颜色图的显示会出现对应不上的情况,后面的实验可以看出这2者的区别,另外对于是否需要设置镜像就要看自己的具体应用场合了。
实验结果:
下面分别分是否设置图像镜像,是否对深度图像进行校正来给出实验结果.
无镜像无校正:
无镜像有校正:
有镜像无校正:
有镜像有校正:
从有无镜像可以看出,设置镜像的效果与字面的理解是一样的,即有镜像时就相当于取镜子中的图像。有无校正可以看出,没有校正时,深度图片和颜色图片同一个物体都对应不起来,可以看下天花板上的吊灯就可以发现,没校正,2者不重合,且相差不少。有校正时效果就好多了,只是此时的深度图像显示的范围要稍小些。
实验主要部分代码及注释(附录有工程code下载链接):
首先来个最小工程,即去掉那些错误处理代码:
main.cpp:
#include <QtGui> #include <XnCppWrapper.h> //包含OpenNI的头文件 using namespace xn;//使用OpenNI库中的命名空间 //全局的OpenNI object Context g_context; ImageGenerator g_image_generator; DepthGenerator g_depth_generator; //全局的Qt Object QGraphicsPixmapItem *g_image_map; QGraphicsPixmapItem *g_depth_map; //CTimer类的定义 class CTimer : public QObject { public: void start() { g_context.StartGeneratingAll();//开启设备读取数据的开关 startTimer(33);//使用startTimer()启动定时器,每当时间到时会自动调用timerEvent()函数 } private: void timerEvent(QTimerEvent *) { g_context.WaitAndUpdateAll();//更新数据 //颜色数据 ImageMetaData image_map; g_image_generator.GetMetaData(image_map); //为g_image_map设置图片,图片的数据来源于外部硬件设备 g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(), image_map.YRes(), QImage::Format_RGB888))); //深度数据 DepthMetaData depth_map; g_depth_generator.GetMetaData(depth_map); XnDepthPixel max_depth_value = depth_map.ZRes(); QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式为ARGB32型的 for(unsigned int i = 0; i < depth_map.XRes(); i++) for(unsigned int j = 0; j < depth_map.YRes(); j++) { XnDepthPixel depth_value_ij = depth_map(i, j);//获取x,y处的坐标值 if(depth_value_ij == 0) { depth_img.setPixel(i, j, qRgba(0, 0, 0, 0)); }//如果捕捉不到深度信息,则将其设置为0 else { float fscale = 1.0f*depth_value_ij/max_depth_value;//当前深度的比例因子 depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale))); } } g_depth_map->setPixmap(QPixmap::fromImage(depth_img)); } }; int main(int argc, char **argv) { QApplication app(argc, argv); g_context.Init();//context初始化 g_context.SetGlobalMirror(true);//设置全局镜像,就像照镜子一样,与设置为false时的2张图片镜像 XnMapOutputMode xmode;//定义图像的输出模式 xmode.nXRes = 640;//x方向分辨率 xmode.nYRes = 480;//y方向分辨率 xmode.nFPS = 30;//帧率 //设置颜色节点属性 g_image_generator.Create(g_context); g_image_generator.SetMapOutputMode(xmode); //设置深度节点属性 g_depth_generator.Create(g_context); g_depth_generator.SetMapOutputMode(xmode); //视觉校正,否则深度图和颜色图感应到的区域不能一一对应 g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator); //Qt场景设置 QGraphicsScene scene; g_image_map = scene.addPixmap(QPixmap()); g_image_map->setZValue(1);//设置为z方向上的第1层 g_depth_map = scene.addPixmap(QPixmap()); g_depth_map->setZValue(2);//设置为z方向上的第2层 //Qt视图创建 QGraphicsView view(&scene); view.resize(660, 500); //设置定时器,每隔一段时间读取kinect的颜色信息和深度信息 CTimer timer; timer.start(); view.show(); return app.exec(); }
加入错误处理部分后的完整main.cpp:
#include <QtGui> #include <XnCppWrapper.h> //包含OpenNI的头文件 using namespace xn;//使用OpenNI库中的命名空间 //全局的OpenNI object XnStatus g_status; Context g_context; ImageGenerator g_image_generator; DepthGenerator g_depth_generator; bool g_has_image_generator = true; //全局的Qt Object QGraphicsPixmapItem *g_image_map; QGraphicsPixmapItem *g_depth_map; //CTimer类的定义 class CTimer : public QObject { public: void start() { g_status = g_context.StartGeneratingAll();//开启设备读取数据的开关 if(g_status == XN_STATUS_OK) { startTimer(33);//使用startTimer()启动定时器,每当时间到时会自动调用timerEvent()函数 } else { QMessageBox::critical(NULL, "Create Data Error!", xnGetStatusString(g_status));//显示创建数据失败,该消息框没有父窗口 } } private: void timerEvent(QTimerEvent *) { g_context.WaitAndUpdateAll();//更新数据 //颜色数据 if(g_has_image_generator) { ImageMetaData image_map; g_image_generator.GetMetaData(image_map); //为g_image_map设置图片,图片的数据来源于外部硬件设备 g_image_map->setPixmap(QPixmap::fromImage(QImage(image_map.Data(), image_map.XRes(), image_map.YRes(), QImage::Format_RGB888))); } //深度数据 DepthMetaData depth_map; g_depth_generator.GetMetaData(depth_map); XnDepthPixel max_depth_value = depth_map.ZRes(); QImage depth_img(depth_map.XRes(), depth_map.YRes(), QImage::Format_ARGB32);//格式为ARGB32型的 for(unsigned int i = 0; i < depth_map.XRes(); i++) for(unsigned int j = 0; j < depth_map.YRes(); j++) { XnDepthPixel depth_value_ij = depth_map(i, j);//获取x,y处的坐标值 if(depth_value_ij == 0) { depth_img.setPixel(i, j, qRgba(0, 0, 0, 0)); }//如果捕捉不到深度信息,则将其设置为0 else { float fscale = 1.0f*depth_value_ij/max_depth_value;//当前深度的比例因子 depth_img.setPixel(i, j, qRgba(255*(1-fscale), 0, 255*fscale, 255*(1-fscale))); } } g_depth_map->setPixmap(QPixmap::fromImage(depth_img)); } }; int main(int argc, char **argv) { QApplication app(argc, argv); g_status = g_context.Init();//context初始化 if(g_status != XN_STATUS_OK) { QMessageBox::critical(NULL, "Context Initial Error!", xnGetStatusString(g_status)); return -1; } // g_context.SetGlobalMirror(true);//设置全局镜像,就像照镜子一样,与设置为false时的2张图片镜像 XnMapOutputMode xmode;//定义图像的输出模式 xmode.nXRes = 640;//x方向分辨率 xmode.nYRes = 480;//y方向分辨率 xmode.nFPS = 30;//帧率 //设置颜色节点属性 g_status = g_image_generator.Create(g_context); if(g_status != XN_STATUS_OK) { QMessageBox::critical(NULL, "Image map create failed", xnGetStatusString(g_status)); g_has_image_generator = false; } if( g_has_image_generator ) { g_status = g_image_generator.SetMapOutputMode(xmode); if(g_status != XN_STATUS_OK) { QMessageBox::critical(NULL, "Image map output mode error!", xnGetStatusString(g_status)); return -1; } } //设置深度节点属性 g_status = g_depth_generator.Create(g_context); if(g_status != XN_STATUS_OK) { QMessageBox::critical(NULL, "Depth map create failed", xnGetStatusString(g_status)); return -1; } g_status = g_depth_generator.SetMapOutputMode(xmode); if(g_status != XN_STATUS_OK) { QMessageBox::critical(NULL, "Depth map output mode error!", xnGetStatusString(g_status)); return -1; } if(g_has_image_generator)//视觉校正,否则深度图和颜色图感应到的区域不能一一对应 ;//g_depth_generator.GetAlternativeViewPointCap().SetViewPoint(g_image_generator); //Qt场景设置 QGraphicsScene scene; g_image_map = scene.addPixmap(QPixmap()); g_image_map->setZValue(1);//设置为z方向上的第1层 g_depth_map = scene.addPixmap(QPixmap()); g_depth_map->setZValue(2);//设置为z方向上的第2层 //Qt视图创建 QGraphicsView view(&scene); view.resize(660, 500); //设置定时器,每隔一段时间读取kinect的颜色信息和深度信息 CTimer timer; timer.start(); view.show(); return app.exec(); }
总结:通过本次实验,了解了怎样使用OpenNI来显示kinect的颜色图像和深度图像了。
参考资料:http://kheresy.wordpress.com/index_of_openni_and_kinect/comment-page-5/
附录:实验工程code下载。