OpenCV 的全名是「Open Source Computer Vision」(官方网站、中文网站),是一套采用 BSD 授权的开放原始码的计算机视觉函式库,在相关领域来说,算是一套相当之行的函式库;在 OpenCV 里面,包含了很多图像处理的功能,同时也包含了基本的图形接口、以及摄影机的操作等功能。
而对于 OpenNI 这样、针对深度影像和彩色影像做处理的架构,其实如果不是单纯只是想靠NITE 来追踪人体骨架的话,OpenCV 是一个相当适合拿来搭配使用的函式库;实际上,OpenCV 现在也可以直接整合 OpenNI 来读取影像(请参考《Using Kinect and other OpenNIcompatible depth sensors》)。
这篇呢,Heresy 则是以简单的范例,大概讲一下怎么把 OpenNI 的深度和彩色数据,读出来转换成 OpenCV 的格式。下面就直接看原始码吧~
//OpenNI Header
#include<XnCppWrapper.h>
// link OpenNI library
#pragmacomment( lib, "OpenNI.lib" )
// OpenCV Header
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
// Link OpenCV Library
#ifdef_DEBUG
#pragma comment(lib, "opencv_core242d.lib" )
#pragma comment(lib, "opencv_highgui242d.lib" )
#pragma comment(lib, "opencv_imgproc242d.lib" )
#else
#pragma comment(lib, "opencv_core242.lib" )
#pragma comment(lib, "opencv_highgui242.lib" )
#pragma comment(lib, "opencv_imgproc242.lib" )
#endif
// main function
int main( intargc, char** argv )
{
// 1a. initial OpenNI
xn::Context xContext;
xContext.Init();
// 1b. create depth generator
xn::DepthGenerator xDepth;
xDepth.Create( xContext );
// 1c. create image generator
xn::ImageGenerator xImage;
xImage.Create( xContext );
// 1d. set alternative view point
xDepth.GetAlternativeViewPointCap().SetViewPoint( xImage );
// 2. create OpenCV Windows
cv::namedWindow( "Depth Image", CV_WINDOW_AUTOSIZE );
cv::namedWindow( "Color Image", CV_WINDOW_AUTOSIZE );
cv::namedWindow( "Depth Edge", CV_WINDOW_AUTOSIZE );
cv::namedWindow( "Color Edge", CV_WINDOW_AUTOSIZE );
// 3. start OpenNI
xContext.StartGeneratingAll();
// main loop
while( true )
{
// 4. update data
xContext.WaitAndUpdateAll();
// 5. get image data
{
xn::ImageMetaData xColorData;
xImage.GetMetaData( xColorData );
// 5a. convert to OpenCV form
cv::Mat cColorImg( xColorData.FullYRes(),xColorData.FullXRes(),
CV_8UC3, (void*)xColorData.Data());
// 5b. convert from RGB to BGR
cv::Mat cBGRImg;
cvtColor( cColorImg, cBGRImg, CV_RGB2BGR );
cv::imshow( "Color Image", cBGRImg );
// 5c. convert to signle channel and do edge detection
cv::Mat cColorEdge;
cvtColor( cColorImg, cBGRImg, CV_RGB2GRAY );
cv::Canny( cBGRImg, cColorEdge, 5,100 );
cv::imshow( "Color Edge", cColorEdge );
}
// 6. get depth data
{
xn::DepthMetaData xDepthData;
xDepth.GetMetaData( xDepthData );
// 6a. convert to OpenCV form
cv::Mat cDepthImg( xDepthData.FullYRes(),xDepthData.FullXRes(),
CV_16UC1, (void*)xDepthData.Data());
// 6b. convert to 8 bit
cv::Mat c8BitDepth;
cDepthImg.convertTo( c8BitDepth, CV_8U, 255.0 / 7000 );
cv::imshow( "Depth Image", c8BitDepth );
// 6c. convert to 8bit, and do edge detection
cv::Mat CDepthEdge;
cv::Canny( c8BitDepth, CDepthEdge,5, 100 );
cv::imshow( "Depth Edge", CDepthEdge );
}
cv::waitKey( 1 );
}
}
在 这边的例子里,Heresy 并没有去使用整合 OpenNI 的 OpenCV,而是独立使用 OpenNI 来做数据的读取,然后再转换成 OpenCV 的格式。实际上,如果只是要使用 OpenNI 的影像数据的话,使用整合过的 OpenCV 可以直接使用内建的 VideoCapture 来做画面的读取,在使用上会比较单纯、简单一点,不过由于这样会少掉一些 OpenNI 的功能,所以在这边 Heresy 不使用这样的方法。
所以,在上面的范例里面,1a 到 1d 的部分,就是用标准 OpenNI 的流程,来进行初始化的动作;详细的说明,请参考《透过OpneNI 读取Kinect 深度影像数据》。而接下来 2 的部分,则是使用 OpenCV 的 highgui 这个模块的namedWindow() 这个函式(官方文件),来建立四个不同名称的窗口、作为画面的显示。
接下来,则是透过一个无穷循环,来不停地更新数据了~里面主要分成两块,也就是5、读取 Image Generator 的彩色影像、以及 6、读取 DepthGenerator 的深度影像的部分。
其中,在 5a 和 6a 的部分,就是把 OpenNI 读出来的 map(xn::ImageMetaData 和 xn::DepthMetaData)转换成 OpenCV 的影像格式、cv::Mat 的部分(官方文件)。
以彩色影像来说,就是在建立 cv::Mat 对象的时候,把影像的大小、也就是 Y 轴、X 轴的分辨率,以及数据的形式、数据的地址,都传递给建构子、以建立出一张 OpenCV 的影像、cColorImg。其中,CV_8UC3 是指 3 channel 的 8bit 正整数(unsignedchar)的资料(参考)。
不过,由于 OpenCV 所使用的彩色影像的色彩,默认是以 Blue、Green、Red 来做排列,和一般Red、Green、Blue 排列不同,所以要拿来用的话,还需要先做一个转换;在这边(5b)就是透过 cvtColor() 这个 OpenCV 的 imgproc 这个模块里的函式(官方文件),把本来的 RGB 影像、转换成 BGR 的影像(cBGRImg)。而在转换好之后,则就是在透过 imshow() 这个函式,把转换完成的影像、显示在对应的窗口(这边是 ColorImage)上了。
而接下来(5c),Heresy 则是试着用 OpenCV 提供的 Canny 这种方法的边缘侦测(官方文件)。不过由于 OpenCV 所提供的 Canny edge 只有针对 8bit 1 channel 的影像作处理,所以这边要先再用 cvtColor(),把影像转成灰阶的、然后再来进行;而之后,则是一样透过imshow() 这个函式,把侦测完的结果、显示在对应的窗口上。
深度的部分(6a)也是类似的,不过由于OpenNI 的深度影像的单一像素的格式是 XnDepthPixel、实际上是单一 channel 的 16bit 的正整数(unsigned short),所以在建立 cv::Mat 的时候的数据型别,则是要设定为 CV_16UC1。
不 过,虽然 OpenNI 的深度影像是 16bit 的正整数,理论上值的范围是 0 - 65,535,但是实际上深度的最大值只会到 10,000,所以如果不处理、直接画的话,会有整个画面偏暗的问题(基本上,画面会接近全黑);所以在这边,Heresy 也先透过 cv::Mat 的 convertTo() 的函式,把这个 16bit 的影像里的每一个像素都乘上一个scale(255.0 / 7000)后,转换成 8bit 的影像(c8BitDepth)。再之后,就是一样把转换好的影像,进行 canny edge 侦测了~
而这样的程序执行的结果,会有下面这样、四个不同数据的窗口,分别代表彩色影像、基于彩色影像的边缘侦测结果、深度影像、以及基于深度影像进行边缘侦测的结果。
这篇就先到这了。基本上,Heresy 是把这篇文章定位成一个极为简单的 OpenNI 和 OpenCV 的数据整合范例;而由于 OpenCV 还有提供相当多的图像处理的功能,接下来要怎么做,就是看自己想要做什么了~