Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别)
前言
因为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自带的手势识别类的使用有了初步的了解。
参考资料:
附录:实验工程code下载。