Kinect+OpenNI学习笔记之6获取人体骨架并在Qt中显示.docx
- 文档编号:6938798
- 上传时间:2023-01-12
- 格式:DOCX
- 页数:21
- 大小:321.21KB
Kinect+OpenNI学习笔记之6获取人体骨架并在Qt中显示.docx
《Kinect+OpenNI学习笔记之6获取人体骨架并在Qt中显示.docx》由会员分享,可在线阅读,更多相关《Kinect+OpenNI学习笔记之6获取人体骨架并在Qt中显示.docx(21页珍藏版)》请在冰豆网上搜索。
Kinect+OpenNI学习笔记之6获取人体骨架并在Qt中显示
前言
MS的kinecSDK和OpenNI都提供了人体骨骼跟踪的算法,人体骨骼跟踪算法在kinect人体行为识别中非常重要,该识别过程通常被用来作为行为识别的第一步,比如说,通过定位人体中的骨骼支架,可以提取出人手的部位,从而可以把手的部分单独拿出来分析,这样就达到了手势的定位,而后面的手势识别则可以在刚刚定位出的领域进行处理。
总而言之,一套有效的人体骨架追踪算法在kinect的一系列应用中非常有用,不过MSSDK和OpenNI虽然都提供了该算法类的直调用,但是其源码并没有开放,毕竟这是人家最核心的东东。
开发环境:
QtCreator2.5.1+OpenNI1.5.4.0+Qt4.8.2
实验说明
在老版本的OpenNI中,要对人进行骨架追踪,需要人先摆出PSI的姿势,然后系统根据该姿势进行骨骼校正,待校正完成后才进行骨骼的跟踪,其流程图可以参考下面的图:
由图可以看出,其完成骨骼跟踪主要分为3个部分,首先需检测到人体,然后需要固定的PSI姿势来对人体的姿势进行校正,待姿势校正完成后,才能进行人体骨骼的追踪。
如果程序开发者用代码实现该过程则可以参考hersey的文章透过OpenNI/NITE分析人体骨架(上)和透过OpenNI/NITE分析人体骨架(下),作者在这2篇文章详细介绍了老版本的人体骨架的OpenNI实现。
在新版本OpenNI1.5以后,人体骨架追踪算法更改了不少,其中最大的特点就是骨架跟踪过程中少了姿势校正的那一步骤,新版本中只需要人体站起来就可以进行跟踪了,使用起来方便很多,程序开发也简单不少。
另外人体骨骼跟踪的效果也提高了不少,一旦骨骼追踪成功后,即使人体没有保持站立姿势有时候也还是可以继续跟踪的。
新版本的人体骨骼跟踪算法使用流程图如下:
下面来看看程序中的Capability,它不同于前面文章的generator:
在进行骨架的判断和姿态检测是需要用到OpenNI延伸的功能,与这种延伸功能相关的类可以称作为Capability。
在进行人体骨骼分析时,usergenerator需要有支援Skeleton和PoseDetection这2个的capability。
在程序中需要绘制骨骼节点之间的连线,而节点的坐标和法向都有函数可以获得,获得的坐标为真实世界中的坐标,画图时需要在平面上绘制,因此需要这2个坐标系的转换,转换过程用到下面的函数:
XnStatusxn:
:
DepthGenerator:
:
ConvertRealWorldToProjective(XnUInt32nCount,constXnPoint3DaRealWorld[],XnPoint3DaProjective[])
该函数表示将深度图获取的真实坐标系转换成平面图形显示的投影坐标系上。
第1个参数表示转换坐标点的个数,第2个参数表示真实坐标系中的坐标,第3个参数表示投影坐标系下的坐标。
本实验的程序分为3个类和一个主函数,其中2个类的基本部分在前面的文章中已有介绍,只需要更新其部分功能。
下面是本实验中这3个类的设计。
当然这都是参考heresy的博客使用Qt显示OpenNI的人体骨架。
COpenNI类的更新:
因为需要对人体进行骨骼跟踪,所以需要用到OpenNI的UserGenerator这个类。
在private变量一栏增加这个类对象的声明。
然后在类的Init()函数中使用Create方法产生人体的node。
同上一篇博客Kinect+OpenNI学习笔记之5(使用OpenNI自带的类进行简单手势识别)中类似,这里的人体骨架校正,跟踪等都是通过回调函数的形式进行的,因此还需要在Init()函数中设置这个node的检测到有新人进入和骨骼校正完成的回调函数(其实还有旧人体目标离去,骨骼校正开始这2个也可以设置回调函数,但在本程序中因为不需要使用它们,因此可以省略不写,老版本的OpenNI是不允许省略的)。
另外,由于色彩节点,深度节点,以及人体检测节点都是私有变量,如果该类的对象需要获取该变量的话不方便,因此在共有函数部分分别设置了3个共有函数来获取这3个变量。
具体该类的全部代码参加本文后面的代码部分。
CSkeletonItem类的设计:
CSkeletonItem这个类主要来完成骨架节点位置的获取,以及画出item中节点之间的连线,同时也在节点位置处画出圆圈代表对应节点的位置。
在构造函数中,设计了一个二维的连接表矩阵,矩阵的大小为14*2,即有14条边,每条边有2个顶点,矩阵中对应位置的值表示的是对应边的节点骨架的标号,在OpenNI中人体的骨架节点共分为15个,手脚共12个,头部2个,躯干1个。
如下图所示:
程序中对这15个点编了序号,头部为0,颈部为1,躯干为2,左肩膀为3,左手肘为4,左手腕5,右肩膀为6,右手肘为7,右手腕为8,左臀为9,左膝盖为10,左脚跟为11,右臀为12,右膝盖为13,右脚跟为14。
该类中需要重写的boundingRect()函数,函数中设置了一个包含15个节点的最小矩形,因为后面的绘图区域需要在这个矩形内进行,很明显,获得的这个矩形不是固定大小的,而是根据人体骨架的位置在不断变化。
大小和位置同时都会发生改变。
重写的paint()函数则需要完成2个部分的功能,第一是画出骨骼中节点的位置,用圆圈显示;第二是画出2个节点之间的连线,共14条,这样通过画出的连线就可以大概看出人的位置和区域了。
本文是参考的heresy文章不用校正姿势的NITE1.5,heresy在设计该类的构造函数时,设计了个15*2的连接表,个人感觉设置为14*2比较合理,因为15个点刚好由14条线可以连接起来,并不是heresy所说的15条线,其实它有2条线是重合的。
CKinectReader类的更新:
该类是在前面的文章Kinect+OpenNI学习笔记之3(获取kinect的数据并在Qt中显示的类的设计)中对应类的更新,前面博文中的该类只是完成了深度图像和颜色图像的显示,而在本实验中,需要完成显示骨架节点之间的连线图,因此该类需要继续更新。
其实现过程主要是获取视野中人体的个数,对检测到的每个人体然后调用CSkeletonItem类中的方法UpdateSkeleton()来更新读取的节点坐标,因为一旦坐标值发生了改变,CSkeletonItem类中的boundingRect()内容也会更改,从而其Item所在区域的矩形也会变化,最后导致paint()函数的执行,在paint()函数中完成骨骼节点连线和骨骼节点的绘图。
实验结果
试验效果的截图:
蓝色的线表示骨骼节点之间的连线,黄色的圈表示骨骼节点。
实验主要部分代码及注释(附录有实验工程code下载链接地址):
copenni.cpp:
#ifndefCOPENNI_CLASS
#defineCOPENNI_CLASS
#include
#include
#include
usingnamespacexn;
usingnamespacestd;
classCOpenNI
{
public:
~COpenNI(){
context.Release();//释放空间
}
boolInitial(){
//初始化
status=context.Init();
if(CheckError("Contextinitialfailed!
")){
returnfalse;
}
context.SetGlobalMirror(true);//设置镜像
xmode.nXRes=640;
xmode.nYRes=480;
xmode.nFPS=30;
//产生颜色node
status=image_generator.Create(context);
if(CheckError("Createimagegeneratorerror!
")){
returnfalse;
}
//设置颜色图片输出模式
status=image_generator.SetMapOutputMode(xmode);
if(CheckError("SetMapOutputMdoeerror!
")){
returnfalse;
}
//产生深度node
status=depth_generator.Create(context);
if(CheckError("Createdepthgeneratorerror!
")){
returnfalse;
}
//设置深度图片输出模式
status=depth_generator.SetMapOutputMode(xmode);
if(CheckError("SetMapOutputMdoeerror!
")){
returnfalse;
}
//产生手势node
status=gesture_generator.Create(context);
if(CheckError("Creategesturegeneratorerror!
")){
returnfalse;
}
/*添加手势识别的种类*/
gesture_generator.AddGesture("Wave",NULL);
gesture_generator.AddGesture("click",NULL);
gesture_generator.AddGesture("RaiseHand",NULL);
gesture_generator.AddGesture("MovingHand",NULL);
//产生人体node
status=user_generator.Create(context);
if(CheckError("Creategesturengeneratorerror!
")){
returnfalse;
}
//视角校正
status=depth_generator.GetAlternativeViewPointCap().SetViewPoint(image_generator);
if(CheckError("Can'tsetthealternativeviewpointondepthgenerator!
")){
returnfalse;
}
//设置有人进入视野的回调函数
XnCallbackHandlenew_user_handle;
user_generator.RegisterUserCallbacks(CBNewUser,NULL,NULL,new_user_handle);
user_generator.GetSkeletonCap().SetSkeletonProfile(XN_SKEL_PROFILE_ALL);//设定使用所有关节(共15个)
//设置骨骼校正完成的回调函数
XnCallbackHandlecalibration_complete;
user_generator.GetSkeletonCap().RegisterToCalibrationComplete(CBCalibrationComplete,NULL,calibration_complete);
returntrue;
}
boolStart(){
status=context.StartGeneratingAll();
if(CheckError("Startgeneratingerror!
")){
returnfalse;
}
returntrue;
}
boolUpdateData(){
status=context.WaitNoneUpdateAll();
if(CheckError("Updatedateerror!
")){
returnfalse;
}
//获取数据
image_generator.GetMetaData(image_metadata);
depth_generator.GetMetaData(depth_metadata);
returntrue;
}
//得到色彩图像的node
ImageGenerator&getImageGenerator(){
returnimage_generator;
}
//得到深度图像的node
DepthGenerator&getDepthGenerator(){
returndepth_generator;
}
//得到人体的node
UserGenerator&getUserGenerator(){
returnuser_generator;
}
public:
DepthMetaDatadepth_metadata;
ImageMetaDataimage_metadata;
GestureGeneratorgesture_generator;//外部要对其进行回调函数的设置,因此将它设为public类型
private:
//该函数返回真代表出现了错误,返回假代表正确
boolCheckError(constchar*error){
if(status!
=XN_STATUS_OK){
QMessageBox:
:
critical(NULL,error,xnGetStatusString(status));
cerr< "< returntrue; } returnfalse; } //有人进入视野时的回调函数 staticvoidXN_CALLBACK_TYPECBNewUser(UserGenerator&generator,XnUserIDuser,void*p_cookie){ //得到skeleton的capability,并调用RequestCalibration函数设置对新检测到的人进行骨骼校正 generator.GetSkeletonCap().RequestCalibration(user,true); } //完成骨骼校正的回调函数 staticvoidXN_CALLBACK_TYPECBCalibrationComplete(SkeletonCapability&skeleton, XnUserIDuser,XnCalibrationStatuscalibration_error,void*p_cookie){ if(calibration_error==XN_CALIBRATION_STATUS_OK){ skeleton.StartTracking(user);//骨骼校正完成后就开始进行人体跟踪了 } else{ UserGenerator*p_user=(UserGenerator*)p_cookie; skeleton.RequestCalibration(user,true);//骨骼校正失败时重新设置对人体骨骼继续进行校正 } } private: XnStatusstatus; Contextcontext; DepthGeneratordepth_generator; ImageGeneratorimage_generator; UserGeneratoruser_generator; XnMapOutputModexmode; }; #endif cskeletonitem.cpp: #ifndefCSKELETONITEM_CLASS #defineCSKELETONITEM_CLASS #include #include #include"copenni.cpp" classCSkeletonItem: publicQGraphicsItem { public: /*构造函数*/ CSkeletonItem(XnUserID&user_id,COpenNI&openni): QGraphicsItem(),user_id(user_id), openni(openni){ /*创建关节相连的二维表 connections[i]表示第i条线(2个节点之间表示一条线),connections[i][0]和connections[i][1]分别表示 第i条线的2个端点*/ //头部和身体的2条线 { connections[0][0]=0; connections[0][1]=1; connections[1][0]=1; connections[1][1]=2; } //左手的3条线 { connections[2][0]=1; connections[2][1]=3; connections[3][0]=3; connections[3][1]=4; connections[4][0]=4; connections[4][1]=5; } //右手的3条线 { connections[5][0]=1; connections[5][1]=6; connections[6][0]=6; connections[6][1]=7; connections[7][0]=7; connections[7][1]=8; } //左腿的3条线 { connections[8][0]=2; connections[8][1]=9; connections[9][0]=9; connections[9][1]=10; connections[10][0]=10; connections[10][1]=11; } //右腿的3条线 { connections[11][0]=2; connections[11][1]=12; connections[12][0]=12; connections[12][1]=13; connections[13][0]=13; connections[13][1]=14; } } /*更新skeleton里面的数据,分别获得15个节点的世界坐标,并转换成投影坐标*/ voidUpdateSkeleton(){ XnPoint3Djoints_realworld[15]; joints_realworld[0]=getSkeletonPos(XN_SKEL_HEAD); joints_realworld[1]=getSkeletonPos(XN_SKEL_NECK); joints_realworld[2]=getSkeletonPos(XN_SKEL_TORSO); joints_realworld[3]=getSkeletonPos(XN_SKEL_LEFT_SHOULDER); joints_realworld[4]=getSkeletonPos(XN_SKEL_LEFT_ELBOW); joints_realworld[5]=getSkeletonPos(XN_SKEL_LEFT_HAND); joints_realworld[6]=getSkeletonPos(XN_SKEL_RIGHT_SHOULDER); joints_realworld[7]=getSkeletonPos(XN_SKEL_RIGHT_ELBOW); joints_realworld[8]=getSkeletonPos(XN_SKEL_RIGHT_HAND); joints_realworld[9]=getSkeletonPos(XN_SKEL_LEFT_HIP); joints_realworld[10]=getSkeletonPos(XN_SKEL_LEFT_KNEE); joints_realworld[11]=getSkeletonPos(XN_SKEL_LEFT_FOOT); joints_realworld[12]=getSkeletonPos(XN_SKEL_RIGHT_HIP); joints_realworld[13]=getSkeletonPos(XN_SKEL_RIGHT_KNEE); joints_realworld[14]=getSkeletonPos(XN_SKEL_RIGHT_FOOT); //将世界坐标系转换成投影坐标系,一定要使用深度信息的节点 openni.getDepthGenerator().ConvertRealWorldToProjective(15,joints_realworld,joints_project); } public: COpenNI&openni; XnUserID&user_id;//每个CSkeletonItem对应一个人体 XnPoint3Djoints_project[15];//15个关节点的坐标 intconnections[14][2]; //intconnections[15][2]; private: XnPoint3DgetSkeletonPos(XnSkeletonJointjoint_name){ XnSkeletonJointPositionpos;//关节点的坐标 //得到指定关节名称的节点的坐标,保存在pos中 openni.getUserGenerator().GetSkeletonCap().GetSkeletonJointPosition(user_id,joint_name,pos); returnxnCreatePoint3D(pos.position.X,pos.position.Y,pos.position.Z);//以3维坐标的形式返回节点的坐标 } //boudintRect函数的重写 QRectFboundingRect()const{ QRectFrect(joints_project[0].X,joints_project[0].Y,0,0);//定义一个矩形外围
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Kinect OpenNI 学习 笔记 获取 人体 骨架 Qt 显示
![提示](https://static.bdocx.com/images/bang_tan.gif)