【原创】 NUI Kinect OpenNI Nite 追踪 玩家 骨骼 流程

http://nuihq.com/wordpress/?p=47

搬家中, 清关注 nuihq.com

 

nuihq.com qt nui ni ui kinect primesense WAVI Xtion 体感

用语约定:

  • 玩家:程序开发的终端用户, 在设备前玩游戏的家伙

对玩家骨骼的追踪是Nite中的重要功能之一, 下面我就为大家简单分析以下Nite自带的 “Players” 这个例子的主要流程

简化代码

要分析代码要现精简代码, 将主要的流程提取出来, 进行分析, 例子的核心文件只有三个, main.cpp, ScreenDrawer.h, ScreenDrawer.cpp, 因为我们只是分析追踪的流程, 因此画图部分可以略去, 首先分析 main.cpp

  • 首先我们要剥离的是有关画图显示的代码, 这样, 关于gl的代码我们就可以跳过
  • 然后关于这个例子中有一个玩家会话控制的功能, 即玩家短暂的离开后再次回来可以让程序很快地恢复校准, 同时为上层的游戏提供一个“中间层”, 将先后加入或更换的玩家对应到游戏中的角色,会话控制的代码是写例子的人自己的实现, 也可以不看(说实话我真的没看懂), 就是 AssignPlayer , FindPlayer, LostPlayer, 这几个函数
  • 接着可以略去的代码是有关深度图录像的函数, StopCapture 和 StartCapture

从入口函数入手

  • 首先是从xml创建环境, 然后初始化并检查生产节点, 接着检查生产节点的能力, 这部分使用了上边定义的宏, 用来检查错误并退出main函数。
  • 接着开始绑定回调函数, 将玩家事件, 校准事件绑定到对应的处理函数,

程序的流程

OpenNI 的数据获取和处理都是在他的更新函数 g_Context.WaitOneUpdateAll(g_DepthGenerator); 中进行的, 你绑定的回调函数也是在这个函数中被调用的, OpenNI有多种更新函数, 具体差别看文档~在更新函数中, OpenNI 等待并搜集所有的数据, 和之前缓存的数据一同进行处理, 如果符合某个事件的条件, 则调取你设定的回调。 这个函数一般要循环调用以持续对数据进行更新, 在官方的例子中, 他是使用opengl的显示循环进行循环调用的。接下来我们针对例子进行分析:

  • 当一个玩家出现在kinect的“视野”中后, openni会对其进行识别, 如果发现这“东西”像是个人, 则调用例子中的 NewUser 函数, 例子中 NewUser 会使用 g_UserGenerator.GetPoseDetectionCap().StartPoseDetection(“Psi”, user); 函数开启这个玩家的姿势检测, 检测的姿势叫做 “Psi”, 这个姿势就是我们说的 “投降” 姿势, 双手举过头顶~
  • 如果此时玩家做出了 “投降” 姿势, 则 PoseDetected 这个回调会被调用, 在这个函数中, 例子使用g_UserGenerator.GetSkeletonCap().RequestCalibration(user, TRUE); 请求对玩家进行 “校准”, 就是对其尝试进行骨骼模式匹配, 同时例子使用 g_UserGenerator.GetPoseDetectionCap().StopPoseDetection(user); 停止玩家的姿势检测, 我实验了一下这个地方如果不停止姿势检测的话也是没有问题的, 但是如果不需要再检测玩家姿势的话就停掉吧, 毕竟可以省点资源嘛
  • 之后openni会用一段时间对玩家进行骨骼模式匹配, 匹配不一定能够成功, 和玩家出现在kinect设备视野中的完整程度有关, 有过完整出现的话基本是可以顺利识别的。 当匹配完成后, 无论成功失败, 都会调用 CalibrationCompleted(xn::SkeletonCapability& skeleton, XnUserID user, XnCalibrationStatus eStatus, void* cxt) 这个函数, 我们可以看到这个函数中有对校准结果的检测, XN_CALIBRATION_STATUS_OK 代表成功, 成功的话, 例子调用了 g_UserGenerator.GetSkeletonCap().StartTracking(user) 开始对此用户进行骨骼追踪
  • 至此, 就可以获取用户的骨骼数据了, 在 ScreenDrawer.cpp 中的 DrawLimb(XnUserID player, XnSkeletonJoint eJoint1, XnSkeletonJoint eJoint2) 函数中, g_UserGenerator.GetSkeletonCap().GetSkeletonJointPosition(player, eJoint1, joint1), eJoint1 这个参数是从下边的DrawDepthMap函数的后边传过来的, 分别代表身体关节, 可以的值有

XN_SKEL_HEAD,
XN_SKEL_NECK,
XN_SKEL_LEFT_SHOULDER, XN_SKEL_RIGHT_SHOULDER,
XN_SKEL_LEFT_ELBOW, XN_SKEL_RIGHT_ELBOW,
XN_SKEL_LEFT_HAND, XN_SKEL_RIGHT_HAND,
XN_SKEL_TORSO,
XN_SKEL_LEFT_HIP, XN_SKEL_RIGHT_HIP,
XN_SKEL_LEFT_KNEE, XN_SKEL_RIGHT_KNEE,
XN_SKEL_LEFT_FOOT, XN_SKEL_RIGHT_FOOT

注意和改进

写到这里, 基本的流程就分析完了, 程序中有些问题

  • 这里要注意一下, 由于openni是用C写的, 所以如果你要使用C++进行开发的话你的回调函数要使用静态成员函数, 或使用非成员函数, 这样的话才可以在编译时确定函数的地址, 进行绑定
  • 例子程序有个缺点, 就是一个玩家校准失败后, 就不能再进行校准, 因此可以将 main.cpp CalibrationCompleted 函数修改如下
    if (eStatus == XN_CALIBRATION_STATUS_OK)
    {
    	...... // 原有代码
    }else
    {
           printf("请求重新校准 %d 号玩家!\n", int(user));
           g_UserGenerator.GetSkeletonCap().RequestCalibration(user, false); // 重新请求校准
    }
    
  • 还有个小问题, 就是 main.cpp CalibrationEnded 这个函数是没有用的~, 下边回调中并没有绑定, 整个程序中也没有用到

你可能感兴趣的:(kinect,nui,openni,nite)