1、编写代码
代码参考了博客Kinect+OpenNI学习笔记之4(OpenNI获取的图像结合OpenCV显示)
仿照上面的博客,也将openni库封装成了一个类,用于读取kinect的彩色图像数据和深度数据,也方便以后,模块化编程。
NIKinect.cpp文件代码如下:
#include<XnCppWrapper.h>
#include<iostream>
#include <boost/concept_check.hpp>
using namespace xn;//openni命名空间
using namespace std;//C++命名空间
class NIKinect
{
private:
XnStatus status;//状态标志
Context context;//kinect物件
DepthGenerator depth_generator;//深度数据生成器
ImageGenerator image_generator;//彩色图像生成器
//检测openni运行状态
bool CheckError(XnStatus result,string re_str)
{
if(result!=XN_STATUS_OK)
{
cerr<<re_str<<"Error:"<<xnGetStatusString(result)<<endl;
return false;
}
return true;
}
public:
DepthMetaData depthMD;
ImageMetaData imageMD;
~NIKinect()
{
context.Release();//释放空间
}
bool Initial()
{
//设备初始化
status=context.Init();
if(!CheckError(status,"Initial context failed !"))
return false;
//彩色图像生成器初始化
status=image_generator.Create(context);
if(!CheckError(status,"Create image generator error!"))
return false;
//深度图像生成器初始化
status=depth_generator.Create(context);
if(!CheckError(status,"Create depth generator error!"))
return false;
//调整视角
status=depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
if(!CheckError(status,"Correcting error!"))
return false;
return true;
}
bool Start()
{
XnMapOutputMode mapMode;
mapMode.nXRes = 640;//设定生成器的参数,分辨率为640*480(标准),30fps采样
mapMode.nYRes = 480;
mapMode.nFPS = 30;
status = depth_generator.SetMapOutputMode( mapMode );//设置参数
status = image_generator.SetMapOutputMode( mapMode );
status=context.StartGeneratingAll();
if(!CheckError(status,"Start generating error !"))
return false;
return true;
}
bool UpdateData()
{
status=context.WaitNoneUpdateAll();
if(!CheckError(status,"Update data error !"))
return false;
//获取数据
image_generator.GetMetaData(imageMD);
depth_generator.GetMetaData(depthMD);
return true;
}
bool Stop()
{
context.StopGeneratingAll();//关闭数据收集开关
context.Shutdown();//关闭所有驱动并且正确地清除所有
return true;
}
};
main.cpp文件代码如下:
#include <iostream>
#include <opencv2/opencv.hpp>
#include "NIKinect.cpp"
using namespace cv;
int main(int argc, char **argv)
{
//【1】OpenCV定义存储相关图像数据的参数
Mat image_gray,image_edge;
IplImage* imgDepth16u=cvCreateImage(cvSize(640,480),IPL_DEPTH_16U,1);
IplImage* imgRGB8u=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
IplImage* depthShow=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* imageShow=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
cvNamedWindow("depth",1);//定义CV图像显示窗口
cvNamedWindow("image",1);
char key=0;
NIKinect nikinect;
nikinect.Initial();
nikinect.Start();
while((key!=27)&&(nikinect.UpdateData()))
{
/*获取并显示深度图像*/
memcpy(imgDepth16u->imageData,nikinect.depthMD.Data(),640*480*2);//数据拷贝
//将深度数据复制到imgDepth16U.imageData,图像大小为640*480,2通道(2个字节)
cvConvertScale(imgDepth16u,depthShow,255/4096.0,0);
//使用线形变换转换数据,255/4096比例压缩,转换到0-255范围
cvShowImage("depth", depthShow);
/*计算深度图像的canny边缘并显示*/
Mat depth_image(nikinect.depthMD.YRes(), nikinect.depthMD.XRes(),
CV_16UC1, (char *)nikinect.depthMD.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据
depth_image.convertTo(image_edge, CV_8U, 255.0/8000);
blur(image_edge,image_edge,Size(7,7));
Canny(image_edge,image_edge,5,100);
imshow("depth to canny",image_edge);
/*获取并显示色彩图像*/
memcpy(imgRGB8u->imageData,nikinect.imageMD.Data(),640*480*3);
cvCvtColor(imgRGB8u,imageShow,CV_RGB2BGR);
//色彩空间转换,将RGB存储方式转换为BGR
cvShowImage("image",imageShow);
/*对色彩图像进行canny边缘检测并显示*/
Mat color_image(nikinect.imageMD.YRes(), nikinect.imageMD.XRes(),
CV_8UC3, (char *)nikinect.imageMD.Data());
cvtColor(color_image,image_gray,CV_RGB2GRAY);
blur(image_gray,image_gray,Size(7,7));
Canny(image_gray,image_gray,5,100);
imshow("images to canny",image_gray);
key=cvWaitKey(20);//不断刷新图像,频率时间为20ms,返回值为当前键盘按键值
}
//destroy
cvDestroyWindow("depth");//关闭窗口
cvDestroyWindow("image");
cvReleaseImage(&imgDepth16u);//释放内存空间
cvReleaseImage(&imgRGB8u);
cvReleaseImage(&depthShow);
cvReleaseImage(&imageShow);
nikinect.Stop();
}
CMakeLists.txt文件代码如下:
cmake_minimum_required(VERSION 2.8)
project(2_hellokinect03)
# 寻找OpenCV库
find_package( OpenCV REQUIRED )
# 添加头文件
include_directories( ${OpenCV_INCLUDE_DIRS} )
include_directories ("/usr/include/ni/" )
add_executable(2_hellokinect03 main.cpp)
target_link_libraries( 2_hellokinect03 OpenNI ${OpenCV_LIBS})
install(TARGETS 2_hellokinect03 RUNTIME DESTINATION bin)
2、编译运行
编译运行出来的界面如下:
3、代码解释
在此不做过多的解释,都是以往的内容,主要是opencv的canny边缘检测函数。
/*计算深度图像的canny边缘并显示*/
Mat depth_image(nikinect.depthMD.YRes(), nikinect.depthMD.XRes(),CV_16UC1, (char *)nikinect.depthMD.Data());
//因为kinect获取到的深度图像实际上是无符号的16位数据
depth_image.convertTo(image_edge, CV_8U, 255.0/8000);
blur(image_edge,image_edge,Size(7,7));
Canny(image_edge,image_edge,5,100);
imshow("depth to canny",image_edge);
/*对色彩图像进行canny边缘检测并显示*/
Mat color_image(nikinect.imageMD.YRes(), nikinect.imageMD.XRes(),CV_8UC3, (char *)nikinect.imageMD.Data());
cvtColor(color_image,image_gray,CV_RGB2GRAY);
blur(image_gray,image_gray,Size(7,7));
Canny(image_gray,image_gray,5,100);
imshow("images to canny",image_gray);
主要是MAT数据格式,blur()函数和canny()函数三个知识点。
【1】blur()函数–降噪(详见【opencv 一日一练】 api 之 blur )
函数原型【低通滤波】
C++: void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
参数详解如下:
第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可。该函数对通道是独立处理的,且可以处理任意通道数的图片,但需要注意,待处理的图片深度应该为CV_8U, CV_16U, CV_16S, CV_32F 以及 CV_64F之一。
第二个参数,OutputArray类型的dst,即目标图像,需要和源图片有一样的尺寸和类型。比如可以用Mat::Clone,以源图片为模板,来初始化得到如假包换的目标图。
第三个参数,Size类型(对Size类型稍后有讲解)的ksize,内核的大小。一般这样写Size( w,h )来表示内核的大小( 其中,w 为像素宽度, h为像素高度)。Size(3,3)就表示3x3的核大小,Size(5,5)就表示5x5的核大小
第四个参数,Point类型的anchor,表示锚点(即被平滑的那个点),注意他有默认值Point(-1,-1)。如果这个点坐标是负值的话,就表示取核的中心为锚点,所以默认值Point(-1,-1)表示这个锚点在核的中心。
第五个参数,int类型的borderType,用于推断图像外部像素的某种边界模式。有默认值BORDER_DEFAULT,我们一般不去管它。
【2】canny()函数–边缘检测(详见【OpenCV入门指南】第三篇Canny边缘检测 )
函数功能:采用Canny方法对图像进行边缘检测,Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法。
函数原型:
void cvCanny(const CvArr image, CvArr* edges,double threshold1,double threshold2,int aperture_size=3);*
参数说明:
第一个参数表示输入图像,必须为单通道灰度图。
第二个参数表示输出的边缘图像,为单通道黑白图。
第三个参数和第四个参数表示阈值,这二个阈值中当中的小阈值用来控制边缘连接,大的阈值用来控制强边缘的初始分割即如果一个像素的梯度大与上限值,则被认为是边缘像素,如果小于下限阈值,则被抛弃。如果该点的梯度在两者之间则当这个点与高于上限值的像素点连接时我们才保留,否则删除。
第五个参数表示Sobel 算子大小,默认为3即表示一个3*3的矩阵。Sobel 算子与高斯拉普拉斯算子都是常用的边缘算子,详细的数学原理可以查阅专业书籍。
【3】opencv中的mat数据类型,暂时不能透彻理解,需要多看相关资料,此处列出一篇相关文章来结束这次的学习吧,详见:
OpenCV Mat数据类型及位数总结 。待学明白后,再做相关解释……