透過 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_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 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 產生的

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

43 thoughts on “透過 OpenNI / NITE 分析人體骨架(上)”

  1. 请问OpenNI跟NITE是什么关系呢?NITE的具体定义什么丫?额,刚刚接触,不太懂,请赐教

  2. 很有用的文章
    我在ubuntu11.04下参照教程安装了openni,nite 还有driver, image generator 跟 depth generator 都能正常工作, 但是每到定义创建新的其他的node 的时候就出错:
    ‘one or more of the following nodes could not be enumerated:’
    设置samplesconfig.xml也没有用
    你有什么建议么?

  3. 你的文章對於我幫助很大謝謝!
    我想請問一下幾個問題
    由於我沒學過C 問題可能瞞蠢的呵

    我按照您其他篇所講的方法安裝了openni.nite和sensor
    然後我測試您這段程式,不過#include 本身
    是在openni裡的h檔.我不知道要怎麼讓他們兩個結合.我有嘗試
    將openni裡#include 的黨放到visual c 2010 的include目錄底下但是未能成功編譯 不好意思我想請問一下我犯了什麼錯誤

  4. ㄜ..我不知道為什麼字不見了
    不過#include < XnCppWrapper.h > 本身

  5. to kavin

    基本上,header file 不用複製到別邊,只是你要設定好 include path。
    請參考《[url=http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=215]透過 OpneNI 讀取 Kinect 深度影像資料[/url]》,裡面有專案設定的簡單說明。

  6. 您好~
    // 11. get users
    XnUserID* aUserID = new XnUserID[nUsers];
    mUserGenerator.GetUsers( aUserID, nUsers );

    我在Qt下照抄您的代码,可是编译的时候:
    mUserGenerator.GetUsers( aUserID, nUsers );
    这句通不过。
    error: no matching function for call to ‘xn::UserGenerator::GetUsers(XnUserID*&, XnUInt8&)’
    不知道怎么回事?

  7. to 林雄民

    您好,這邊的程式整段直接拿來編譯應該是沒問題的。
    以你的錯誤訊息來看…
    應該是呼叫 GetUsers() 時,參數的型別有誤所造成的 ;請確定你的 nUsers 這個變數的型別是 XnUInt16。(你的 nUsers 似乎是 XnUInt8?)

  8. 是的,我解决了,的确是这个错误。

    现在编译通过了,可是,连上kinect运行的时候,
    Starting /home/bear/QtProgram/opencv001-build-desktop/opencv001…
    new user identified:1

    接着等了老长老长时间(目测有两分钟),没有下文啊。。

    还有,程序里面
    // 12. check each user
    bool bGetSkeleton = false;
    这个变量好像没有用到?

  9. to 林雄民

    這個程式是在 NITE 1.5 之前的範例版本,你需要擺出 Psi 校正姿勢才會進行骨架的校正以及追蹤。

    而 bGetSkeleton 的確是在這邊沒有用到,算是忘了刪掉的東西。

    StickFigure 是指安裝 NITE 後,他所提供的範例,預設檔案會在 C:Program FilesPrimeSenseNITESamplesStickFigure

  10. 你好,我在做骨骼跟踪时发现摄像头移动时,目标非常容易丢失,但是摄像头移动比较慢时相对不怎么丢失,是不是由于该算法的计算量比较大造成的呢?如果我把深度数据裁剪成320*240,是不是就会不丢失呢?

  11. to TDT
    這動作偵測的演算法很多本來就是在背景不動的前提下設計的,所以當攝影機會移動的時候,會出現問題其實算是一個不讓人意外的問題。
    而如果真是演算法的問題的話,降低解析度基本上也不會有什麼幫助。

  12. heresy,你好!
    我已经得到各个关节点的坐标以及深度图相,但是不知道怎样将它二者用opencv画在一幅图中,就如同文章顶部所示图片一样!请不吝赐教!

  13. to 小书童

    抱歉,Heresy 沒有使用 OpenCV,這部分可能要請你自行查詢 OpenCV 的使用說明。

  14. 谢谢!
    还有个问题,请问人体骨架的坐标和真实世界坐标是一样的吗?如果不一样,二者各是什么样的?要怎么样才能统一到同一坐标系下?

  15. heresy,你好!
    StickFigure 这个例程中人的轮廓和骨架信息只能在一个很小的范围内显示,能够调整使之铺满整个显示窗口吗?我做了很多的尝试,但是始终不得其法,所以想请教heresy!

  16. heresy, 你好
    最近嘗試把上述的程式碼加入介面裡
    專案類型使用windows form application
    但在register callback function時總會出現問題
    但在原本的主控台應用程式卻是可以執行的
    想請問你發展open相關的GUI通常會用什麼開發工具呢
    謝謝!

  17. 抱歉,Heresy 沒有在使用 Windows Form 進行開發,所以不確定你的問題。
    不過,Heresy 這邊使用 OpenNI,都是採用他的 C 介面,對應到 Visual Studio 裡,就是 Win32 / Native C 的開發環境;而沒弄錯的話,微軟的 Windows Form 基本上應該算是 managed C 的東西,兩者是有一定程度的差異的。

    Heresy 自己是沒試過,不過在這種環境下,去使用 OpenNI 提供的 .Net Wrapper 或許會比較好。

    另外,在 Native C 的環境下,還是有很多圖形介面的開發工具。Heresy 這邊目前是使用 Nokia Qt 來做視窗介面。
    http://kheresy.wordpress.com/2011/08/18/show_maps_of_openni_via_qt_graphicsview/

  18. Heresy,你好!
    请教你个问题:我看了下OpenNI的API,看到有kinect保存录像再读取进来的功能,是.ONI的格式。想问下是否能用OpenNI去读取其它的摄像头,这个摄像头也有深度图,彩色图,也就是把硬件整个替换,将深度图和彩色图作为输入的数据,请问有没有例子或者解决方案?谢谢你了!

  19. to 残云52011
    你的需求基本上應該是需要自己去寫一個 Depth Generator 和 ImageGenerator 了。
    也就是不要使用現有的 Sensor 的模組,自己根據現有的硬體來寫一個新的。

  20. 谢谢!但是仅仅Gesture Generator就可以识别手势,所以我猜测深度图从硬件传输到PC上,然后被Gesture Generator模块使用,应该在Context Init时就进行了相应的配置,在StartGeneratingAll()后,硬件的图像数据就不断的传输过来,Gesture Generator模块进行识别,而Depth Generator 模块也仅仅是将图像读出来继续使用,不知道对不对,请Heresy指点下。
    另外,如果把图像数据输入这块替换成自己的摄像头,是不是要去改变OpenNI的开发库,有没有实现的可能性?哪里有OpenNI的源码?

  21. to 残云52011

    OpenNI 本身本來就是 Open Source 的,在官方網站上就可以找到原始碼的下載連結了。
    http://openni.org/Downloads/OpenSources.aspx

    不過基本上 OpenNI 本身就有建立一個有擴充性的 framework。
    以你的需求來說,並沒有必要去修改 OpenNI 本身的原始碼;比較好的方法,應該是去另外寫一個對應自己裝置的 depth generator、然後再放到 OpenNI 的框架裡。

    官方文件裡面有一段「Creating OpenNI Modules 」,就是在講如何建立自己的 production node;而官方範例裡面有一個 NiSampleModule,建議可以參考看看。

  22. 你好Heresy,我用的是ASUS Xtion Pro,前几天根据你的代码实现了手的跟踪。后来看NiSampleModule这部分,今天跑前几天的代码有问题了,尝试NiViewer.exe,发现没有深度图了,是一片黄色,并且传感器发射红外的地方也不亮了。难道是我的硬件坏了?

  23. to 残云52011
    抱歉,Heresy 沒碰過類似的問題。
    不過,不知道在建立 Depth Generator 時是否有錯誤?
    是否有試過換一台電腦試試看?

  24. 昨天那个错误是因为我自己写的depth模块注册成功,而我给深度图随便赋了同样的值。所以DepthGenerator取出的图像数据是我的模块里的,并不启动ASUS传感器。
    现在想用GestureGenerator,在RegisterGestureCallbacks这里出现错误,不知道是什么原因?
    我模块里继承了ModuleDepthGenerator,所以DepthGenerator可以用,难道还要继承ModuleGestureGenerator,才可以用GestureGenerator,请教Heresy

  25. 补充:在xn::GestureGenerator mGesture;
    mGesture.Create(context);这两句执行后,DOS框里输出一句话“Couldn’t get max shift value”

  26. to 残云52011
    抱歉,Heresy 沒有自己寫過 generator。

    不過 user generator 出現 “Couldn’t get max shift value” 的錯誤訊息,這幾天在和人研究 mockDepthGenerator 時也有碰到。
    目前 Heresy 的想法是:
    這個錯誤應該是因為 User generator 無法在使用的 Depth Generator 裡面,找到某些資料的關係。
    而這些資料資料應該算是 PrimeSense 自己針對自家的感應器加上得額外的資料,並沒有列在 OpenNI 的官方規裡,而是透過 Property 的形式來做存取的。所以如果要讓 NITE 的 User Generator 可以使用自己的 Depth Generator,就必須把這些可能是沒有文件定義的資料都補上。
    而比較有可能的方法,可能就是去參考官方的硬體模組的原始碼了…

    不過,另外要提醒一下的是,NITE 的使用授權基本上是只允許使用 PrimeSense 的硬體(Kinect 和 Xtion 都是)使用其相關功能,如果你是搭配其他廠商的 Depth Generator 來使用的話,應該算是違反它的使用協議的。

  27. Heresy您好!我把您的代码编译了一遍可以通过,但是运行时出错了。我用断点跟踪,发现程式在mUserGenerator.RegisterUserCallbacks( NewUser, LostUser, NULL, hUserCB )这个函数出错了。再跟踪这个函数进去,是xnRegisterUserCallbacks(GetHandle(), NewUserCallback, LostUserCallback, pUserCookie, &pUserCookie->hCallback)这个函数出错。请问Heresy大大知道是什么原因吗。

  28. 补充:好像是mUserGenerator这里出现了问题,没办法正常创建。。

  29. to Phantom

    可否麻煩確認一下問題是什麼?
    有沒有可能是你的 OpenNI 環境有問題?
    OpenNI 和 NiTE 的版本如果不符合的話,是可能會出問題的。

  30. 报告Heresy大大,问题解决了。。好像是开发环境没配置好的问题。。

  31. 不好意思请问一下..感觉openni处理的图片或者视频都必须是通过Kinect实时输入的..对吗?可不可以不通过Kinect直接输入已有的视频或者图片然后提取深度信息或者跟踪骨骼信息呢?

  32. to bucktoothsir
    你可以把資料錄製成 OpenNI 專屬的 ONI 格式,來做後續使用。

  33. 您好。看见您回答的可以把视频录制成oni的格式然后直接输入,但是感觉oni格式的视频现在只有Openni通过Kinect才能生成啊,可以通过其它方式来录制吗?

  34. to allenwwj
    除非要透過原始碼解析他的格式,然後自行處理,不然現階段只能透過 OpenNI 的 API 來錄製。

發佈回覆給「TDT」的留言 取消回覆

發佈留言必須填寫的電子郵件地址不會公開。