OpenNI XnSkeletonJointOrientation 簡單分析

之前在介紹 OpenNI 的人體骨架追蹤的時候(參考《透過 OpenNI / NITE 分析人體骨架》、《使用 Qt 顯示 OpenNI 的人體骨架》),有提到:「OpenNI 的人體骨架基本上是由「關節」(joint)來構成的,而每一個關節都有位置(position)和方向(orientation)兩種資料」;而在當時,Heresy 只有針對位置的資訊(position)做解釋,對於他提供的方向(orientation)資料,Heresy 算是直接跳過的。

Heresy 自己是覺得關節的方向資訊其實意義不是很大,所以本來是不打算下去研究的,不過由於最近很多人在問這邊的東西,所以 Heresy 大概還是整理一下相關的東西吧…

首先,OpenNI 所提供的關節點資訊,有三種不同的資料型別,分別是:

型別
內容
取得的函式
XnSkeletonJointPosition
關節的位置資訊
三度空間中的座標點
GetSkeletonJointPosition()
XnSkeletonJointOrientation
關節的方向資訊
三度空間中的座標軸方向
以 3×3 的矩陣、記錄三組向量
GetSkeletonJointOrientation()
XnSkeletonJointTransformation
包含前兩者的資料
GetSkeletonJoint()

其中,XnSkeletonJointTransformation 就是一個包含了 XnSkeletonJointPositionXnSkeletonJointOrientation 的資料型別,也是最完整的關節點資料。

XnSkeletonJointPosition 這個資料型別內,就是一個代表可信度的浮點數 fConfidence,以及代表座標點資訊、型別為 XnVector3D 的資料,裡面就是紀錄了 x / y / z 的資訊。

XnSkeletonJointOrientation 裡面,除了有同樣代表可以信度的 fConfidence 外,還有一個型別是 XnMatrix3X3 的陣列 orientation,代表了這個關節點的方向性;而在官方的《OpenNI Documentation》文件裡面的說明也相當簡單,就只有:

A joint orientation is described by its actual rotation and the confidence we have in that rotation

The first column is the X orientation, where the value increases from left to right.
The second column is the Y orientation, where the value increases from bottom to top.
The third column is the Z orientation, where the value increases from near to far.

他的意思就是說,這個矩陣裡第一欄的資料是代表 X 軸的方向,值是由左到右遞增,第二欄是 Y 軸的方向,值是由下到上遞增,第三欄則是 Z 軸的方向,值由近到遠遞增。

而詳細的使用方法呢?在 NITE 所提供的官方範例「StickFigure」內,其實是有範例的。在 StickFigure.cpp 這個檔案裡的 drawOrientation() 這個函式,在做的事情,就是把關節點的方向資訊給畫出來∼它的內容如下:

void drawOrientation(XnUserID user, xn::UserGenerator userGenerator, 
                     XnSkeletonJoint joint, const XnPoint3D& corner)
{
XnSkeletonJointOrientation orientation;
XnSkeletonJointPosition position;

userGenerator.GetSkeletonCap().GetSkeletonJointPosition(
                                               user, joint, position);
userGenerator.GetSkeletonCap().GetSkeletonJointOrientation(
                                               user, joint, orientation);

if (position.fConfidence != 1 &&
orientation.fConfidence != 1)
{
return;
}

XnSkeletonJointPosition virt1, virt2;
virt1.fConfidence = virt2.fConfidence = 1;

glColor3f(1,0,0);

virt1.position = position.position;
virt2.position = xnCreatePoint3D(
    virt1.position.X 100*orientation.orientation.elements[0],
virt1.position.Y 100*orientation.orientation.elements[3],
virt1.position.Z 100*orientation.orientation.elements[6]);
drawStickPoint(virt1, corner);
drawStickPoint(virt2, corner);

glColor3f(0,1,0);

virt1.position = position.position;
virt2.position = xnCreatePoint3D(
    virt1.position.X 100*orientation.orientation.elements[1],
virt1.position.Y 100*orientation.orientation.elements[4],
virt1.position.Z 100*orientation.orientation.elements[7]);
drawStickPoint(virt1, corner);
drawStickPoint(virt2, corner);

glColor3f(0,0,1);

virt1.position = position.position;
virt2.position = xnCreatePoint3D(
    virt1.position.X 100*orientation.orientation.elements[2],
virt1.position.Y 100*orientation.orientation.elements[5],
virt1.position.Z 100*orientation.orientation.elements[8]);
drawStickPoint(virt1, corner);
drawStickPoint(virt2, corner);

}

從這個範例程式裡面,可以看的出來,XnSkeletonJointOrientation::orientation 這個型別是 XnMatrix3X3 的資料,裡面實際上可以看成這樣三個向量:

  • X 軸方向:orientation.elements[0] / orientation.elements[3] / orientation.elements[6]
  • Y 軸方向:orientation.elements[1] / orientation.elements[4] / orientation.elements[7]
  • Z 軸方向:orientation.elements[2] / orientation.elements[5] / orientation.elements[8]

