出处:http://kheresy.wordpress.com/2011/12/06/nite1-5_skeleton_without_calibration_pose/
作者:Heresy
前一阵子前有提过了,PrimeSense 在新推出的 1.5.x 的 NITE 中,终于让 user generator 可以不需要摆出 Psi 校正姿势、就直接进行人体的骨架追踪了~如此一来,要用 Kinect 透过 OpenNI 来做人体的骨架追踪,在程序的撰写上就可以稍微简单一些了!而这一篇,就大概来提一下,新版 user generator 要怎么用吧~
不过在开始之前,建议请先回去大概看过之前的介绍文章,这样应该会比较有些概念:
• 透过 OpenNI / NITE 分析人体骨架(上)、透过 OpenNI / NITE 分析人体骨架(下) 
• 使用 Qt 显示 OpenNI 的人体骨架 
• OpenNI 人体骨架分析部分补充、OpenNI XnSkeletonJointOrientation 简单分析 旧有程序可以不用改首先很重要的,新的 user generator 提供了不用校正姿势的人体骨架追踪后,已经写好、要使用 psi 校正姿势的程序还可以用吗?要不要修改呢?答案是不用,旧有的程序都还是可以再不修改程序代码的情况下,正常运作的!以之前《使用 Qt 显示 OpenNI 的人体骨架》一文的范例程序来说,不但程序代码可以不用修改,就连编译好的执行档,也都还是可以直接执行、使用的~
而此时,之前侦测到新的使用者后、需要此用 Pose detection 来侦测 Psi 校正姿势、并进行骨架校正的 callback function,也同样都会被正常地执行到。不过相对的,旧有的程序在没有经过修改的情况下,要进行骨架追踪,应该还是需要摆出骨架校正的 Psi 姿势的;不过,现在要的 Psi 判定变成非常地简单、而且校正的也相当地快速,所以其实还是满方便的~
 
新的程序比较简单
而如果希望不用摆出特定校正姿势的话,新的程序要怎么写呢?Heresy 这边是根据官方的 NiUserTracker 这个范例,来做简化以及修改的。
首先,如果想要知道目前系统的 User Generator 所提供的 Skeleton Capability 是否有提供不需要特定校正姿势的骨架追踪的话,可以使用 Skeleton Capability 的 NeedPoseForCalibration() 这个函式来做确认。大致用法就是像下面这样:
xn::UserGenerator mUser;
// ....
bool bNeedPose = mUser.GetSkeletonCap().NeedPoseForCalibration();
而如果确定 Skeleton Capability 有支持不需要特定校正姿势的话,就可以省略掉 Pose Detection 部分的程序了~新的流程,大致会如下图所示:
 
