Opencv骨骼获取

Opencv骨骼获取
Opencv骨骼获取

透過 OpenNI / NITE 分析人體骨架(上)
前面幾篇文章,基本上都是單純地讀取 OpenNI 裡的原始影像資料(深度/彩色) ,並沒有額外去做其 他進一步的處理; 而這一篇的, 則是直接進入可能比較 實用、也比較特別的部分,要去讀取使用 NITE 這套 middleware 分析出來的人體骨架資料了~ 不過 Heresy 覺得 OpenNI 在這方面的資料其實相對 少了許多, 目前 Heresy 自己主要是根據 NITE 的範例 程式「StickFigure」來做參考的,而整體來說,也算是 摸到能正常運作而已;所以如果要看更完整的程式,也 可以找這個官方的範例原始碼來看看。 接下來的部分, 就是 Heresy 自己對於這部分摸出來的 一些東西了。 OpenNI 人體骨架的構成 首先,OpenNI 的人體骨架基本上是由「關節」 (joint)來構成的,而每一個關節都有位置 (position)和方向(orientation)兩種資料;同時,這兩者也都還包含了對於這個值的「信賴 度」 (confidence) ,可以讓程式開發者知道 middleware 所判斷出來的這個關節資訊有多大的 可信度。 而在目前的 OpenNI 裡, 他以列舉型別的方式總共定義了 24 個關節 (XnSkeletonJoint) , 分別是: ? XN_SKEL_HEAD, XN_SKEL_NECK, XN_SKEL_TORSO, XN_SKEL_WAIST ? XN_SKEL_LEFT_COLLAR, XN_SKEL_LEFT_SHOULDER, XN_SKEL_LEFT_ELBOW, XN_SKEL_LEFT_WRIST, XN_SKEL_LEFT_HAND, XN_SKEL_LEFT_FINGERTIP ? XN_SKEL_RIGHT_COLLAR, XN_SKEL_RIGHT_SHOULDER, XN_SKEL_RIGHT_ELBOW, XN_SKEL_RIGHT_WRIST, XN_SKEL_RIGHT_HAND, XN_SKEL_RIGHT_FINGERTIP ? XN_SKEL_LEFT_HIP, XN_SKEL_LEFT_FOOT ? XN_SKEL_RIGHT_HIP, XN_SKEL_RIGHT_FOOT XN_SKEL_LEFT_KNEE, XN_SKEL_RIGHT_KNEE, XN_SKEL_LEFT_ANKLE, XN_SKEL_RIGHT_ANKLE,
不過雖然 OpenNI 定義了這麼多的關節,但是實際上在透過 NITE 這個 middleware 分析 骨架時, 其實能用的只有上面標記成紅色的十五個 (可以透過 xn::SkeletonCapability 的 成員函式 EnumerateActiveJoints() 取得可用的關節列表) 而如果把 NITE 有支援的關 。 節畫成圖的話,就是下面的樣子了~

