前言
因为OpenNI可以获取到kinect的深度信息,而深度信息在手势识别中有很大用处,因此本文就来使用OpenNI自带的类来做简单的手势识别。识别的动作为4种,挥手,手移动,举手,往前推手。通过后面的实验可以发现,其实提供的类的效果非常不好。
开发环境:QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2+OpenCV2.4.2
实验说明
跟手势相关的是GestureGenerator这个类,它的初始化过程和depth_metadata,image_metadata都一样,因此首先在上2篇文章的COpenNI类中增加一个public类对象GestureGenerator gesture_generator;为什么不放在private里呢?因为我们的COpenNI对象需要调用这个变量来设置手势获取的一些属性,比如手势识别的种类等,总之就是这个变量外部需要能够访问得到,因此这里我将其放在public里面。另外在COpenNI类的Init()函数中需要加入下面的代码:
status = gesture_generator.Create(context); if(CheckError("Create gesture generator error!")) { return false; } /*添加手势识别的种类*/ gesture_generator.AddGesture("Wave", NULL); gesture_generator.AddGesture("click", NULL); gesture_generator.AddGesture("RaiseHand", NULL); gesture_generator.AddGesture("MovingHand", NULL);
OpenNI进行手势识别的方式是采用函数回调,即如果一个手势发生了或者正在发生时可以触发相应的回调函数,从而去执行回调函数,这有点类似于Qt中的信号与槽的关系。在OpenNI中设置回调函数的原型为:
XnStatus RegisterGestureCallbacks(GestureRecognized RecognizedCB, GestureProgress ProgressCB, void* pCookie, XnCallbackHandle& hCallback);
其中前2个参数为回调函数,第一个回调函数表示手部某个动作已经执行完毕,第二个参数表示收部某个动作正在执行;参数三为一个空指针,即可以指向任何数据类型的指针,其作用为给回调函数当额外的参数使用;参数四为回调函数的处理函数,用来记录和管理回调函数的。参数三在本实验中设置为NULL,参数四实际上本实验中也没有用到。
上面2个回调函数的名称可以自定义,但是这2个函数参数的个数和类型不能改变,这2个回调函数的参数个数都为5,但是其类型有些不同,具体的可以参考后面提供的代码。
由于在程序中添加了4种动作的捕捉,所以打算在检测到某个手势动作时,在窗口显示栏的图片上添加相应的手势动作文字提示。很明显,只有当手势检测到时才能在图片上添加文字,该部分在回调函数中实现。但是如果我们单独在回调函数中给图片添加相应的文字,然后在主程序中显示图片,则因为回调函数一结束完就回到了主函数的while循环中,而这时图片的内容已经更新了(即有文字的图片被重新覆盖了),因此人眼一瞬间看不到有文字提示的图片。最后个人的解决方法是用一个标志来表示检测到了某个手势动作,如果检测到了则显示存储下来的有文字的图片,反正,显示正常的图片。本程序提供的图片为深度图。
实验结果
举手的显示结果如下:
其实从本人的实验过程来看,大部分的手势动作都被检测为举手RaiseHand,少部分为挥手Wave,其它的基本上没出现过。说明OpenNI自带的手势识别类的功能不是很强。
实验主要部分代码及注释(附录有实验工程code下载链接):
copenni.cpp:
#include <XnCppWrapper.h> #include <QtGui/QtGui> #include <iostream> using namespace xn; using namespace std; class COpenNI { public: ~COpenNI() { context.Release();//释放空间 } bool Initial() { //初始化 status = context.Init(); if(CheckError("Context initial failed!")) { return false; } context.SetGlobalMirror(true);//设置镜像 //产生图片node status = image_generator.Create(context); if(CheckError("Create image generator error!")) { return false; } //产生深度node status = depth_generator.Create(context); if(CheckError("Create depth generator error!")) { return false; } //视角校正 status = depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator); if(CheckError("Can't set the alternative view point on depth generator!")) { return false; } status = gesture_generator.Create(context); if(CheckError("Create gesture generator error!")) { return false; } /*添加手势识别的种类*/ gesture_generator.AddGesture("Wave", NULL); gesture_generator.AddGesture("click", NULL); gesture_generator.AddGesture("RaiseHand", NULL); gesture_generator.AddGesture("MovingHand", NULL); return true; } bool Start() { status = context.StartGeneratingAll(); if(CheckError("Start generating error!")) { return false; } return true; } bool UpdateData() { status = context.WaitNoneUpdateAll(); if(CheckError("Update date error!")) { return false; } //获取数据 image_generator.GetMetaData(image_metadata); depth_generator.GetMetaData(depth_metadata); return true; } public: DepthMetaData depth_metadata; ImageMetaData image_metadata; GestureGenerator gesture_generator;//外部要对其进行回调函数的设置,因此将它设为public类型 private: //该函数返回真代表出现了错误,返回假代表正确 bool CheckError(const char* error) { if(status != XN_STATUS_OK ) { QMessageBox::critical(NULL, error, xnGetStatusString(status)); cerr << error << ": " << xnGetStatusString( status ) << endl; return true; } return false; } private: XnStatus status; Context context; DepthGenerator depth_generator; ImageGenerator image_generator; };
main.cpp:
#include <QCoreApplication> #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <opencv2/core/core.hpp> #include "copenni.cpp" #include <iostream> using namespace cv; using namespace xn; Mat depth_image; Mat depth_image_result;//深度结果图,且在该图上显示手势动作的类型 COpenNI openni; bool test_flag = false; // callback function for gesture recognized //回调函数,该函数的函数名字可以随便取,但是其参数的格式必须不能改变 //这里该函数的作用是表示上面4种手势发生完成后调用 void XN_CALLBACK_TYPE GRecognized ( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pIDPosition, const XnPoint3D *pEndPosition, void *pCookie ) { depth_image_result = depth_image.clone(); putText(depth_image_result, strGesture, Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 ); test_flag = true; } // callback function for gesture progress //该函数表示上面4种手势某一种正在发生时调用 void XN_CALLBACK_TYPE GProgress ( xn::GestureGenerator &generator, const XnChar *strGesture, const XnPoint3D *pPosition, XnFloat fProgress, void *pCookie ) { ; } int main (int argc, char **argv) { if(!openni.Initial()) return 1; XnCallbackHandle handle; openni.gesture_generator.RegisterGestureCallbacks(GRecognized, GProgress, NULL, handle); if(!openni.Start()) return 1; namedWindow("depth image", CV_WINDOW_AUTOSIZE); putText(depth_image, "YES!", Point(50, 150), 3, 0.8, Scalar(255, 0, 0), 2 ); while(1) { if(!openni.UpdateData()) { return 1; } /*获取并显示深度图像,且这2句代码不能放在回调函数中调用,否则后面的imshow函数会因为执行时找不到图片(因为此时回调函数不一定执行了)而报错*/ Mat depth_image_src(openni.depth_metadata.YRes(), openni.depth_metadata.XRes(), CV_16UC1, (char *)openni.depth_metadata.Data());//因为kinect获取到的深度图像实际上是无符号的16位数据 depth_image_src.convertTo(depth_image, CV_8U, 255.0/8000); if(!test_flag) imshow("depth image", depth_image); else imshow("depth image", depth_image_result); waitKey(30); test_flag = false; } }
错误总结
如果用Qt的控制台建立程序,运行程序时出现下面的错误提示:
这是因为控制台程序不能使用Qt的界面(本程序中使用了QMessageBox),因此需要在工程pro的代码中把QT – gui给去掉,否则会报类似的这种错误。
如果是在OpenCV中出现如下错误:
则表示是imshow函数需要还来不及显示完成就被其它的函数给中断了,这可能在回调函数中出现这种情况。
实验总结
通过本次实验对OpenNI自带的手势识别类的使用有了初步的了解。
参考资料:
OpenNI 的手势侦测
附录:实验工程code下载。