如果要把它轉換成 OpenNI 的向量的話,則可以透過 xnCreatePoint3D() 這個函示來做;比如說現在有一個 XnSkeletonJointOrientation 的變數 O1 的話,那就可以寫成:

XnVector3D X = xnCreatePoint3D( O1.orientation.elements[0], 
                                O1.orientation.elements[3],
                                O1.orientation.elements[6] );
XnVector3D Y = xnCreatePoint3D( O1.orientation.elements[1],
                                O1.orientation.elements[4],
                                O1.orientation.elements[7] );
XnVector3D Z = xnCreatePoint3D( O1.orientation.elements[2],
                                O1.orientation.elements[5],
                                O1.orientation.elements[8] );

這樣的形式。如此一來,建立出來的 XYZ,就是型別為 XnVector3D、分別代表這個關節點的 X / Y / Z 軸方向的向量了∼基本上,這三個向量應該會是兩兩互相垂直的。

而範例程式裡面所做的事,其實也就是把這三個向量、把它放大一百倍後,以不同的顏色畫在這個關節點的位置上了∼而結果,就會像是右邊的圖了;其中,紅色的線是該關節點的 X 軸、綠色的線是 Y 軸、深藍色的線則是 Z 軸。

其中要注意的是,在這個範例裡,他有去檢查該關節點的位置、以及方向的可信度;只要兩者中有一個的可信度不是 1,那就不會畫出來。而由於這個原因,再加上 NITE 演算法的關係,所以可以發現其實手(hand)和腳(foot)都是沒有關節的方向資訊的(似乎完全不會有、註 1)。

而接下來的問題,就是這些座標軸的向量,原始定義是怎麼定義的?這點在 OpenNI 或 NITE 的文件內,似乎都沒有明確的描述,不過仔細看看的話,可以發現他的座標定義,應該就是「Prime Sensor™ NITE Algorithms notes」這份文件裡的那張圖(下圖、註 3)了∼

而仔細研究的話,可以發現:以右手手肘(XN_SKEL_RIGHT_ELBOW)來說,這個關節點的 X 軸的向量,基本上就是手臂的方向;而 Y 軸的向量,則就是手臂的方向和上臂方向(註 4)的向量外積、也就是這個關節主要的旋轉軸。最後的 Z 軸呢,則就是透過 X / Y 的外積所產生,讓這三個向量互相垂直的結果。

而其他的關節點,雖然像頭部、軀幹、肩膀等關節點,其實條件都比較不一樣,但是理論上應該也都是透過類似這樣的方法來做處理的;而究竟是怎麼實作的,這就要取決於 NITE 內部的演算法了∼

至於這個關節到底旋轉了幾度呢?實際上 OpenNI 應該是沒有提供這樣的資訊的,如果有需要的話,是需要自己透過前後的關節點的位置、或是方向,來進行計算的;而一個比較直覺的作法,應該就是透過和前一個關節的方向來做比較,看看這個關節做了怎樣的旋轉吧∼(註 5)


附註:

  1. 可信度(fConfidence)的型別是浮點數,理論上應該只有在其值為 0 的時候,這項資料才是沒意義的;但是 NITE 的範例裡、在這邊只要是「非 1」就跳過了。

  2. 承 1,實際上在測試的時候,fConfidence 還會有 0.5 這樣的值,個人猜測是因為個關節點的資訊其實都要靠相連的關節來做判斷,所以以 0.5 來說,可能就是其中一半需要參考的關節沒有抓到的關係。不過這點只是 Heresy 自己的猜測。

  3. NITE 所定義的骨架,預設是應該以 mirror(鏡射)模式來做處理的;也就是就像在照鏡子一樣,左右是顛倒的。

  4. 右手手臂的方向基本上就是 XN_SKEL_RIGHT_ELBOWXN_SKEL_RIGHT_HAND 構成的向量、上臂的方向則是 XN_SKEL_RIGHT_ELBOWXN_SKEL_RIGHT_SHOULDER 構成的向量。

  5. 求兩個向量的夾角,基本上可以利用向量內積的定義來解(求 acos),請參考維基百科

