首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > VC/MFC >

OpenNI 的简介与初步应用(4),该如何处理

2012-03-31 
OpenNI 的简介与初步应用(4)出处:http://kheresy.wordpress.com/2011/01/28/detecte_skeleton_via_openni_

OpenNI 的简介与初步应用(4)
出处:http://kheresy.wordpress.com/2011/01/28/detecte_skeleton_via_openni_part1/
作者:Heresy
前面几篇文章,基本上都是单纯地读取 OpenNI 里的原始影像数据(深度/彩色),并没有额外去做其它进一步的处理;而这一篇的,则是直接进入可能比较实用、也比较特别的部分,要去读取使用 NITE 这套 middleware 分析出来的人体骨架资料了

不过我觉得 OpenNI 在这方面的资料其实相对少了许多,目前我自己主要是根据 NITE 的范例程序「StickFigure」来做参考的,而整体来说,也算是摸到能正常运作而已;所以如果要看更完整的程序,也可以找这个官方的范例原始码来看看。
接下来的部分,就是我自己对于这部分摸出来的一些东西了。

OpenNI 人体骨架的构成
首先,OpenNI 的人体骨架基本上是由「关节」(joint)来构成的,而每一个关节都有位置(position)和方向(orientation)两种资料;同时,这两者也都还包含了对于这个值的「信赖度」(confidence),可以让程序开发者知道 middleware 所判断出来的这个关节信息有多大的可信度。

而在目前的 OpenNI 里,他以列举型别的方式总共定义了 24 个关节(XnSkeletonJoint),分别是:
?XN_SKEL_HEAD , XN_SKEL_NECK, 
  XN_SKEL_TORSO,XN_SKEL_WAIST 
