不用校正姿勢的 NITE 1.5


前一陣子前有提過了,PrimeSense 在新推出的 1.5.x 的 NITE 中,終於讓 user generator 可以不需要擺出 Psi 校正姿勢、就直接進行人體的骨架追蹤了~如此一來,要用 Kinect 透過 OpenNI 來做人體的骨架追蹤,在程式的撰寫上就可以稍微簡單一些了!而這一篇,就大概來提一下,新版 user generator 要怎麼用吧~

不過在開始之前,建議請先回去大概看過之前的介紹文章,這樣應該會比較有些概念:

舊有程式可以不用改

首先很重要的,新的 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::SkeletonCapabilityRequestCalibration() 函式、要求他對於偵測到的新使用者(user)進行骨架的校正。

而接下來,xn::SkeletonCapability 就會試著去分析使用者的骨架、並進行校正的工作,等到骨架校正的動作完成後,就會呼叫 CalibrationEnd() 這個 callback function。而它的內容和本來的很接近,就是要先判斷骨架校正的結果是否正常(XnCalibrationStatus 應該要是 XN_CALIBRATION_STATUS_OK),如果正常的話,就是接著透過 xn::SkeletonCapabilityStartTracking() 函式、開始追蹤 user 的骨架;而如果失敗的話,則是重新透過 RequestCalibration()、要求 xn::SkeletonCapability 再度對 user 進行骨架的校正。

所以實際上,新的人體骨架的追蹤架構,其實主要就是省略掉 Pose Detection 的部分而已了~再來,就是 NITE 內部的實作應該也做了不少修改,所以在效率上也好了不少。

而上面的程式,基本上是只有用 standard output 來輸出結果而已,並沒有任何的圖形介面;如果希望看到有畫面的範例程式的話,請參考這個使用 Qt 來做圖形介面的範例,這隻程式基本上是根據《使用 Qt 顯示 OpenNI 的人體骨架》一文的範例程式修改而成的,在這邊就不多做說明了。


這篇就寫到這了。話說,本來 Heresy 一直以為新版的 OpenNI 和 NITE 應該會在偵測到 user 後,就自動做骨架的校正、而不必額外再去做設定;不過現在看來,至少還是要自己去要求 xn::SkeletonCapability 進行骨架的校正與追蹤的。

想想,其實這也算是合理的。畢竟,不是每個時候都有需要去知道畫面內的使用者的骨架,如果 OpenNI 每次都直接做掉的話,其實只會增加許多無謂的計算;而現在這樣可以控制,也算是比較好的方法了~


OpenNI / Kinect 相關文章目錄