而如果再把這些關節連成線畫出來,結果就會是類似本文最右上方的那張圖裡的樣子了。 建立人體骨架的基本流程 要能夠在 OpenNI 的環境裡建立人體骨架,基本上是要靠所謂的 User Generator、也就 是 xn::UserGenerator;而由於他的資料來源是深度影像(depth map) ,所以要使用的話,同時 也要建立一個可用的 Depth Generator 出來才行。 不過 Heresy 個人覺得比較奇怪的是,在 OpenNI 的文件中,有提及「Production Chain」 這個概念(請參考《Kinect 的軟體開發方案:OpenNI 簡介》,但是在實作時,似乎沒有提 ) 供建立這個鏈結的方法?以這邊這個例子來說,似乎只要同時有 User Generator 以及 Depth Generator 這兩種 production node, 就會自動讓 User Generator 去存取 Depth Generator 的資 料; 而這樣的機制或許算是滿方便, 但是 Heresy 比較好奇的是, 如果存在兩個不同的 Depth Generator 的話,那 User Generator 會去存取哪一個的資料?不過,由於 Heresy 手邊只有一 個 Kinect,所以也沒辦法做測試了。 而 User Generator 的使用方法和之前介紹過的 depth generator 或 image generator 差比 較多, 他主要還必須要透過 callback function 的機制, 來做事件 (event) 的處理; 而在 OpenNI 裡面,要為一個 production node 加上 callback function,基本上就是透過各種 node 提供、 名稱為 RegisterXXXCallbacks() 的成員函式,來「註冊」 (register)該 node 的 callback function; 如果以這邊要使用的 xn::UserGenerator 來說, 就是 RegisterUserCallbacks() 這個函 式了。 另外由於骨架的判斷、以及判斷骨架時需要的姿勢偵測在 OpenNI 都是屬於延伸功能 的「Capability」 ,所以在這裡所使用的 user generator 也必須要有支援 Skeleton 和 Pose Detection 這兩個 capability 才行;不過現階段所使用的 user generator 應該都是 NITE 所提 供的,所以應該都會有支援。 前置的說明大概告了一個段落,接下來,就來看在 NITE 的 StickFigure 這個範例程式裡, 用來建立人體骨架的標準流程了~整個進行人體骨架分析的流程大致如下圖所示,雖然可能 不是很精確,但是應該算是可以用來說明了。

? 在上方的流程圖中,最左邊的紅色方塊是代表 user generator(xn::UserGenerator) ,裡 面的「New User」和「Lost User」則是代表他的兩個事件的 callback function;這兩個函 示分別會在「畫面內偵測到新的使用者」「使用者離開可偵測範圍一段時間」時被呼叫。 、 ? 错误! ? 而 中 間 偏 左 的 藍 色 方 塊 則 是 pose detection 這 個 capability (xn::PoseDetectionCapability) 在 user generator 偵測到有新的使用者、 「New User」 。 呼叫 這個 callback function 時, 「New User」的程式會去呼叫 pose detection 的「Start Pose Detection」 、讓 pose detection 開始偵測 NITE 預先定義的校正用姿勢: 「Psi」 (如右圖) 。 在呼叫「Start Pose Detection」前,pose detection 是不會進行姿勢偵測的動作的。 ? 當 pose detection 偵測到使用者擺出「Psi」這個姿勢後,他就會去呼叫自己的「Pose Detected」 這個 callback function、 以進行下一階段的動作; 在這個例子裡, 「Pose Detected」 會去做兩件事,一個是去呼叫自己的「Stop Pose Detection」來停止繼續偵測使用者的動 作、另一個則是去呼叫 skeleton 這個 capability(xn::SkeletonCapability)的「Request Calibration」函式,要求 skeleton 開始進行人體骨架的校正、分析。 ? 在 xn::SkeletonCapability 的「Request Calibration」被呼叫後,skeleton 就會開始進行 骨架的校正、分析。當開始進行骨架校正的時候,skeleton 會去呼叫「Calibration Start」 這個 callback function,讓程式開發者可以知道接下來要開始進行骨架的校正了,如果有 需要的話,可以在這邊做一些前置處理;而當骨架校正完後,則是會去呼叫「Calibration End」這個 callback function。 ? 不過,當「Calibration End」被呼叫的時候,只代表骨架的校正、辨識的階段工作結 束了,並不代表骨架辨識一定成功,也有可能是會失敗的。如果成功的話,就是要進入 下一個階段、呼叫 xn::SkeletonCapability 的「StartTracking()」函式,讓系統開始去追蹤