?XN_SKEL_LEFT_COLLAR, [color=#FF0000XN_SKEL_LEFT_SHOULDER[/color], 
XN_SKEL_LEFT_ELBOW,XN_SKEL_LEFT_WRIST, 
XN_SKEL_LEFT_HAND,XN_SKEL_LEFT_FINGERTIP 
?XN_SKEL_RIGHT_COLLAR, XN_SKEL_RIGHT_SHOULDER, 
XN_SKEL_RIGHT_ELBOW, XN_SKEL_RIGHT_WRIST, 
XN_SKEL_RIGHT_HAND, XN_SKEL_RIGHT_FINGERTIP 
?XN_SKEL_LEFT_HIP, XN_SKEL_LEFT_KNEE, 
XN_SKEL_LEFT_ANKLE, XN_SKEL_LEFT_FOOT
?XN_SKEL_RIGHT_HIP, XN_SKEL_RIGHT_KNEE, 
XN_SKEL_RIGHT_ANKLE, XN_SKEL_RIGHT_FOOT
不过虽然 OpenNI 定义了这么多的关节,但是实际上在透过 NITE 这个 middleware 分析骨架时,其实能用的只有上面标记成红色的十五个(可以透过 xn::SkeletonCapability 的成员函式 EnumerateActiveJoints() 取得可用的关节列表)。而如果把 NITE 有支持的关节画成图的话,就是下面的样子了~

而如果再把这些关节连成线画出来,结果就会是类似本文最上方的那张图里的样子了。

建立人体骨架的基本流程
要能够在 OpenNI 的环境里建立人体骨架,基本上是要靠所谓的 User Generator、也就是 xn::UserGenerator;而由于他的数据来源是深度影像(depth map),所以要使用的话,同时也要建立一个可用的 Depth Generator 出来才行。

不过我个人觉得比较奇怪的是,在 OpenNI 的文件中,有提及「Production Chain」这个概念(请参考《Kinect 的软件开发方案:OpenNI 简介》),但是在实际操作时,似乎没有提供建立这个链接的方法?以这边这个例子来说,似乎只要同时有 User Generator 以及 Depth Generator 这两种 production node,就会自动让 User Generator 去存取 Depth Generator 的数据;而这样的机制或许算是满方便,但是我比较好奇的是,如果存在两个不同的 Depth Generator 的话,那 User Generator 会去存取哪一个的资料?不过,由于我手边只有一个 Kinect,所以也没办法做测试了。

而 User Generator 的使用方法和之前介绍过的 depth generator 或 image generator 差比较多,他主要还必须要透过 callback function 的机制,来做事件(event)的处理;而在 OpenNI 里面,要为一个 production node 加上 callback function,基本上就是透过各种 node 提供、名称为 RegisterXXXCallbacks() 的成员函式,来「注册」(register)该 node 的 callback function;如果以这边要使用的 xn::UserGenerator 来说,就是 RegisterUserCallbacks() 这个函式了。

另外由于骨架的判断、以及判断骨架时需要的姿势侦测在 OpenNI 都是属于延伸功能的「Capability」,所以在这里所使用的 user generator 也必须要有支持 Skeleton 和 Pose Detection 这两个 capability 才行;不过现阶段所使用的 user generator 应该都是 NITE 所提供的,所以应该都会有支持。

前置的说明大概告了一个段落,接下来,就来看在 NITE 的 StickFigure 这个范例程序里,用来建立人体骨架的标准流程了~整个进行人体骨架分析的流程大致如下图所示,虽然可能不是很精确,但是应该算是可以用来说明了。

 在上方的流程图中,最左边的红色方块是代表 user generator(xn::UserGenerator),里面的「New User」和「Lost User」则是代表他的两个事件的 callback function;这两个函示分别会在「画面内侦测到新的使用者」、「使用者离开可侦测范围一段时间」时被呼叫。

而中间偏左的蓝色方块则是 pose detection 这个 capability(xn::PoseDetectionCapability)。在 user generator 侦测到有新的使用者、呼叫「New User」这个 callback function 时,「New User」的程序会去呼叫 pose detection 的「Start Pose Detection」、让 pose detection 开始侦测 NITE 预先定义的校正用姿势:「Psi」(如下图)。在呼叫「Start Pose Detection」前,pose detection 是不会进行姿势侦测的动作的。

当 pose detection 侦测到使用者摆出「Psi」这个姿势后(如上图),他就会去呼叫自己的「Pose Detected」这个 callback function、以进行下一阶段的动作;在这个例子里,「Pose Detected」会去做两件事,一个是去呼叫自己的「Stop Pose Detection」来停止继续侦测使用者的动作、另一个则是去呼叫 skeleton 这个 capability(xn::SkeletonCapability)的「Request Calibration」函式,要求 skeleton 开始进行人体骨架的校正、分析。

在 xn::SkeletonCapability 的「Request Calibration」被呼叫后,skeleton 就会开始进行骨架的校正、分析。当开始进行骨架校正的时候,skeleton 会去呼叫「Calibration Start」这个 callback function,让程序开发者可以知道接下来要开始进行骨架的校正了,如果有需要的话,可以在这边做一些前置处理;而当骨架校正完后,则是会去呼叫「Calibration End」这个 callback function。

不过,当「Calibration End」被呼叫的时候,只代表骨架的校正、辨识的阶段工作结束了,并不代表骨架辨识一定成功,也有可能是会失败的。如果成功的话,就是要进入下一个阶段、呼叫 xn::SkeletonCapability 的「StartTracking()」函式,让系统开始去追踪校正成功的骨架数据;而如果失败的话,则是要再让 pose detection 重新侦测校正姿势,等到有侦测到校正姿势后,再进行下一次的骨架校正。

而在骨架校正成功、并开始进行追踪骨架后,之后只要呼叫 xn::SkeletonCapability 用来读取关节资料的函式(例如 GetSkeletonJoint()),就可以读取到最新的关节相关信息,并建立整个人体的骨架数据了~



Callback Function 简单说明
如果在整个流程图里面仔细算一下的话,可以发现整个流程下来,总共有五个不同的 callback function,分别是 xn::UserGenerator 两个,以及 xn::PoseDetectionCapability 一个、xn::SkeletonCapability 两个;他们分别是:
?User Generator: 
oNew User、Lost User 
o两者形式皆为:void (XN_CALLBACK_TYPE* UserHandler)( UserGenerator& generator, XnUserID user, void* pCookie ) 
?Pose Detection Capability: 
oPose Detected 
o形式为:void (XN_CALLBACK_TYPE* PoseDetection)( PoseDetectionCapability& pose, const XnChar* strPose, XnUserID user, void* pCookie ) 
?Skeleton Capability: 
oCalibration Start、Calibration End 
o两者形式不同,分别为: 
void (XN_CALLBACK_TYPE* CalibrationStart)( SkeletonCapability& skeleton, XnUserID user, void* pCookie )
void (XN_CALLBACK_TYPE* CalibrationEnd)( SkeletonCapability& skeleton, XnUserID user, XnBool bSuccess, void* pCookie ) 

上面这五个 callback function,就是在进行人体骨架校正时,所需要用到的所有 callback fucntion、以及他们的形式了~而由于 OpenNI 有定义 XN_CALLBACK_TYPE 来定义 callback function 的 calling convention(参考 MSDN),所以在自己编写的 callback functions,也要用同样的形式。

不过,虽然这边列了五个 callback function,但是其实这些 callback function 在意义上,不见得是必须的;其中「Lost User」和「Calibration Start」实际上由于没有额外的动作,所以应该是没有必要性;但是由于目前版本的 OpenNI 在没有给这两个 callback 的情况下进行骨架的校正会让程序出问题,所以就算不想做任何事、也要给他一个空的 callback function,而不能给 NULL。这个在我来看,应该算是 OpenNI 现行版本的错误,只能希望之后的版本可以修正了。

程序代码
前面大致把整个人体骨架校正的流程都讲过了,接下来,就是看程序的部分了!下面的程序代码是我根据 NITE 的范例程序「StickFigure」来做简化、改写的,里面的输出只有用简单的文字输出,来显示目前的状态;如果想看有图形结果的版本,则可以直接去找 NITE 的范例来看。
PS:由于字数限制,这次只能分两部分来写~~

[解决办法]
不错,谢谢提供,很实用

热点排行