24 thoughts on “OpenNI XnSkeletonJointOrientation 簡單分析”

  1. 你好,我在运行stickfigure的时候,根本无法运行,提示是没有在debug文件夹中找到exe文件,不知道你是否有这个情况,求指教

  2. 你是自己用 VC 重新建置嗎?
    如果是的話,要注意一下就是他有修改預設檔案的輸出路徑,所以必須要做一些專案設定上的修改。

  3. 不是,我是用vs2010去打开他的工程文件的,然后就打不开,他发布的debug里面的程序也是无法运行的。

  4. to yaoshunyu

    執行範例建議執行
    C:\Program Files\PrimeSense\NITE\Samples\Bin\Release 下編譯好的檔案

    如果要自己開啟專案的話,在 WIndows Vista 或 Windows 7 這種有針對 Program Files 做權限控制的系統,你必須把整個 sample 資料夾複製到別的地方來做修改。

  5. 我按照你的方法试了试,还是打不开,他是在启动的一瞬间直接关闭,你可以把你可以运行的工程文件发我么,307279991@qq.com

  6. to yaoshunyu
    那應該是執行不成功,有可能是沒辦法正確執行造成的。
    建議你開一個命令提示字元,透過命令提示字元去執行,這樣就可以看到錯誤訊息了。

  7. 谢谢哦,已经正常运行了,正在下一步研究,有问题再请教

  8. 请问,orientation的第一列是不是就是沿x轴方向的向量呢,就是说,他应该是(1,0,0)么?这个orientation的矩阵的9个元素是怎么回事呢?

  9. 比如说,在世界坐标系中,orientation是xy平面上y=x的方向,是不是orientation就是(1,0,0
    0,1,0
    0,0,0)

  10. 比如说,在世界坐标系中,orientation是xy平面上y=x的方向,是不是orientation就是(1,0,0
    0,1,0
    0,0,0)

  11. to yaoshunyu
    orientation 所代表的是他的區域座標系統的 X, Y, Z 軸。
    當雙手水平舉起、整個姿勢擺出像上面示意圖的樣子的時候,這時候 X, Y, Z 就會和真實世界座標軸系統完全一致。此時矩陣就會是一個單位矩陣(Identity matrix),也就是
    1 0 0
    0 1 0
    0 0 1

    但是當關節位置、角度有所變化後,就會變得不一致、而形成一個新的座標系統。

    另外,由於他是代表一個座標系統,所以裡面三個向量會是 orthonormal 的,所以你舉的例子是錯誤的。

  12. 请问一下:有没有可以得到当前kinect采集图像的时间的函数?就是要把那个时间取出来,谢谢

  13. to lx

    map meta data 都有 Timestamp() 這個函式可以使用,或許可以試試看。

  14. if (position.fConfidence != 1 &&
    orientation.fConfidence != 1)
    {
    return;
    }
    这个的意思不是 两个的可信度都不是1的时候 才不会画出来? 为什么您原文说是 只要兩者中有一個的可信度不是 1,那就不會畫出來?

  15. hi,heresy..
    有没有具体的文档说明这个Orientation或得到的x、y、z轴分别代表什么?

    openni的GetSkeletonJointOrientation获取的数据有时会出错,即错误的方向…尤其是双脚(左脚),总是扭转了180度..本来朝前的,变成朝后了,这个应该是z轴控制的吧?
    请看图:
    https://trello-attachments.s3.amazonaws.com/521c28a239b44d202500099e/522ec1b9cc0b87004f00590f/94886f16c1542a45c566f5f42f4e7d8f/sinbad_wrong_hip.jpg

    我有一个办法就是判断hip的z轴与troso的z轴的夹角(大于90度)来过滤一些错误的朝向,但是如果想要正确的把脚扭回来,把错误的orientation改成正确的应该怎么处理?简单只是“z轴”换成“z轴的中心对称轴”我测试了不是答案…

  16. a.这个Orientation的x、y、z是两两垂直的么?
    b.想要这个关节旋转回来180度,是让y轴、z轴让x轴逆时针旋转180度么?

  17. to gangan1121

    基本上 OpenNI / NiTE 對於這部分的說明,就只有「Prime Sensor™ NITE Algorithms notes」這份文件,也就是本文中附圖內的那張圖,以及一些簡單的說明;他的 X/Y/Z 軸,基本上就是根據那張參考圖定義出來的。

    而至於要對哪個軸作旋轉才能修正,應該也會需要考慮是哪一個關節點;如果是腳的話,應該是要對關節的 Y 軸旋轉來做調整。
    但是 Heresy 自己沒有做過這樣的事,所以也不確定這樣處理是不是最好的。

  18. heresy…
    我把这个GetSkeletonJointOrientation的x、y、z轴画出来看,但是好像与初始坐标不对应,有些地方把他进行下面的变换(注意负号),画出来的就是相应初始xyz的旋转…
    X 軸方向:orientation.elements[0] / – orientation.elements[3] / orientation.elements[6]
    Y 軸方向:- orientation.elements[1] / orientation.elements[4] / – orientation.elements[7]
    Z 軸方向:orientation.elements[2] / – orientation.elements[5] / orientation.elements[8]

    GetSkeletonJointOrientation得到的x、y、z的向量对应的坐标系是?为什么变换后的看上去才像是对应的?

  19. @ gangan1121
    抱歉,不太確定你的狀況?
    個人會建議你先參考看看 NiTE 官方的範例「StickFigure」看看。

  20. 想請問一下heresy神大
    如果我只想要抓取某關節點(EX:right hand)的XYZ座標 應該要怎樣修改呢?

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

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