之前在介紹 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)


附註:

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

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

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

  4. 右手手臂的方向基本上就是 XN_SKEL_RIGHT_ELBOW 到 XN_SKEL_RIGHT_HAND 構成的向量、上臂的方向則是XN_SKEL_RIGHT_ELBOW 到 XN_SKEL_RIGHT_SHOULDER 構成的向量。

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

OpenNI XnSkeletonJointOrientation 簡單分析相关推荐

  1. 簡單編譯內核 linux kernel gnu

    2019独角兽企业重金招聘Python工程师标准>>> 需要 make, gcc 下戴內核檔 http://www.kernel.org 成為 root 進入 /usr/src 解壓 ...

  2. JSP + AJAX 打造簡單聊天室

    本文代碼下載地址:http://download.csdn.net/source/798197 一個簡單的聊天室程式,但基本包含了簡單的AJAX的使用方法,可以做為簡單的Demo用來學習. 整個程式包 ...

  3. D - F e n d 簡 單 教 學 DOSBOX Easy Shell 【玩DOS游戏的必备软件】

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow D - F e n ...

  4. matlab 白色像素点,MATLAB 簡單的計算白色輪廓中像素點的個數

    近來,有朋友問到,如何計算白色輪廓中的像素點的個數.我在這里就舉一個超級簡單的例子,就是假設一副二值圖片,其背景是黑色的,而你的邊緣是白色的,而且你的白色邊緣中不包含黑色的點,就如附件中的那個圖像.下 ...

  5. Perl 第二章 簡單變量

    基本上,簡單變量就是一個數據單元,這個單元可以是數字或字符串. [ 整型 ] PERL最常用的簡單變量,由於其與其它語言基本相同,不再贅述.例︰ $x = 12345; if (1217 + 116 ...

  6. python3librequest_python3.x學習之urilib.request簡單學習

    參考鏈接:https://docs.python.org/3/library/urllib.request.html#module-urllib.request (翻譯有誤之處請見諒,我還是個初學者. ...

  7. 遊戲是這樣寫成的 (第三篇: 簡單的遊戲框架)

    遊戲是這樣寫成的 (第三篇: 簡單的遊戲框架) 通過上一篇, 我們已有個基本的畫圖功能, 這次讓我們弄一個簡單的遊戲框架吧! 其實一般的遊戲, 大至有兩個主要的函數就可以了: update 和 ren ...

  8. Csharp windowform bindingNavigator,bindingSource,DataGridView簡單分頁:首頁,上一頁,下一頁,末頁...

    /// <summary>/// 塗聚文 2011-10-24 (參考相關網絡和書藉資料)/// 締友計算機信息技術有限公司///C# Winform 簡單分頁: 首頁,上一頁,下一頁,末 ...

  9. FMDB與SQLite 數據庫應用示範:打做一隻簡單的電影資料庫 App

    原文:http://www.appcoda.com/fmdb-sqlite-database/ 作者:GABRIEL THEODOROPOULOS 譯者:kmyhy 通常在 App 中使用數據庫并處理 ...

  10. android 全景播放器,Android VR Player(全景視頻播放器) [5]:簡單的歡迎界面

    Android VR Player(全景視頻播放器) [5]:簡單的歡迎界面 歡迎界面 在繼續下一部分,即視頻列表實現的介紹前,分享一下簡單的歡迎界面的實現.一來是可以整合一下前面說的側滑菜單和底部導 ...

最新文章

  1. 如何干掉恶心的 SQL 注入?
  2. android 加载html6,WebView使用总结2(加载HTML内容形式的String)
  3. Pavel and Triangles(贪心)
  4. 白话详细解读(一)-----GoogLeNet(Inception V1-Inception V3)
  5. 关于解决form表单记录上次保存填写记录清空
  6. java jcifs 速度_java – JCIFS:文件检索太慢而无法使用
  7. 对于enable_shared_from_this、shared_from_this使用笔记
  8. Linux 内核源代码的结构
  9. 实验2-4-3 求平方根序列前N项和 (C语言)
  10. 台式电脑网络连接配置异常_电脑上不了网,360断网急救箱显示网络连接配置和网络存在問題,点击修复。网络连接配置修复了,网络存......
  11. Html设置图片大小代码
  12. 数据分析的同比和环比以及其在excel中的应用
  13. Android:WebView使用常见问题汇总(持续更新)
  14. U3D游戏开发框架(四)——音频管理器
  15. 如何组建权责明确、运营高效的数据团队
  16. deepin启动盘制作工具_YUMI——多重引导制作工具
  17. (附源码)计算机毕业设计SSM幼儿园管理系统
  18. JavaScript Array --map()、filter()、reduce()、forEach()函数的使用
  19. 写给地方网站的创业新手(转载)
  20. 简单网店php,Php免费商城系统让你白手起家开网店

热门文章

  1. Event Listener's Adapter Classes
  2. sqlserver日期函数
  3. android service 的各种用法(IPC、AIDL)
  4. Windows 8实用窍门系列:11.Windows 8 中的Toast Tile Badge通知
  5. Sun发布MySQL 5.4 响应速度提升90% ?
  6. python基本编程技巧_Python编程小白入门技巧,从入门到精通只需一个月。
  7. 两个矩阵是否相交的算法_个性化推荐召回算法——Personal Rank
  8. php 26进制转10进制,PHP 10进制转62进制
  9. Linux基础之tr与重定向管道
  10. jmeter的http cookies管理器使用