而如果希望不用擺出特定校正姿勢的話,新的程式要怎麼寫呢?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 的人體骨架》一文的範例程式修改而成的,在這邊就不多做說明了。
這篇就寫到這了。話說,本來 Heresy 一直以為新版的 OpenNI 和 NITE 應該會在偵測到 user 後,就自動做骨架的校正、而不必額外再去做設定;不過現在看來,至少還是要自己去要求 xn::SkeletonCapability 進行骨架的校正與追蹤的。
想想,其實這也算是合理的。畢竟,不是每個時候都有需要去知道畫面內的使用者的骨架,如果 OpenNI 每次都直接做掉的話,其實只會增加許多無謂的計算;而現在這樣可以控制,也算是比較好的方法了∼