校正成功的骨架資料;而如果失敗的話,則是要再讓 pose detection 重新偵測校正姿勢, 等到有偵測到校正姿勢後,再進行下一次的骨架校正。 ? 而在骨架校正成功、 並開始進行追蹤骨架後, 之後只要呼叫 xn::SkeletonCapability 用 來讀取關節資料的函式(例如 GetSkeletonJoint()) ,就可以讀取到最新的關節相關資訊, 並建立整個人體的骨架資料了~ ? Callback Function 簡單說明 ? 如果在整個流程圖裡面仔細算一下的話,可以發現整個流程下來,總共有五個不同的 callback function, 分別是 xn::UserGenerator 兩個, 以及 xn::PoseDetectionCapability 一個、 xn::SkeletonCapability 兩個;他們分別是: ? User Generator: ? New User、Lost User ? 兩 者 形 式 皆 為 : void (XN_CALLBACK_TYPE* UserHandler)( UserGenerator& generator, XnUserID user, void* pCookie ) ? Pose Detection Capability: ? Pose Detected ? 形式為: void (XN_CALLBACK_TYPE* PoseDetection)( PoseDetectionCapability& pose, const XnChar* strPose, XnUserID user, void* pCookie ) ? Skeleton Capability: ? Calibration Start、Calibration End ? 兩者形式不同,分別為: void (XN_CALLBACK_TYPE* CalibrationStart)( SkeletonCapability& skeleton, XnUserID user, void* pCookie ) void (XN_CALLBACK_TYPE* CalibrationEnd)( SkeletonCapability& skeleton, XnUserID user, XnBool bSuccess, void* pCookie ) 上面這五個 callback function,就是在進行人體骨架校正時,所需要用到的所有 callback fucntion、以及他們的形式了~而由於 OpenNI 有定義 XN_CALLBACK_TYPE 來定義 callback function 的 calling convention(參考 MSDN) ,所以在自己編寫的 callback functions,也要用 同樣的形式。 不過,雖然這邊列了五個 callback function,但是其實這些 callback function 在意義上,不見 得是必須的;其中「Lost User」和「Calibration Start」實際上由於沒有額外的動作,所以應該 是沒有必要性;但是由於目前版本的 OpenNI 在沒有給這兩個 callback 的情況下進行骨架 的校正會讓程式出問題,所以就算不想做任何事、也要給他一個空的 callback function,而 不能給 NULL。這個在 Heresy 來看,應該算是 OpenNI 現行版本的錯誤,只能希望之後的 版本可以修正了。 程式碼 前面大致把整個人體骨架校正的流程都講過了,接下來,就是看程式的部分了!下面的 程式碼是 Heresy 根據 NITE 的範例程式「StickFigure」來做簡化、改寫的,裡面的輸出只 有用簡單的文字輸出,來顯示目前的狀態;如果想看有圖形結果的版本,則可以直接去找 NITE 的範例來看。 #include #include #include #include

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.GetPoseDetectionCap().StartPoseDetection("Psi", user); } // callback function of user generator: lost user void XN_CALLBACK_TYPE LostUser( xn::UserGenerator& generator, XnUserID user, void* pCookie ) { cout << "User " << user << " lost" << endl; } // callback function of skeleton: calibration start void XN_CALLBACK_TYPE CalibrationStart( xn::SkeletonCapability& skeleton, XnUserID user, void* pCookie ) { cout << "Calibration start for user " << user << endl; } // callback function of skeleton: calibration end void XN_CALLBACK_TYPE CalibrationEnd( xn::SkeletonCapability& skeleton, XnUserID user, XnBool bSuccess, void* pCookie ) { cout << "Calibration complete for user " << user << ", "; if( bSuccess ) { cout << "Success" << endl; skeleton.StartTracking( user ); } else { cout << "Failure" << endl; ((xn::UserGenerator*)pCookie)->GetPoseDetectionCap().StartPoseDetection( "Psi", user ); } }