虽然好像还是要很多步骤,但是实际上和之前还需要使用 Pose Detection 的方法相比(流程图),其实已经算是简化相当多了~而实际上,虽然上面的流程图里有四个 callback function(New User、Lost User、Calibration Start、Calibration End),但是实际上,有必要一定要实作的,就只有 New User 和 Calibration End 这两个而已。
如此一来,本来《透过 OpenNI / NITE 分析人体骨架(上)》中的范例程序,则可以简化如下:
#include <stdlib.h>
#include <iostream>
#include <vector>#include <XnCppWrapper.h>using namespace std;
// 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;
  generator.GetSkeletonCap().RequestCalibration( user, true );
}// callback function of skeleton: calibration end 
void XN_CALLBACK_TYPE CalibrationEnd( xn::SkeletonCapability& skeleton,
                                      XnUserID user,
                                      XnCalibrationStatus eStatus,
                                      void* pCookie )
{
  cout << "Calibration complete for user " <<  user << ", ";
  if( eStatus == XN_CALIBRATION_STATUS_OK )
  {
    cout << "Success" << endl;
    skeleton.StartTracking( user );
  }
  else
  {
    cout << "Failure" << endl;
    skeleton.RequestCalibration( user, true );
  }
}
int main( int argc, char** argv )
{
  // 1. initial context
  xn::Context mContext;
  mContext.Init();  // 2. create user generator
  xn::UserGenerator mUserGenerator;
  mUserGenerator.Create( mContext );
// 3. Register callback functions of user generator
  XnCallbackHandle hUserCB;
  mUserGenerator.RegisterUserCallbacks( NewUser, NULL, NULL, hUserCB );  // 4. Register callback functions of skeleton capability
  xn::SkeletonCapability mSC = mUserGenerator.GetSkeletonCap();
  mSC.SetSkeletonProfile( XN_SKEL_PROFILE_ALL );
  XnCallbackHandle hCalibCB;
  mSC.RegisterToCalibrationComplete( CalibrationEnd, &mUserGenerator, hCalibCB );
  // 5. start generate data
  mContext.StartGeneratingAll();
  while( true )
  {
    // 6. Update date
    mContext.WaitAndUpdateAll();    // 7. get user information
    XnUInt16 nUsers = mUserGenerator.GetNumberOfUsers();
    if( nUsers > 0 )
    {
      // 8. get users
      XnUserID* aUserID = new XnUserID[nUsers];
      mUserGenerator.GetUsers( aUserID, nUsers );      // 9. check each user
      for( int i = 0; i < nUsers; ++i )
      {
        // 10. if is tracking skeleton
        if( mSC.IsTracking( aUserID[i] ) )
        {
          // 11. get skeleton joint data
          XnSkeletonJointTransformation mJointTran;
          mSC.GetSkeletonJoint( aUserID[i], XN_SKEL_HEAD, mJointTran );          // 12. output information
          cout << "The head of user " << aUserID[i] << " is at (";
          cout << mJointTran.position.position.X << ", ";
          cout << mJointTran.position.position.Y << ", ";
          cout << mJointTran.position.position.Z << ")" << endl;
        }
      }
      delete [] aUserID;
    }  }
  // 13. stop and shutdown
  mContext.StopGeneratingAll();
  mContext.Release();  return 0;
}
在上面的程序代码中,NewUser() 这个就是在侦测到有新的使用者时,会被执行的 callback function;而内容呢,就是直接去呼叫 xn::SkeletonCapability 的 RequestCalibration() 函式、要求他对于侦测到的新使用者(user)进行骨架的校正。
而接下来,xn::SkeletonCapability 就会试着去分析使用者的骨架、并进行校正的工作,等到骨架校正的动作完成后,就会呼叫 CalibrationEnd() 这个 callback function。而它的内容和本来的很接近,就是要先判断骨架校正的结果是否正常(XnCalibrationStatus 应该要是 XN_CALIBRATION_STATUS_OK),如果正常的话,就是接着透过 xn::SkeletonCapability 的 StartTracking() 函式、开始追踪 user 的骨架;而如果失败的话,则是重新透过 RequestCalibration()、要求 xn::SkeletonCapability 再度对 user 进行骨架的校正。
所以实际上,新的人体骨架的追踪架构,其实主要就是省略掉 Pose Detection 的部分而已了~再来,就是 NITE 内部的设计应该也做了不少修改,所以在效率上也好了不少。
而上面的程序,基本上是只有用 standard output 来输出结果而已,并没有任何的图形接口;如果希望看到有画面的范例程序的话,请参考这个使用 Qt 来做图形接口的范例,这只程序基本上是根据《使用 Qt 显示 OpenNI 的人体骨架》一文的范例程序修改而成的,在这边就不多做说明了。
________________________________________
这篇就写到这了。话说,本来我一直以为新版的 OpenNI 和 NITE 应该会在侦测到 user 后,就自动做骨架的校正、而不必额外再去做设定;不过现在看来,至少还是要自己去要求 xn::SkeletonCapability 进行骨架的校正与追踪的。
想想,其实这也算是合理的。毕竟,不是每个时候都有需要去知道画面内的使用者的骨架,如果 OpenNI 每次都直接做掉的话,其实只会增加许多无谓的计算;而现在这样可以控制,也算是比较好的方法了~