前一陣子前有提過了,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 每次都直接做掉的話,其實只會增加許多無謂的計算;而現在這樣可以控制,也算是比較好的方法了~
[…] 透過 OPENNI / NITE 分析人體骨架(上)/透過 OPENNI / NITE 分析人體骨架(下)/ 不用校正姿勢的 NITE 1.5 […]
讚讚
heresy大大好,
我有一个问题,就是说如果我想用openni库对人体骨骼进行追踪,就必须要用到NITE库是么?有没有就是不用nite库从而也能进行骨骼追踪的
讚讚
請參考:2013 OpenNI 簡介
https://kheresy.wordpress.com/2013/09/30/about-openni-2013/
> OpenNI 只有提供讀取影像的功能,進一步的骨架追蹤等功能,都是要靠所謂的「middleware」、以 plug-in 的形式來提供實作。
如果不使用 NiTE,你就必須找一個足以替代 NiTE 的函式庫。
但是就目前所知,應該沒有。
讚讚
heresy大大您好
百忙之中又要麻煩您一下,之前的熱心解答讓我獲益良多,非常感謝。
這次我想詢問下關於lostuser的問題,我發現離開鏡頭範圍之後,觸發lostuser總需要10秒左右的時間。當一人以上反復進出鏡頭就會很卡,甚至出現假死現象。
請問這個lostuser函數是能直接調用的嗎?請問能不能給lostuser設定一個觸發條件呢?比如當user的真實世界x座標超過了一定值就調用lostuser。
請問大大有什麽能改良這種假死情況的建議或者方法呢?非常感謝!
讚讚
使用者離開的部分,OpenNI 並沒有提供介面可以強制執行,必須要等他時間到了,自己消失。
而至於有沒有辦法減少那個延遲?印象中,如果是 release mode 的話,這個問題的影響應該不大才對?
讚讚
原來是這樣子啊。。。不過我本來就是用release來編譯的,好像沒什麼改善。而且老是會有一些錯誤識別或者在識別新用戶和用戶消失時假死。不知道是不是openni本身的問題。
我也看了大大關於openni2的文章,在openni2中,用戶識別是否有改善呢?或者改用kinect sdk又會更好一些呢?非常感謝百忙之中抽出時間答覆我~
讚讚
與其說是 OpenNI 的問題,應該說是 NiTE 這個 middleware 的問題。
至於哪種方案比較好,說實話以這個問題來說,Heresy 並沒有去做過比較(也不太知道該怎樣比較比較好?);個人會建議可以考慮自己玩看看,看看哪個方案比較符合自己的需求。
讚讚
Heresy大您好我想請問你一個問題,就是您這個範例程式,New user identified Success之後,基本上我就算離開鏡頭再進去 只要在LostUser之前 他就不會再次重新啟動 void XN_CALLBACK_TYPE NewUser 所以出去再進去他不會誤判你其實有兩個人 ,但重點來了, 我在if( mSC.IsTracking( aUserID[i] ) ) 這個判斷式裡 我也寫了許多我想計算的東西 ,這時候我出去再進去他就變成很容易誤判以為我有兩個人(雖然過一段時間她會自動lost ,但我不太喜歡,因為lost的速度太慢了),我想請問Heresy大,他會誤判的原因是為何?您知道如何改善嗎
讚讚
個人建議不要假定移出鏡頭、再進來的動作不會觸發 new user 比較好。
另外,你的問題有可能是因為計算太過複雜、造成 NITE 處理的時間間隔變長造成的。
建議你可以試試看、簡化你裡面的計算,然後再測試看看。
讚讚
其實我也覺得是時間的問題,但我覺得可能不太容易再簡化計算了(至少以我的功力不行),所以覺得很苦惱
讚讚
這部分就是看要怎麼簡化、或是最佳化了。
基本上,如果可以平行化的話,在現在的多核心電腦,大多可以有效地加速。
另外,有試過 release mode 會有一樣的問題嗎?
讚讚
謝謝您的建議,我會再嘗試看看的 感謝!!
讚讚
[…] 2012/08/23 發表迴響 OpenNI 搭配 NITE 雖然提供了全身的骨架追蹤功能,但是他並沒有提供針對手部的細部處理,緊緊有提供手部的位置追蹤、以及幾組預設的手勢辨識的能力(參考)。而由於手部動作的分析、尤其是去抓出手指、或是判斷手是否握著,在互動操作上也是相當實用的功能,所以實際上也有不少人在往這方面做研究~ […]
讚讚
[…] User Generator 是採用不需要標準校正姿勢的架構(請參考《不用校正姿勢的 NITE 1.5》),如果程式的人體骨架追蹤使採用早期、需要使用「Psi」 […]
讚讚
那 heresy大大
依照您說的
假如我將OpenNI 版本改回 1.3.4.3
問題就可以解決
可是這樣我的NITE版本還是可以配對1.5.0.2 嘛!!?
讚讚
理論上換回舊版應該可以解決問題,但是舊版的 OpenNI 是否可以使用新版的 middle ware?這點 Heresy 不確定。
讚讚
謝謝大大的建議
看來目前還是先使用舊版比較好XDD
(鞠躬
讚讚
Heresy 大大您好
小弟在安裝完成NITE 1.5
以及OpenNI1.4.0.2
和驅動5.0.5.1之後
Sample可以跑
但是用Visual Studio 2010 Run Heresy大的Code跟自己的Code
都發生了Error
使用Heresy大大的CODE出現的Error>> http://ppt.cc/iDeU
小弟有依照OpenNI_UserGuido第19頁 Creating an empty project that uses OpenNI 的步驟做
想請教一下大大知道這個問題如何解決嗎@@?
讚讚
請參考官方論壇: https://groups.google.com/d/msg/openni-dev/r8SID3IwCtc/gwIWX4tAAZoJ
基本上,應該是這個版本的 binary 有問題,下個版本應該會修正。
現階段請不要使用 Visual Studio 的偵錯功能來執行程式。
例如,使用「Start without Debugging」、或是直接按 Ctrl + F5,這樣應該都可以跑。
讚讚
而且XnStatus 印出來後總是顯示 Input pointer is null
麻煩大大指教>"<
讚讚
不好意思Heresy大..
剛剛試了一下
因為小弟一直都是用最新版的OPENNI
跟這位Chen Yuan大大的情況一模一樣..
就試著按ctrl +F5
WIN7會強制關閉程式 ..
然後在強制關閉前我在命令提示字元看到的錯誤是在XnCppWrapper裡的
nRetVal = xnRegisterUserCallbacks(GetHandle(), NewUserCallback, LostUserCallback, pUserCookie, &pUserCookie->hCallback);
而另外一個則是
原本程式碼裡的mUserGenerator.RegisterUserCallbacks( NewUser, NULL, NULL, hUserCB );
的錯誤
姆.. 我再努力找找看好了…
讚讚
請教你使用的 OpenNI 版本?
理論上 OpenNI 1.5 應該沒有這個問題才對。
讚讚
安裝檔上面是.. openni-win32-1.5.2.23-dev
我人品有問題嗎OTZ
讚讚
想問Heresy在寫的時候是用VC++2010 的WIN32主控台應用程式寫的嗎?
還是用…?
讚讚
恩~我用他Documentation裡面的程式也不行
原始碼也無錯誤
這是一個很無解的問題…
讚讚
忽然想到,你有拿 NITE 官方的範例測試過嗎?
User generator 是否有成功地建立出來?
另,是使用 Windows 32 主控台沒有錯。
讚讚
回Heresy :問題已解決 感謝熱心的解答
昨天NITE範例測試是OK的
但還是會出現記憶體錯誤
在絕望之時把整個OPENNI全部刪掉重灌
下載OpenNI packages =>Stable=>發展套件
然後再灌sensor kinect..
之前都是先灌Binaries 再灌上面的套件
所以會出現記憶體錯誤
真是感謝大大不厭其煩的回答
真是抱歉呢 讓你回答我 結果竟然是安裝的問題…
讚讚
囧…結果居然是安裝順序錯了?
讚讚