之前在介紹 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 就是一個包含了 XnSkeletonJointPosition 和 XnSkeletonJointOrientation 的資料型別,也是最完整的關節點資料。
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] );
這樣的形式。如此一來,建立出來的 X、Y、Z,就是型別為 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)
附註:
-
可信度(fConfidence)的型別是浮點數,理論上應該只有在其值為 0 的時候,這項資料才是沒意義的;但是 NITE 的範例裡、在這邊只要是「非 1」就跳過了。
-
承 1,實際上在測試的時候,fConfidence 還會有 0.5 這樣的值,個人猜測是因為個關節點的資訊其實都要靠相連的關節來做判斷,所以以 0.5 來說,可能就是其中一半需要參考的關節沒有抓到的關係。不過這點只是 Heresy 自己的猜測。
-
NITE 所定義的骨架,預設是應該以 mirror(鏡射)模式來做處理的;也就是就像在照鏡子一樣,左右是顛倒的。
-
右手手臂的方向基本上就是 XN_SKEL_RIGHT_ELBOW 到 XN_SKEL_RIGHT_HAND 構成的向量、上臂的方向則是 XN_SKEL_RIGHT_ELBOW 到 XN_SKEL_RIGHT_SHOULDER 構成的向量。
-
求兩個向量的夾角,基本上可以利用向量內積的定義來解(求 acos),請參考維基百科。
我想请问一下Position的0,0,0点是位于哪里呢?
他的世界座標系統的原點是 device 所在的位置
你好,我在运行stickfigure的时候,根本无法运行,提示是没有在debug文件夹中找到exe文件,不知道你是否有这个情况,求指教
你是自己用 VC 重新建置嗎?
如果是的話,要注意一下就是他有修改預設檔案的輸出路徑,所以必須要做一些專案設定上的修改。
不是,我是用vs2010去打开他的工程文件的,然后就打不开,他发布的debug里面的程序也是无法运行的。
to yaoshunyu
執行範例建議執行
C:\Program Files\PrimeSense\NITE\Samples\Bin\Release 下編譯好的檔案
如果要自己開啟專案的話,在 WIndows Vista 或 Windows 7 這種有針對 Program Files 做權限控制的系統,你必須把整個 sample 資料夾複製到別的地方來做修改。
我按照你的方法试了试,还是打不开,他是在启动的一瞬间直接关闭,你可以把你可以运行的工程文件发我么,307279991@qq.com
to yaoshunyu
那應該是執行不成功,有可能是沒辦法正確執行造成的。
建議你開一個命令提示字元,透過命令提示字元去執行,這樣就可以看到錯誤訊息了。
谢谢哦,已经正常运行了,正在下一步研究,有问题再请教
请问,orientation的第一列是不是就是沿x轴方向的向量呢,就是说,他应该是(1,0,0)么?这个orientation的矩阵的9个元素是怎么回事呢?
比如说,在世界坐标系中,orientation是xy平面上y=x的方向,是不是orientation就是(1,0,0
0,1,0
0,0,0)
比如说,在世界坐标系中,orientation是xy平面上y=x的方向,是不是orientation就是(1,0,0
0,1,0
0,0,0)
to yaoshunyu
orientation 所代表的是他的區域座標系統的 X, Y, Z 軸。
當雙手水平舉起、整個姿勢擺出像上面示意圖的樣子的時候,這時候 X, Y, Z 就會和真實世界座標軸系統完全一致。此時矩陣就會是一個單位矩陣(Identity matrix),也就是
1 0 0
0 1 0
0 0 1
但是當關節位置、角度有所變化後,就會變得不一致、而形成一個新的座標系統。
另外,由於他是代表一個座標系統,所以裡面三個向量會是 orthonormal 的,所以你舉的例子是錯誤的。
请问一下:有没有可以得到当前kinect采集图像的时间的函数?就是要把那个时间取出来,谢谢
to lx
map meta data 都有 Timestamp() 這個函式可以使用,或許可以試試看。
if (position.fConfidence != 1 &&
orientation.fConfidence != 1)
{
return;
}
这个的意思不是 两个的可信度都不是1的时候 才不会画出来? 为什么您原文说是 只要兩者中有一個的可信度不是 1,那就不會畫出來?
to OnePiece
恩,這邊應該是寫的時候弄錯了…
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轴的中心对称轴”我测试了不是答案…
a.这个Orientation的x、y、z是两两垂直的么?
b.想要这个关节旋转回来180度,是让y轴、z轴让x轴逆时针旋转180度么?
to gangan1121
基本上 OpenNI / NiTE 對於這部分的說明,就只有「Prime Sensor™ NITE Algorithms notes」這份文件,也就是本文中附圖內的那張圖,以及一些簡單的說明;他的 X/Y/Z 軸,基本上就是根據那張參考圖定義出來的。
而至於要對哪個軸作旋轉才能修正,應該也會需要考慮是哪一個關節點;如果是腳的話,應該是要對關節的 Y 軸旋轉來做調整。
但是 Heresy 自己沒有做過這樣的事,所以也不確定這樣處理是不是最好的。
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的向量对应的坐标系是?为什么变换后的看上去才像是对应的?
@ gangan1121
抱歉,不太確定你的狀況?
個人會建議你先參考看看 NiTE 官方的範例「StickFigure」看看。
想請問一下heresy神大
如果我只想要抓取某關節點(EX:right hand)的XYZ座標 應該要怎樣修改呢?
to CK
請參考之前的文章
http://viml.nchc.org.tw/blog/paper_info.php?CLASS_ID=1&SUB_ID=1&PAPER_ID=219