對「不用校正姿勢的 NITE 1.5」的想法

  1. heresy大大好,
    我有一个问题,就是说如果我想用openni库对人体骨骼进行追踪,就必须要用到NITE库是么?有没有就是不用nite库从而也能进行骨骼追踪的

  2. heresy大大您好

    百忙之中又要麻煩您一下,之前的熱心解答讓我獲益良多,非常感謝。

    這次我想詢問下關於lostuser的問題,我發現離開鏡頭範圍之後,觸發lostuser總需要10秒左右的時間。當一人以上反復進出鏡頭就會很卡,甚至出現假死現象。
    請問這個lostuser函數是能直接調用的嗎?請問能不能給lostuser設定一個觸發條件呢?比如當user的真實世界x座標超過了一定值就調用lostuser。
    請問大大有什麽能改良這種假死情況的建議或者方法呢?非常感謝!

    • 使用者離開的部分,OpenNI 並沒有提供介面可以強制執行,必須要等他時間到了,自己消失。
      而至於有沒有辦法減少那個延遲?印象中,如果是 release mode 的話,這個問題的影響應該不大才對?

      • 原來是這樣子啊。。。不過我本來就是用release來編譯的,好像沒什麼改善。而且老是會有一些錯誤識別或者在識別新用戶和用戶消失時假死。不知道是不是openni本身的問題。
        我也看了大大關於openni2的文章,在openni2中,用戶識別是否有改善呢?或者改用kinect sdk又會更好一些呢?非常感謝百忙之中抽出時間答覆我~

        • 與其說是 OpenNI 的問題,應該說是 NiTE 這個 middleware 的問題。
          至於哪種方案比較好,說實話以這個問題來說,Heresy 並沒有去做過比較(也不太知道該怎樣比較比較好?);個人會建議可以考慮自己玩看看,看看哪個方案比較符合自己的需求。

  3. Heresy大您好我想請問你一個問題,就是您這個範例程式,New user identified Success之後,基本上我就算離開鏡頭再進去 只要在LostUser之前 他就不會再次重新啟動 void XN_CALLBACK_TYPE NewUser 所以出去再進去他不會誤判你其實有兩個人 ,但重點來了, 我在if( mSC.IsTracking( aUserID[i] ) ) 這個判斷式裡 我也寫了許多我想計算的東西 ,這時候我出去再進去他就變成很容易誤判以為我有兩個人(雖然過一段時間她會自動lost ,但我不太喜歡,因為lost的速度太慢了),我想請問Heresy大,他會誤判的原因是為何?您知道如何改善嗎

    • 個人建議不要假定移出鏡頭、再進來的動作不會觸發 new user 比較好。

      另外,你的問題有可能是因為計算太過複雜、造成 NITE 處理的時間間隔變長造成的。
      建議你可以試試看、簡化你裡面的計算,然後再測試看看。

      • 其實我也覺得是時間的問題,但我覺得可能不太容易再簡化計算了(至少以我的功力不行),所以覺得很苦惱

        • 這部分就是看要怎麼簡化、或是最佳化了。
          基本上,如果可以平行化的話,在現在的多核心電腦,大多可以有效地加速。

          另外,有試過 release mode 會有一樣的問題嗎?

  4. […] 2012/08/23 發表迴響 OpenNI 搭配 NITE 雖然提供了全身的骨架追蹤功能,但是他並沒有提供針對手部的細部處理,緊緊有提供手部的位置追蹤、以及幾組預設的手勢辨識的能力(參考)。而由於手部動作的分析、尤其是去抓出手指、或是判斷手是否握著,在互動操作上也是相當實用的功能,所以實際上也有不少人在往這方面做研究~ […]

  5. 那 heresy大大

    依照您說的

    假如我將OpenNI 版本改回 1.3.4.3

    問題就可以解決

    可是這樣我的NITE版本還是可以配對1.5.0.2 嘛!!?

  6. 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 的步驟做

    想請教一下大大知道這個問題如何解決嗎@@?

      • 不好意思Heresy大..
        剛剛試了一下
        因為小弟一直都是用最新版的OPENNI
        跟這位Chen Yuan大大的情況一模一樣..
        就試著按ctrl +F5
        WIN7會強制關閉程式 ..
        然後在強制關閉前我在命令提示字元看到的錯誤是在XnCppWrapper裡的
        nRetVal = xnRegisterUserCallbacks(GetHandle(), NewUserCallback, LostUserCallback, pUserCookie, &pUserCookie->hCallback);
        而另外一個則是
        原本程式碼裡的mUserGenerator.RegisterUserCallbacks( NewUser, NULL, NULL, hUserCB );
        的錯誤
        姆.. 我再努力找找看好了…

          • 想問Heresy在寫的時候是用VC++2010 的WIN32主控台應用程式寫的嗎?
            還是用…?

          • 恩~我用他Documentation裡面的程式也不行
            原始碼也無錯誤
            這是一個很無解的問題…

        • 忽然想到,你有拿 NITE 官方的範例測試過嗎?
          User generator 是否有成功地建立出來?

          另,是使用 Windows 32 主控台沒有錯。

          • 回Heresy :問題已解決 感謝熱心的解答
            昨天NITE範例測試是OK的
            但還是會出現記憶體錯誤
            在絕望之時把整個OPENNI全部刪掉重灌
            下載OpenNI packages =>Stable=>發展套件
            然後再灌sensor kinect..
            之前都是先灌Binaries 再灌上面的套件
            所以會出現記憶體錯誤
            真是感謝大大不厭其煩的回答
            真是抱歉呢 讓你回答我 結果竟然是安裝的問題…

發表留言

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料