在前一篇《NiTE2 基本使用》裡,已經大致講了在 OpenNI 2 的架構下,NiTE2 的基本使用方法。不過,在那篇文章裡,主要是在講整個 NiTE 的架構和使用概念,並沒有講到細節;尤其一般人會使用 NiTE,大多都是為了去追蹤人的骨架,而這點在上一篇文章中,並沒有提到。所以這一篇,就來補充這一部分,來針對 NiTE 2 的 UserTracker 來做比較完整的說明~
在 OpenNI 1.x 的時候,OpenNI 是採取了一種比較複雜的 callback 事件,來做為人體骨架的辨識、追蹤的開發模式(參考),在使用上其實相對繁瑣;而在 OpenNI 2 NiTE 2 的架構,要進行人體骨架的辨識、追蹤,算是相對簡單不少了~下面就是一段簡單的讀取頭部位置的範例程式: // STL Header #include <iostream>   // 1. include NiTE Header #include <NiTE.h>   // using namespace using namespace std;   int main( int argc, char** argv ) { // 2. initialize NiTE nite::NiTE::initialize();   // 3. create user tracker nite::UserTracker mUserTracker; mUserTracker.create();   nite::UserTrackerFrameRef mUserFrame; for( int i = 0; i < 300; i ) { // 4. get user frame mUserTracker.readFrame( &mUserFrame );   // 5. get users' data const nite::Array<nite::UserData>& aUsers = mUserFrame.getUsers(); for( int i = 0; i < aUsers.getSize(); i ) { const nite::UserData& rUser = aUsers[i]; if( rUser.isNew() ) { cout << "New User [" << rUser.getId() << "] found." << endl; // 5a. start tracking skeleton mUserTracker.startSkeletonTracking( rUser.getId() ); } if( rUser.isLost() ) { cout << "User [" << rUser.getId()  << "] lost." << endl; }  
// 5b. get skeleton const nite::Skeleton& rSkeleton = rUser.getSkeleton(); if( rSkeleton.getState() == nite::SKELETON_TRACKED ) { // if is tracked, get joints const nite::SkeletonJoint& rHead = rSkeleton.getJoint( nite::JOINT_HEAD ); const nite::Point3f& rPos = rHead.getPosition(); cout << " > " << rPos.x << "/" << rPos.y << "/" << rPos.z; cout << " (" << rHead.getPositionConfidence() << ")" << endl; } } } nite::NiTE::shutdown();   return 0; }
基本說明 這個範例程式基本上是從《NiTE2 基本使用》這個範例做延伸的,黃底的部分,就是增加的部分。可以看到,如果只是單純要針對使用者做骨架的追蹤,以及關節位置資料的讀取,其實程式真的相當單純,比 OpenNI 1.x 的時候,簡單了許多。 首先,在主迴圈內,每次透過 UserTracker 的 readFrame() 來取得新的資料的時候,在讀取出來的 mUserFrame 內,都可以透過 getUsers() 這個函式,來取得當下的使用者列表;而每一個使用者的資料,都是一個 UserData 的物件,裡面儲存著使用者的資料、以及狀態。由於使用者的偵測,是 UserTracker 自己會進行的,所以這邊不需要其他的步驟,只要把使用者列表讀出來就可以了。 而在骨架追蹤的部分,由於 NiTE 基本上是讓程式開發者自行決定要針對那些使用者進行骨架的追蹤,所以並不會在找到使用者的時候,就自行開始追蹤骨架;因此,如果要針對使用者進行骨架追蹤的話,就需要呼叫 UserTracker 的 startSkeletonTracking() 這個函式,指定要針對哪一個使用者,進行骨架的追蹤。 在最簡單的狀況下,就是在每一次更新的時候,針對每一個使用者,都透過 isNew() 這個函式來判斷是否為新的使用者,如果是新的使用者的話,就開始追蹤這個使用者的人體骨架;這部分,就是上面範例程式裡面,「5a」的部分了。如果有需要停止使用者的骨架追蹤的話,也可以使用 stopSkeletonTracking() 來針對個別的使用者,停止骨架的追蹤。 另外,NiTE 2 也捨棄了 OpenNI 1.x 可以選擇要追蹤那些關節的功能,現在都是固定去追蹤全身的骨架,不能像以前一樣,可以只追蹤上半身或下半身了。
讀取骨架、關節資料 針對每一個有被追蹤的使用者,則可以透過 UserData 的 getSkeleton() 這個函式,來取得該使用者的骨架資料;getSkeleton() 回傳的資料會是 nite::Skeleton 這個型別的資料,他基本上只有兩個函式可以用,一個是 getState(),是用來取得目前的骨架資料的狀態的,另一個則是 getJoint(),是用來取得特定關節點的資訊的。 基本上,在使用讀取骨架的資料之前,最好先對骨架資料的狀態,做一個簡單的確認;如果是有被正確追蹤的話,得到的狀態應該會是 nite::SKELETON_TRACKED,這樣才有繼續使用的意義。 當確定這筆骨架資料是有用的之後,接下來就可以透過 getJoint() 這個函式,來取得各個關節點的資料了~在 NiTE 2 裡可以使用的關節點,是定義成 nite::JointType 這個列舉型別,它包含了下列十五個關節: - JOINT_HEAD
- JOINT_NECK
- JOINT_LEFT_SHOULDER
- JOINT_RIGHT_SHOULDER
- JOINT_LEFT_ELBOW
- JOINT_RIGHT_ELBOW
- JOINT_LEFT_HAND
- JOINT_RIGHT_HAND
- JOINT_TORSO
- JOINT_LEFT_HIP
- JOINT_RIGHT_HIP
- JOINT_LEFT_KNEE
- JOINT_RIGHT_KNEE
- JOINT_LEFT_FOOT
- JOINT_RIGHT_FOOT
這十五個關節點,基本上和 OpenNI 1.x 所支援的是相同的,很遺憾,還是不支援手腕和腳踝。 而各關節點透過 getJoint() 這個函式所取得出來的的資料,型別則是 nite::SkeletonJoint,裡面記錄了他是哪一個關節(JointType),以及這個關節目前的位置(position)和方向(orientation);而和 OpenNI 1.x 時相同,他也同時有紀錄位置和方向的可靠度(confidence)。 而如果是要取得關節點的位置的話,就是使用 SkeletonJoint 所提供的 getPosition() 這個函式, 來取得該關節點的位置;而得到的資料的型別會是 nite::Point3f,裡面包含了 x、y、z 三軸的值,代表他在空間中的位置。他的座標系統基本上就是之前介紹過的、在三度空間內所使用的「世界座標系統」(參考《OpenNI 2 的座標系統轉換》),如果需要把它轉換到深度影像上的話,可以使用 UserTracker 所提供的 convertJointCoordinatesToDepth() 這個函式來進行轉換。(如果要用 OpenNI 的 CoordinateConverter 應該也是可以的。) 不過,由於關節不見得一定準確,如果肢體根本是在攝影機的範圍之外的話,NiTE 也就只能靠猜的,來判斷位置了…而在這種狀況下,位置的準確性會相當低。所以在使用關節位置的時候,個人會建議最好也要透過 getPositionConfidence() 這個函式,來確認該關節位置的可靠度,作為後續處理的參考;他回傳的值會是一個浮點數,範圍是 0 ~ 1 之間,1 代表最可靠、而 0 則是代表純粹是用猜的。 而在上面的例子裡,就是去讀取頭部這個關節點(JOINT_HEAD)的資料,並把它的位置、可靠度都做輸出;如果要得到全身的骨架的資料的話,只要依序針對 15 個關節做讀取就可以了。 而至於關節的方向性的部分,如果需要的話,則是使用 getOrientation() 來做讀取;而讀取出來的資料,則不是像之前 OpenNI 1.x 一樣是一個陣列,而是採用「Quaternion」來代表他的方向。由於他在概念上算是比較複雜一點的東西,所以在這邊就先不提了,等之後有機會再來講吧…
關節資訊的平滑化 由於關節點的資訊在計算的時候,有可能會因為各式各樣的因素,導致有誤差的產生,進一步在人沒有動的情況下,有抖動的問題,所以這個時候,就可能會需要針對計算出來的骨架資訊,做平滑化的動作。和在 OpenNI 1.x 的時候相同,NiTE 2 一樣可以控制人體骨架追蹤的平滑化的參數。在 NiTE 2 裡,透過 UserTracker 提供的 setSkeletonSmoothingFactor(),設定一個 0 ~ 1 之間的福點數,就可以調整關節資訊的平滑化程度了~ 如果給 0 的話,就是完全不進行平滑化,值愈大、平滑化的程度越高,但是如果給 1 的話,則是會讓關節完全不動。至於要用多大的值?這點就要看個人的應用來決定了。
|