5、 透過 OpenNI / NITE 分析人體骨架(上)(非原创)

http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=219

前面幾篇文章,基本上都是單純地讀取 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_KNEE, XN_SKEL_LEFT_ANKLE, XN_SKEL_LEFT_FOOT
  • XN_SKEL_RIGHT_HIP, XN_SKEL_RIGHT_KNEE, XN_SKEL_RIGHT_ANKLE, XN_SKEL_RIGHT_FOOT

不過雖然 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 UserLost 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 StartCalibration 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 <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.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 contextxn::Context mContext;mContext.Init();// 2. map output modeXnMapOutputMode mapMode;mapMode.nXRes = 640;mapMode.nYRes = 480;mapMode.nFPS = 30;// 3. create depth generatorxn::DepthGenerator mDepthGenerator;mDepthGenerator.Create( mContext );mDepthGenerator.SetMapOutputMode( mapMode );// 4. create user generatorxn::UserGenerator mUserGenerator;mUserGenerator.Create( mContext );// 5. Register callback functions of user generatorXnCallbackHandle hUserCB;mUserGenerator.RegisterUserCallbacks( NewUser, LostUser, NULL, hUserCB );// 6. Register callback functions of skeleton capabilityxn::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 capabilityXnCallbackHandle hPoseCB;mUserGenerator.GetPoseDetectionCap().RegisterToPoseCallbacks( PoseDetected, NULL,&mUserGenerator, hPoseCB );// 8. start generate datamContext.StartGeneratingAll();while( true ){// 9. Update datemContext.WaitAndUpdateAll();// 10. get user informationXnUInt16 nUsers = mUserGenerator.GetNumberOfUsers();if( nUsers > 0 ){// 11. get users XnUserID* aUserID = new XnUserID[nUsers];mUserGenerator.GetUsers( aUserID, nUsers );// 12. check each userbool bGetSkeleton = false;for( int i = 0; i < nUsers;   i ){// 13. if is tracking skeletonif( mSC.IsTracking( aUserID[i] ) ){// 14. get skeleton joint dataXnSkeletonJointTransformation mJointTran;mSC.GetSkeletonJoint( aUserID[i], XN_SKEL_HEAD, mJointTran );// 15. output informationcout << "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 產生的

再來「10. get user information」開始的部分,就是要讀取 user generator 所抓出來的人體骨架資料了!而這一部分,也等到下一篇再來講了∼

5、透過 OpenNI / NITE 分析人體骨架(上)(非原创)相关推荐

  1. 透過 OpenNI 建立 Kinect 3D Point Cloud

    在可以透過 OpenNI 讀取到 Kinect 的深度.色彩資訊之後,其實就可以試著用這些資訊,來重建 3D 的環境做顯示了-不過實際上,在前面的範例中所讀到的深度資訊,都算是原始資料,而且座標軸也都 ...

  2. php Wrapper LFI,透過 LFI 引入 PHP session 檔案觸發 RCE

    先前因為朋友分享而得知某個小站具有 LFI 漏洞,於是就想嘗試著觸發 RCE,但發現主機上檔案權限蠻嚴格的,幸好最終還是成功透過 session 檔案觸發,因為過程有幾個蠻有趣的小細節,就趕緊寫篇文章 ...

  3. 使用 ssmtp 於 shell 透過 Gmail 寄信

    有很多程式於 bash shell 執行, 執行完要自動寄信出去, 但是最近都被 Google 退信, 最好的方法是透過 Gmail 直接寄信. 本來是要另外寫隻 script 來做這種事, 剛剛發現 ...

  4. 源码通透-mybatis源码分析以及整合spring过程

    源码通透-mybatis源码分析以及整合spring过程 mybatis源码分析版本:mybaits3 (3.5.0-SNAPSHOT) mybatis源码下载地址:https://github.co ...

  5. 如何透過JavaScript來觸發LinkButton的PostBack,呼叫後端的程式

    緣起 這一篇最終的目的,是希望能夠透過JavaScript來觸發Server端的Function來做一些事情,在這個過程中,我們透過觀察LinkButton的PostBack運作,進而想出如何使用Ja ...

  6. 大乐透历史中奖号码分析

    大乐透历史中奖号码分析 废话不多说,见代码.代码主要是实现功能 ,没有细分优化,望老铁轻喷 import base64 import ctypes from tkinter import * from ...

  7. Ubuntu+openni+nite+sensor+配置

    Ubuntu+openni+nite+sensor+配置 1.  根据自己的系统,确定是32位还是64位,下载配置kinect所需的库,下载地址: http://www.openni.ru/openn ...

  8. 3D虚拟化技术透析及竞争分析

    标签: sVGA vSGA vDGA GPU vGPU citrix vmware 原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. http: ...

  9. Part1:3D虚拟化技术透析及竞争分析

    因为每篇blog字数限制,所以我们分四章节来介绍这个vGPU专题.这是第一篇. 一:3D虚拟化技术透析及竞争分析 sVGA.vSGA.vDGA.vGPU.GPUPass-through,天哪,太多的缩 ...

最新文章

  1. 在函数调用中,星号运算符是什么意思? [重复]
  2. C语言预定义宏 __func__、__FUNCTION__、__LINE__、__FILE__、__DATE__、__TIME__
  3. 【大二在读】说说我对“软件工程”的理解
  4. 小区物业费信息管理系统设计c++_没有入住需要交物业费吗?有没有办法不交物业费呢?法律专家解读...
  5. [Leedcode][JAVA][第572题][另一个树的子树]
  6. 阿里云云效何勉:云原生是“精益实践”的最佳助力
  7. ubuntu 20.04 快速开启TCP BBR实现高效单边加速
  8. svn从本地更新了资源库的资源后删除了某个文件夹无法恢复(已解决)
  9. C++之安装boost库
  10. vue快速复制快捷键_vue快捷键.doc
  11. vscode java settings设置_vscode 搭建java环境
  12. 从心理学角度看,如何通过故事影响客户的决策?
  13. winform利用html开发,Winform开发框架之HTML编辑控件介绍
  14. 【面试】阿里巴巴Java经典面试题整理及答案详解
  15. 详解Flink中yarn部署模式以及测试
  16. Java比较两个数组是否相等(equals())
  17. Java日期格式2019-11-05T00:00:00转换标准日期
  18. 缺少计算机所需的介质程序,安装Win10显示缺少计算机所需的介质驱动程序的解决办法...
  19. 程序启动时,vc2015设置哪个窗体先打开,优先启动,设置方法
  20. 第13天 面向对象

热门文章

  1. CCD图像传感器的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  2. 徐小平:正在死亡的交易员们
  3. 半弧形进门鞋柜:小东西很考究
  4. 创建一个属于自己的小程序(注册开发账号)
  5. 汽车喇叭E-mark认证详情
  6. python 三个内置装饰器,python中自带的三个装饰器
  7. 用pygame_menu的十行代码给你的pygame添加一个狂拽炫酷炸的游戏菜单
  8. 机器学习:K-means算法基本原理及其变种
  9. 乐曲软件APP,钢琴键盘模拟器分享
  10. 提高背单词和记忆的效率:记忆辅助神器Anki