// callback function of pose detection: pose start void XN_CALLBACK_TYPE PoseDetected( xn::PoseDetectionCapability& poseDetection, const XnChar* strPose, XnUserID user, void* pCookie) { cout << "Pose " << strPose << " detected for user " << user << endl; ((xn::UserGenerator*)pCookie)->GetSkeletonCap().RequestCalibration( user, FALSE ); poseDetection.StopPoseDetection( user ); } int main( int argc, char** argv ) { // 1. initial context xn::Context mContext; mContext.Init(); // 2. map output mode XnMapOutputMode mapMode; mapMode.nXRes = 640; mapMode.nYRes = 480; mapMode.nFPS = 30; // 3. create depth generator xn::DepthGenerator mDepthGenerator; mDepthGenerator.Create( mContext ); mDepthGenerator.SetMapOutputMode( mapMode ); // 4. create user generator xn::UserGenerator mUserGenerator; mUserGenerator.Create( mContext ); // 5. Register callback functions of user generator XnCallbackHandle hUserCB; mUserGenerator.RegisterUserCallbacks( NewUser, LostUser, NULL, hUserCB ); // 6. Register callback functions of skeleton capability xn::SkeletonCapability mSC = mUserGenerator.GetSkeletonCap(); mSC.SetSkeletonProfile( XN_SKEL_PROFILE_ALL ); XnCallbackHandle hCalibCB; mSC.RegisterCalibrationCallbacks( CalibrationStart, CalibrationEnd, &mUserGenerator, hCalibCB ); // 7. Register callback functions of Pose Detection capability XnCallbackHandle hPoseCB; mUserGenerator.GetPoseDetectionCap().RegisterToPoseCallbacks( PoseDetected, NULL, &mUserGenerator, hPoseCB ); // 8. start generate data mContext.StartGeneratingAll();

