前一陣子前有提過了,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 的人體骨架》一文的範例程式修改而成的,在這邊就不多做說明了。
這篇就寫到這了。話說,本來 Heresy 一直以為新版的 OpenNI 和 NITE 應該會在偵測到 user 後,就自動做骨架的校正、而不必額外再去做設定;不過現在看來,至少還是要自己去要求 xn::SkeletonCapability 進行骨架的校正與追蹤的。
想想,其實這也算是合理的。畢竟,不是每個時候都有需要去知道畫面內的使用者的骨架,如果 OpenNI 每次都直接做掉的話,其實只會增加許多無謂的計算;而現在這樣可以控制,也算是比較好的方法了∼
您好,又来打扰您了~:D
第四步:
// 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 );
我仿照您的代码,自己写了个类,刚开始在类里面的一个叫capture()的成员函数上照搬您上面的代码,编译通过。
但是,但是。。
我试着将您这里的mSC作为类的成员函数(private),其余的代码还是放在capture()
这样,编译的时候报错:
no matching function for call to ‘xn::SkeletonCapability::SkeletonCapability
然后系统会自动把错误光标指向我的类的构造函数。
要是把mSC=mUserGenerator.GetSkeletonCap();放在构造函数里面。其余不变,还是报错。
您知道是怎么回事吗?是xn::SkeletonCapability声明之后必须初始化吗?
即xn::SkeletonCapability mSC = mUserGenerator.GetSkeletonCap();
to 林雄民
建議請參考 OpenNI 的 API Reference。
OpenNI 的 Capability 都沒有 default constructor,必須要在宣告變數時同時初始化。