[转载]Kinect开发教程五:OpenNI获取人体骨架
http://blog.csdn.net/chenxin_130/article/details/6950480
临近毕业,小斤最近一直忙活着相关事宜,教程这边也搁浅了一阵。前几篇教程介绍了OpenNI的一些基本范例以及手势应用,但如果光用Kinect识别一些手势,总有点杀鸡用牛刀的感觉。在大部分体感应用中,获取骨架的步骤都不可缺少,这也是小斤一直想写的专题。
好了,废话不多说了,让我们进入正题吧!
在OpenNI库的enum XnSkeletonJoint中,定义了24个人体的关节,如下:
XN_SKEL_HEAD = 1, XN_SKEL_NECK = 2,
XN_SKEL_TORSO = 3, XN_SKEL_WAIST = 4,
XN_SKEL_LEFT_COLLAR = 5, XN_SKEL_LEFT_SHOULDER = 6,
XN_SKEL_LEFT_ELBOW = 7, XN_SKEL_LEFT_WRIST = 8,
XN_SKEL_LEFT_HAND = 9, XN_SKEL_LEFT_FINGERTIP =10,
XN_SKEL_RIGHT_COLLAR =11, XN_SKEL_RIGHT_SHOULDER =12,
XN_SKEL_RIGHT_ELBOW =13, XN_SKEL_RIGHT_WRIST =14,
XN_SKEL_RIGHT_HAND =15, XN_SKEL_RIGHT_FINGERTIP =16,
XN_SKEL_LEFT_HIP =17, XN_SKEL_LEFT_KNEE =18,
XN_SKEL_LEFT_ANKLE =19, XN_SKEL_LEFT_FOOT =20,
XN_SKEL_RIGHT_HIP =21, XN_SKEL_RIGHT_KNEE =22,
XN_SKEL_RIGHT_ANKLE =23, XN_SKEL_RIGHT_FOOT =24
小斤试下来,目前可使用的有14个关节,如下图:
先上代码:
- #include <stdlib.h>
- #include <iostream>
- #include <vector>
- #include <XnCppWrapper.h>
- #include <XnModuleCppInterface.h>
- #include "cv.h"
- #include "highgui.h"
- using namespace std;
- using namespace cv;
- //#pragma comment (lib,"cv210")
- //#pragma comment (lib,"cxcore210")
- //#pragma comment (lib,"highgui210")
- //#pragma comment (lib,"OpenNI")
- //【1】
- xn::UserGenerator userGenerator;
- xn::DepthGenerator depthGenerator;
- xn::ImageGenerator imageGenerator;
- /*
- XN_SKEL_HEAD = 1, XN_SKEL_NECK = 2,
- XN_SKEL_TORSO = 3, XN_SKEL_WAIST = 4,
- XN_SKEL_LEFT_COLLAR = 5, XN_SKEL_LEFT_SHOULDER = 6,
- XN_SKEL_LEFT_ELBOW = 7, XN_SKEL_LEFT_WRIST = 8,
- XN_SKEL_LEFT_HAND = 9, XN_SKEL_LEFT_FINGERTIP =10,
- XN_SKEL_RIGHT_COLLAR =11, XN_SKEL_RIGHT_SHOULDER =12,
- XN_SKEL_RIGHT_ELBOW =13, XN_SKEL_RIGHT_WRIST =14,
- XN_SKEL_RIGHT_HAND =15, XN_SKEL_RIGHT_FINGERTIP =16,
- XN_SKEL_LEFT_HIP =17, XN_SKEL_LEFT_KNEE =18,
- XN_SKEL_LEFT_ANKLE =19, XN_SKEL_LEFT_FOOT =20,
- XN_SKEL_RIGHT_HIP =21, XN_SKEL_RIGHT_KNEE =22,
- XN_SKEL_RIGHT_ANKLE =23, XN_SKEL_RIGHT_FOOT =24
- */
- //a line will be drawn between start point and corresponding end point
- int startSkelPoints[14]={1,2,6,6,12,17,6,7,12,13,17,18,21,22};
- int endSkelPoints[14]={2,3,12,21,17,21,7,9,13,15,18,20,22,24};
- // callback function of user generator: new user
- void XN_CALLBACK_TYPE NewUser( xn::UserGenerator& generator, XnUserID user,void* pCookie )
- {
- cout << "New user identified: " << user << endl;
- //userGenerator.GetSkeletonCap().LoadCalibrationDataFromFile( user, "UserCalibration.txt" );
- generator.GetPoseDetectionCap().StartPoseDetection("Psi", user);
- }
- // callback function of user generator: lost user
- void XN_CALLBACK_TYPE LostUser( xn::UserGenerator& generator, XnUserID user,void* pCookie )
- {
- cout << "User " << user << " lost" << endl;
- }
- // callback function of skeleton: calibration start
- void XN_CALLBACK_TYPE CalibrationStart( xn::SkeletonCapability& skeleton,XnUserID user,void* pCookie )
- {
- cout << "Calibration start for user " << user << endl;
- }
- // callback function of skeleton: calibration end
- void XN_CALLBACK_TYPE CalibrationEnd( xn::SkeletonCapability& skeleton,XnUserID user,XnCalibrationStatus calibrationError,void* pCookie )
- {
- cout << "Calibration complete for user " << user << ", ";
- if( calibrationError==XN_CALIBRATION_STATUS_OK )
- {
- cout << "Success" << endl;
- skeleton.StartTracking( user );
- //userGenerator.GetSkeletonCap().SaveCalibrationDataToFile(user, "UserCalibration.txt" );
- }
- else
- {
- cout << "Failure" << endl;
- //For the current version of OpenNI, only Psi pose is available
- ((xn::UserGenerator*)pCookie)->GetPoseDetectionCap().StartPoseDetection( "Psi", user );
- }
- }
- // callback function of pose detection: pose start
- void XN_CALLBACK_TYPE PoseDetected( xn::PoseDetectionCapability& poseDetection,const XnChar* strPose,XnUserID user,void* pCookie)
- {
- cout << "Pose " << strPose << " detected for user " << user << endl;
- ((xn::UserGenerator*)pCookie)->GetSkeletonCap().RequestCalibration( user, FALSE );
- poseDetection.StopPoseDetection( user );
- }
- void clearImg(IplImage* inputimg)
- {
- CvFont font;
- cvInitFont( &font, CV_FONT_VECTOR0,1, 1, 0, 3, 5);
- memset(inputimg->imageData,255,640*480*3);
- }
- int main( int argc, char** argv )
- {
- char key=0;
- int imgPosX=0;
- int imgPosY=0;
- // initial context
- xn::Context context;
- context.Init();
- xn::ImageMetaData imageMD;
- IplImage* cameraImg=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
- cvNamedWindow("Camera",1);
- // map output mode
- XnMapOutputMode mapMode;
- mapMode.nXRes = 640;
- mapMode.nYRes = 480;
- mapMode.nFPS = 30;
- // create generator
- depthGenerator.Create( context );
- depthGenerator.SetMapOutputMode( mapMode );
- imageGenerator.Create( context );
- userGenerator.Create( context );
- //【2】
- // Register callback functions of user generator
- XnCallbackHandle userCBHandle;
- userGenerator.RegisterUserCallbacks( NewUser, LostUser, NULL, userCBHandle );
- //【3】
- // Register callback functions of skeleton capability
- xn::SkeletonCapability skeletonCap = userGenerator.GetSkeletonCap();
- skeletonCap.SetSkeletonProfile( XN_SKEL_PROFILE_ALL );
- XnCallbackHandle calibCBHandle;
- skeletonCap.RegisterToCalibrationStart( CalibrationStart,&userGenerator, calibCBHandle );
- skeletonCap.RegisterToCalibrationComplete( CalibrationEnd,&userGenerator, calibCBHandle );
- //【4】
- // Register callback functions of Pose Detection capability
- XnCallbackHandle poseCBHandle;
- userGenerator.GetPoseDetectionCap().RegisterToPoseDetected( PoseDetected,&userGenerator, poseCBHandle );
- // start generate data
- context.StartGeneratingAll();
- while( key!=27 )
- {
- context.WaitAndUpdateAll();
- imageGenerator.GetMetaData(imageMD);
- memcpy(cameraImg->imageData,imageMD.Data(),640*480*3);
- cvCvtColor(cameraImg,cameraImg,CV_RGB2BGR);
- // get users
- XnUInt16 userCounts = userGenerator.GetNumberOfUsers();
- if( userCounts > 0 )
- {
- XnUserID* userID = new XnUserID[userCounts];
- userGenerator.GetUsers( userID, userCounts );
- for( int i = 0; i < userCounts; ++i )
- {
- //【5】
- // if is tracking skeleton
- if( skeletonCap.IsTracking( userID[i] ) )
- {
- XnPoint3D skelPointsIn[24],skelPointsOut[24];
- XnSkeletonJointTransformation mJointTran;
- for(int iter=0;iter<24;iter++)
- {
- //XnSkeletonJoint from 1 to 24
- skeletonCap.GetSkeletonJoint( userID[i],XnSkeletonJoint(iter+1), mJointTran );
- skelPointsIn[iter]=mJointTran.position.position;
- }
- depthGenerator.ConvertRealWorldToProjective(24,skelPointsIn,skelPointsOut);
- //【6】
- for(int d=0;d<14;d++)
- {
- CvPoint startpoint = cvPoint(skelPointsOut[startSkelPoints[d]-1].X,skelPointsOut[startSkelPoints[d]-1].Y);
- CvPoint endpoint = cvPoint(skelPointsOut[endSkelPoints[d]-1].X,skelPointsOut[endSkelPoints[d]-1].Y);
- cvCircle(cameraImg,startpoint,3,CV_#0000ff,12);
- cvCircle(cameraImg,endpoint,3,CV_#0000ff,12);
- cvLine(cameraImg,startpoint,endpoint,CV_#0000ff,4);
- }
- }
- }
- delete [] userID;
- }
- cvShowImage("Camera",cameraImg);
- key=cvWaitKey(20);
- }
- // stop and shutdown
- cvDestroyWindow("Camera");
- cvReleaseImage(&cameraImg);
- context.StopGeneratingAll();
- context.Shutdown();
- return 0;
- }
【1】 对于人体骨架的获取,小斤声明了UserGenerator这个生成器,UserGenerator具有检测新的User(以下称为人物)出现或者离开,获取画面中的人物数,人物位置信息,与上一教程介绍的GestureGenerator类似,通过注册回调函数的方式,一旦其检测到了动静(如人物出现),那么相应的回调函数就会被调用。
【2】 小斤为UserGenerator注册了NewUser和LostUser两个回调函数,对应人物出现和人物消失。
【3】 这里出现了一个新的Capability,SkeletonCapability。小斤为了避免混淆,常常将Capability理解为生成器的一种能力,比如SkeletonCapability就可以理解UserGenerator获取人物骨架信息的能力。
在获取人物骨架前,首先要进行标定的工作,因此SkeletonCapability需要注册两个回调函数CalibrationStart和CalibrationEnd,分别在人物标定开始与结束时调用。(在较早版本的OpenNI中,接口名可能有所变化)
【4】 与【3】类似,userGenerator.GetPoseDetectionCap()获取了一个PoseDetectionCapability,这个Capability可以检测人物的特定姿势,目前来说,只支持Psi姿势,如图:
小斤并为其注册了回调函数PoseDetected,在检测到人物的Psi姿势时,会调用该函数。
将【2】【3】【4】的回调函数串联起来看,(1)人物出现会触发NewUser(),开始Pose检测;(2)检测到Pose会触发PoseDetected(),请求标定;(3)标定开始触发CalibrationStart();(4)标定结束触发CalibrationEnd(),如果标定成功,那么调用SkeletonCapability的StartTracking()开始跟踪对应的人物。
【5】 通过GetSkeletonJoint()方法,可以得到对应关节的XnSkeletonJointTransformation,这个结构体包含position和orientation,position中又包含一个position和fConfidence,分别代表关节的位置和可信度,orientation同样如此,包含关节的运动方向和可信度。这里小斤对24个关节都进行了操作,但能得到位置信息的只有14个。
这些步骤得到的position信息,是一个真实场景的3D坐标,需要通过投影转换到屏幕坐标,转换过程通过ConvertRealWorldToProjective()方法实现。
【6】 为了更直观地输出显示,可以各个关节通过直线连接起来,形成一个人体的骨架。小斤定义了startSkelPoints和endSkelPoints数组,两个数组的值一一对应,代表一组起点终点的关节对,将每组起点和终点通过直线连接,比如HEAD与NECT与TORSO等。
整个程序启动后,先将身体正对摄像头(至少露出头部和上半身),控制台会显示“New user identified”,然后做出Psi姿势,在Pose Psi detected后,程序开始标定工作,此时维持Psi姿势数秒,标定成功后,骨架就会正确显示出来了。祝大家玩得愉快。
邮箱:steven9801@163.com
QQ: 48039387