while( true ) { // 9. Update date mContext.WaitAndUpdateAll(); // 10. get user information XnUInt16 nUsers = mUserGenerator.GetNumberOfUsers(); if( nUsers > 0 ) { // 11. get users XnUserID* aUserID = new XnUserID[nUsers]; mUserGenerator.GetUsers( aUserID, nUsers ); // 12. check each user bool bGetSkeleton = false; for( int i = 0; i < nUsers; ++i ) { // 13. if is tracking skeleton if( mSC.IsTracking( aUserID[i] ) ) { // 14. get skeleton joint data XnSkeletonJointTransformation mJointTran; mSC.GetSkeletonJoint( aUserID[i], XN_SKEL_HEAD, mJointTran ); // 15. 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; } } // 16. stop and shutdown mContext.StopGeneratingAll(); mContext.Shutdown(); return 0; } 在這段程式碼裡,一開始的五個函式,就是這邊要給 OpenNI 用的 callback function 了 ~他們分別是給 user generator 用的 NewUser()、LostUser(),給 skeleton capability 用的 CalibrationStart() 、 CalibrationEnd() 和 給 pose detection capability 用 的 PoseDetected()。而這幾個函式在這邊至少都有透過 cout 來輸出現在的狀態,做為測試 以及錯誤偵測的依據。 不過其中, NewUser()、 PoseDetected() 和 CalibrationEnd() 是 還有其他功能的~而這些功能,在前面其實已經有提過了,在之後也還會再做解釋。 大概先帶過 callback function 的部分,接下來,就繼續看 main() 的內容了~ 首先, 「1. initial context」「2. map output mode」「3. create depth generator」的部分,和之前 、 、 都是一樣的, 只是稍微把錯誤偵測的部分省略掉, 所以在這邊就不特別做說明了。 「4. create 而

user generator」的部分,也就是建立一個 xn::UserGenerator 的 production node 物件: mUserGenerator 了;他基本的建立方法和 xn::DephGenerator 也是相同的, 一樣是透過 Create() 這個函式來建立。 接下來程式裡面比較特別的,就是程式碼裡的 5 - 7、用來設定 callback function 的部分 了~這一部分, 包括註冊 callback function 以及每一個 callback function 的內容, 請跳到下一 篇文章的「Callback Function 的細節」這個段落、參考比較詳細的說明。 而到了「8. start generate data」時,整個 OpenNI 要做人體骨架分析的環境已經算是建置完成 了~接下來,基本上也和之前的程式相同類似,先透過 context 的 StartGeneratingAll() 來開始產生資料、再透過 WaitAndUpdateAll() 來更新各個 production node 的資料了~ 這部分的寫法,Heresy 也還和之前的範例相同,用一個無窮迴圈來跑、不停地進行資料的更 新。 不過這邊可能要注意一下的是, 雖然 user generator 的 callback 是使採用事件導向 (event driven) 的 方式 來進行 的, 但是 如 果 沒 有 不 停 地去 執 行 WaitAndUpdateAll() 來 更 新 production node 的資料的話,似乎是不會有任何 event 產生的!
Callback Function 的細節
User Generator
在「5. Register callback functions of user generator」的部分,是使用 xn::UserGenerator 的成 員函式 RegisterUserCallbacks(),來進行 callback function 的「註冊」 (register) ;這個函式 他有四個參數,他們的型別和意義分別是: UserHandler NewUserCB:偵測到新使用者的 callback function UserHandler LostUserCB:使用者消失的 callback function void* pCookie:額外傳遞給 callback function 使用的資料 XnCallbackHandle& hCallback:管理 callback function 用的 handle 值,如果要取消 callback function 設定時,需要用到這個變數。 而在這裡,我們給他註冊的兩個 callback function,就是之前定義的 NewUser() 和 LostUser(),由於這兩個 callback function 都不需要其他的資料,所以 pCookie 就直接給他 NULL 就 可 以 了 ; 而 最 後 的 callback handle , 雖 然 在 這 個 程 式 裡 沒 有 打 算 取 消 註 冊 (unregister) ,但是還是需要給他一個變數來儲存(這邊就是 hUserCB) 。 其中,在 callback function 的部分,NewUser() 和 LostUser() 在被呼叫的時候,都會 取得三個變數:xn::UserGenerator& generator、XnUserID user、void* pCookie。 其中第一個參數 generator 就是 user generator 本身、也就是 main() 裡面所建立出來的 mUserGenerator;而 user 則是代表目前新抓到的使用者,當場景裡有大於一個使用者的話, 就是要靠這個 XnUserID 來做識別了~最後的 pCookie 就是在透過 RegisterUserCallbacks() 來註冊 callback function 時的額外參數;不過由於在這個例子裡,並沒有需要額外傳資料進 入 callback function,所以他的值會是我們在設定時的 NULL。 而在 callback function 本身的內容部分,LostUser() 雖然裡面只有輸出訊息,但是在目 前 版 本 的 OpenNI 是 不 可 以 省 略 的 ; 而 在 NewUser() 裡 , 則 是 會 透 過 GetPoseDetectionCap() 去 取 得 pose detection 這 個 capability , 並 使 用 StartPoseDetection() 這個函式、來讓他針對目前新抓到的使用者(user)開始進行姿勢 偵測。

Skeleton Capability
接下來,則是「6. Register callback functions of skeleton capability」的部分; 這一部分的程式,是針對 mUserGenerator 的 skeleton capability 來做設定。由於之後 對於 skeleton capability 的操作比較頻繁,所以在這邊 Heresy 是先用一個物件 mSC, 來做為 skeleton capability 的操作物件;而取得的方法則和其他 capability 類似,是 使用 GetSkeletonCap() 這個函式。 而要使用 skeleton capability 前,也還要使用 SetSkeletonProfile() 這個函式, 來設定所謂的「skeleton profile」 ,決定要使用那些關節。在 OpenNI 是定義了五種不同 的 profile: XN_SKEL_PROFILE_NONE:沒有任何關節。 XN_SKEL_PROFILE_ALL:代表所有關節,以目前的 NITE 來說就是 15 個(列表請見前一 篇) 。 XN_SKEL_PROFILE_UPPER:含軀幹(torso)的上半身、 以目前的 NITE 來說總共是九個 關節。 XN_SKEL_PROFILE_LOWER:含軀幹(torso)的下半身、 以目前的 NITE 來說理論上要有 七個關節。 (注意!以程式內的註解來看,應該是要包含 torso 共七個,不過 Heresy 自己 試著用 EnumerateActiveJoints() 來做查詢,卻只有列出不含 torso 的六個關節) XN_SKEL_PROFILE_HEAD_HANDS:只有頭和雙手這三個關節。 在這邊,Heresy 是使用完整的「XN_SKEL_PROFILE_ALL」 ,來抓所有可用的關節。 接 下 來 , 就 是 要 註 冊 mSC 的 callback function 了 。 這 邊 使 用 的 函 式 , 是 xn::SkeletonCapability 的 RegisterCalibrationCallbacks() , 他 和 前 面 xn::UserGenerator 的 RegisterUserCallbacks() 類 似 , 都 是 有 包 含 兩 個 callback function 的四個參數: ? CalibrationStart CalibrationStartCB: 開始進行骨架校正的 callback function ? CalibrationEnd CalibrationEndCB:骨架校正完的 callback function ? void* pCookie:額外傳遞給 callback function 使用的資料 ? XnCallbackHandle& hCallback:管理 callback function 用的 handle 值 在這邊,所註冊的兩個 callback function,就是在自己定義的 CalibrationStart() 和 CalibrationEnd();而和前面 user generator 時不同,這邊的 pCookie 由於有另外的需 要,所以 Heresy 是把目前使用的 user generator 的位址(&mUserGenerator)當作額外的 參數,傳進去給 callback function 使用。最後、第四個參數 callback handle 的部分,一樣 是要用一個變數來儲存他,在這邊用的就是 hCalibCB。 在 這 樣 設 定 後 , 當 skeleton capability 開 始 進 行 骨 架 校 正 的 時 候 , 就 會 去 呼 叫 CalibrationStart() 這個函式, 而當骨架校正完後, 則是會再去呼叫 CalibrationEnd() 這個函式。其中,CalibrationStart() 這個函式也是只有輸出資訊而已,並沒有實際的 用途(但是在目前的 OpenNI 環境不指定的話,程式的執行會有問題) ,所以在這邊就不多 做說明了。 而 CalibrationEnd() 這個函式,則是有實際用處的函式,他必須要根據是否有正確地辨 識出人體骨架,做出下一階段的動作;而他會接受四個參數,分別是: ? xn::SkeletonCapability& skeleton:目前所使用的 skeleton capability,在這個 例 子 裡 就 是 main() 裡 面 透 過 的 mUserGenerator.GetSkeletonCap() 取 得 的 mSC。 ? XnUserID user:目前進行骨架校正的使用者。

? XnBool bSuccess:人體骨架校正校正是否成功。 ? void* pCookie : 額 外 傳 遞 的 資 料 , 由 於 在 註 冊 callback function 時 是 傳 入 &mUserGenerator,所以這邊 pCookie 代表的意義,就是一個指向目前使用的 user generator、也就是 mUserGenerator 的指標。 CalibrationEnd() 實際的內容呢,就是先透過 bSuccess 來判斷是否有正確骨架,如果 成功的話,就呼叫 skeleton.StartTracking( user ),來要求 skeleton capability 開始 進行這個使用者的骨架追蹤;而如果失敗的話,則是要求 pose detectioncapability 重新開始 偵測「Psi」校正姿勢,等到偵測到下一次的校正姿勢,再重新開始分析人體骨架。 不過 由於 CalibrationEnd() 取得的 只有 skeleton capability, 並沒有 pose detection capability 的物件,所以要對 pose detection capability 做操作的話,就是要透過額外傳進來 的 pCookie 來取得;在這邊,由於傳進來的是 mUserGenerator 的指標,但是由於型別 已 經 被 轉 型 為 void* 了 , 所 以 要 使 用 的 話 , 要 再 透 過 強 制 轉 型 來 把 他 轉 回 xn::UserGenerator* 才行。 而實際上,由於在這個範例裡,CalibrationEnd() 只有用到 skeleton capability 和 pose detection capability,而他本身就可以直接取得 skeleton capability,拿不到的只有 pose detection capability,所以其實也可以考慮不是把 user generator 傳進來,而只傳 pose detection capability 也是可以的。 附註: ? 在 官方 的 StickFigure 範 例 裡, 是將 user generator 宣告 為全域 變數 ,所以 在 callback funciton 裡也可以直接存取,而不必用到 pCookie。 ? xn::SkeletonCapability 除了 RegisterCalibrationCallbacks() 可以設定 兩個 callback function 外,還有 RegisterToJointConfigurationChange() 可以設 定另一個 callback function,不過因為這邊用不到,所以就不提了。
Pose Detection Capability
這部分的最後, 則是 「7. Register callback functions of Pose Detection capability」 也就是 pose , detection capability 的 設 定 ; 這 邊 所 用 來 註 冊 callback function 的 , 是 xn::PoseDetectionCapability 的成員函式 RegisterToPoseCallbacks()。其實他和 前面 user generator 或 skeleton capability 類似,也是可以註冊兩個 callback function、要指 定四個參數: ? PoseDetection PoseStartCB:偵測到姿勢時會被呼叫的 callback funtion ? PoseDetection PoseEndCB:當偵測到的姿勢結束的時候會被呼叫的 callback function ? void* pCookie:額外傳遞的資料 ? XnCallbackHandle& hCallback:管理 callback function 用的 handle 值 而和前面兩項相比,比較特別的一點是,在這個程式裡,我們只註冊了第一個 callback function , 也 就 是 PoseStartCB / PoseDetected() ! 對 於 第 二 個 callback function PoseEndCB,在這邊是沒有去使用,而僅只有給他一個 NULL 的。這是由於 PoseEndCB 在 這個例子裡面並沒有要做任何事、也沒有任何用處,而且在目前的 OpenNI 版本,可以把 它省略而不會有問題。 相較於此,如果把 skeleton capability 的 CalibrationStartCB 省略掉的話,是會導致程 式執行到一半當掉的;理論上,這是不該發生問題的,所以只能希望之後 OpenNI 能修正

這個問題了。 所以,在這邊執行 RegisterToPoseCallbacks() 註冊 callback function 時,傳進去的四 個參數依序為:PoseDetected、NULL、&mUserGenerator、hPoseCB;其中後面兩者的意 義和設定 skeleton capability 時是相同的,所以在這邊就不多加介紹了。 回到 PoseDetected() 這個函式本身,他在被呼叫時,會接受到四個參數: ? xn::PoseDetectionCapability& poseDetection:pose detection capability 本身 的 物 件 參 考 , 在 這 邊 是 等 同 於 main() 裡 面 , 透 過 mUserGenerator.GetPoseDetectionCap() 取得的 pose detection capability 物件。 ? const XnChar* strPose:偵測到的姿勢,這邊是以字串的形式來告訴 callback function,這邊偵測到的是哪種姿勢(不過目前似乎也只有「Psi」一種) 。 ? XnUserID user:偵測到該姿勢的使用者 ? void* pCookie:額外傳遞的資料,這邊代表的意義就是一個指向目前使用的 user generator、也就是 mUserGenerator 的指標。 而在 OpenNI 偵測到校正用的姿勢、並呼叫 PoseDetected() 後,他做了兩件事。第一件 事是去呼叫 user generator 的 skeleton capability 的 RequestCalibration() 函式,要求 skeleton capability 開始針對擺出校正姿勢的使用者(user)進行人體骨架的校正;而之後, 由於已經偵測到姿勢、 並且進入下一個處理階段了,所以也要透過 StopPoseDetection() 這個函式,來停止對這個使用者繼續做姿勢的偵測。
讀取骨架資料 讀取骨架資料的部分,在程式碼裡面就是「10. get user information」開始的部分了。 首先,要讀取 user generator 的相關資料時,大多都需要使用 XnUserID 來指定是要針對 哪一個使用者來做操作; 而要取得使用者的資料, 則是要透過 user generator 的 GetUsers() 來讀取。 而 OpenNI 對於這種未知數量的資料讀取, 基本上都是採用同樣的形式, 像以 GetUsers() 來說,就是要給他一個已經分配好記憶體位址的陣列、讓他寫入資料,並給他分配好的空間 大小、讓他知道可以寫多少東西進去。所以 GetUsers() 的介面,就是 XnStatus xn::UserGenerator::GetUsers( XnUserID aUsers[], XnUInt16& nUsers ) 而使用的話,大致上就是(程式碼摘錄自 NITE 範例的 StickFigure.cpp) : XnUserID Users[10]; XnUInt16 nUsers = 10; g_UserGenerator.GetUsers( Users, nUsers ); 這樣寫的話,他就會試著去由取得 g_UserGenerator 目前偵測到的前十個使用者,並將 這些使用者的資料,儲存在 Users 這個 XnUserID 陣列裡;而在執行之後,nUsers 的值 也會跟著被修改成取得的使用者資料數量。 比如說 g_UserGenerator 有五個使用者的話,在執行過上面的程式後,Users 的前五項 就會被寫入資料、而 nUsers 的值也會從 10 變成 5;如果 g_UserGenerator 有十五個 使用者的話, 在執行過後, Users 的十項都會被寫入資料、 nUsers 的值則會維持是 10, 而 後面五個使用者的資料就沒讀出來了。 而實際上比較好的方法, 應該是先去取得使用者的數量, 再來讀取相關的資料; 所以 Heresy

在這邊的做法,就是先透過 user generator 的 GetNumberOfUsers() 函式,來取得目前的 使用者數量( 「10. get user information」的部分) ,之後再根據這個已知的數量,去取得使用 者的資料( 「11. get users」的部分) 。 接下來,Heresy 則是用迴圈的方式,去針對每一個偵測到的使用者都做處理。在迴圈內的 第一步,就是透過 skeleton capability 的 IsTracking() 函式,來判斷使否正在追蹤該使 用者的骨架資料( 「13. if is tracking skeleton」的部分) ;如果有的話,他的骨架資料才是有 意義、有需要去讀取的。 如同前一篇文章所提過的,OpenNI 的關節資料標括了位置(position)和角度(orientation) , 而在讀取骨架資料的時候,也可以透過使用不同的函式,來讀取不同的資料;OpenNI 的 xn::SkeletonCapability 有提供三個函式,分別是: ? GetSkeletonJointPosition() : 讀 到 的 資 料 是 只 有 位 置 資 訊 的 XnSkeletonJointPosition ? GetSkeletonJointOrientation() : 讀 到 的 資 料 是 只 有 角 度 資 訊 的 XnSkeletonJointOrientation ? GetSkeletonJoint() : 讀 到 的 資 料 是 包 括 位 置 和 角 度 的 XnSkeletonJointTransformation 至於到底要用哪種讀取方法,就是看個人需求了。Heresy 在 這邊的程式裡,是使 用 GetSkeletonJoint() 來讀取關節的資料( 「14. get skeleton joint data」的部分) ,他的方法 基本上就是要指定要讀取「哪個使用者」的「哪個關節」 ,也就是必須要一個關節、一個關 節來做讀取;而 Heresy 為了簡化程式,這邊就只有讀取頭部、也就是 XN_SKEL_HEAD 的 資料了。 最後,由於 Heresy 沒有寫圖形顯示的部分,所以在把關節資料讀取出來後,也就只有很簡 單地用文字的形式,把讀到的資料做輸出了( 「15. output information」的部分)~
小結 基本上,這個程式還是只有純文字的 console 介面。在執行起來後,他會在有事件發生的 時候,輸出一些對應事件的訊息,像是「New user identified」「User xxx lost」之類的。而當 、 使用者擺出校正姿勢、並且校正成功、成功地抓到骨架後,程式就會不斷地輸出目前抓到的 骨架的頭部位置,直到使用者離開為止。 雖然這樣的程式基本上沒什麼意義,但是要拿來做基本的 OpenNI 人體骨架的範例、測試, 應該也算是夠了~如果要延伸的話,就是在去根據讀取到的骨架資料,看看是要拿來幹嘛了 ~

相关主题
相关文档
最新文档