Kinect開發學習筆記之(七)骨骼資料的提取
【原文:http://blog.csdn.net/zouxy09/article/details/8161617】
Kinect開發學習筆記之(七)骨骼資料的提取
我的Kinect開發平臺是:
Win7x86 + VS2010 + Kinect for Windows SDK v1.6 + OpenCV2.3.0
開發環境的搭建見上一文:
http://blog.csdn.net/zouxy09/article/details/8146055
本學習筆記以下面的方式組織:程式設計前期分析、程式碼與註釋和重要程式碼解析三部分。
要實現目標:通過微軟的SDK提取骨骼資料並用OpenCV顯示
一、程式設計前期分析
Kinect產生的深度資料作用有限,要利用Kinect建立真正意義上互動,還需要除了深度資料之外的其他資料。這就是骨骼追蹤技術的初衷,也是Kinect最神奇,最有作為的地方。骨骼追蹤技術通過處理深度資料來建立人體各個關節的座標,骨骼追蹤能夠確定人體的各個部分,如那部分是手,頭部,以及身體,還能確定他們所在的位置。
1.1、骨架空間
先看看啥叫骨架?應該地球人都知道吧。呵呵。在Kinect裡面,是通過20個關節點來表示一個骨架的,具體由下圖可以看到。當你走進Kinect的視野範圍的時候,Kinect就可以把你的20個關節點的位置找到(當然你得站著),位置通過(x, y, z)座標來表示。這樣,你在Kinect前面做很多複雜的動作的時候,因為人的動作和這些關節點的位置的變化關係還是很大的,那麼電腦拿到這些資料後,對於理解你做什麼動作就很有幫助了。
玩家的各關節點位置用(x, y, z)座標表示。與深度影象空間座標不同的是,這些座標單位是米。座標軸x,y, z是深度感應器實體的空間x, y, z座標軸。這個座標系是右手螺旋的,Kinect感應器處於原點上,z座標軸則與Kinect感應的朝向一致。y軸正半軸向上延伸,x軸正半軸(從Kinect感應器的視角來看)向左延伸,如下圖所示。為了方便討論,我們稱這些座標的表述為骨架空間(座標)。
Kinect放置的位置會影響生成的影象。例如,Kinect可能被放置在非水平的表面上或者有可能在垂直方向上進行了旋轉調整來優化視野範圍。在這種情況下,y軸就往往不是相對地面垂直的,或者不與重力方向平行。最終得到的影象中,儘管人筆直地站立,在影象中也會顯示出事傾斜的。
1.2、骨骼跟蹤
Kinect最多可以跟蹤兩個骨骼,可以最多檢測六個人。站立模式可以跟蹤20個關節點,坐著的模式的話,可以跟蹤10個關節點。
NUI骨骼跟蹤分主動和被動兩種模式,提供最多兩副完整的骨骼跟蹤資料。主動模式下需要呼叫相關幀讀取函式獲得使用者骨骼資料,而被動模式下還支援額外最多四人的骨骼跟蹤,但是在該模式下僅包含了使用者的位置資訊,不包括詳細的骨骼資料。也就是說,假如Kinect面前站著六個人,Kinect能告訴你這六個人具體站在什麼位置,但只能提供其中兩個人的關節點的資料(這兩個人屬於主動模式),也就是他們的手啊,頭啊等等的位置都能告訴你,而其他的人,Kinect只能提供位置資訊,也就是你站在哪,Kinect告訴你,但是你的手啊,頭啊等具體在什麼位置,它就沒法告訴你了(這四個人屬於被動模式)。
對於所有獲取的骨骼資料,其至少包含以下資訊:
1)、相關骨骼的跟蹤狀態,被動模式時僅包括位置資料(使用者所在位置),主動模式包括完整的骨骼資料(使用者20個關節點的空間位置資訊)。
2)、唯一的骨骼跟蹤ID,用於分配給視野中的每個使用者(和之前說的深度資料中的ID是一個東西,用以區分現在這個骨骼資料是哪個使用者的)。
3)、使用者質心位置,該值僅在被動模式下可用(就是標示使用者所在位置的)。
1.3、關於程式設計處理過程
在上篇文章中,我們討論瞭如何獲取畫素點的深度值以及如何根據深度值產生影像。
彩色影象資料,深度資料分別來自ColorImageSteam和DepthImageStream,同樣地,骨骼資料來自SkeletonStream。要使用骨架資料,應用程式必須在初始化NUI的時候宣告,並且要啟用骨架追蹤。訪問骨骼資料和訪問彩色影象資料、深度資料一樣,也有事件模式和查詢模式兩種方式。在本例中我們採用基於事件的方式,因為這種方式簡單,程式碼量少,並且是一種很普通基本的方法。當SkeletonStream中有新的骨骼資料產生時就會觸發該事件。
我們初始化並開啟骨骼跟蹤後,就可以從SkeletonStream中拿骨骼資料了。SkeletonStream產生的每一幀資料skeletonFrame都是一個骨骼物件集合。包含了一個骨架資料結構的陣列,其中每一個元素代表著一個被骨架追蹤系統所識別的一個骨架資訊。每一個骨架資訊包含有描述骨骼位置以及骨骼關節的資料。每一個關節有一個唯一標示符如頭(head)、肩(shoulder)、肘(dlbow)等資訊和對應的三維座標資料。
Kinect能夠追蹤到的骨骼數量是一個常量。這使得我們在整個應用程式中能夠一次性的為陣列分配記憶體。迴圈遍歷skeletonFrame,每一次處理一個骨骼。那麼跟蹤的骨骼也有跟得好與不好之分吧,你的姿勢、是否有阻擋等等情況,都會使得跟蹤不那邊好。所以在處理之前需要判斷一下是否是一個追蹤好的骨骼,可以使用Skeleton物件的TrackingState屬性來判斷,只有骨骼追蹤引擎追蹤到的骨骼我們才進行處理,忽略哪些不是遊戲者的骨骼資訊即過濾掉那些TrackingState不等於SkeletonTrackingState.Tracked的骨骼資料。
Kinect能夠探測到6個遊戲者,但是同時只能夠追蹤到2個遊戲者的骨骼關節位置資訊。處理骨骼資料相對簡單,首先,我們根據Kinect追蹤到的遊戲者的編號,用不同的顏色把遊戲者的骨架畫出來。
涉及的東西還是比較多的,但是程式碼思路還是比較清晰的,我們可以看程式碼,再看看第三部分的解析就會比較好理解了。
二、程式碼與註釋
三、程式碼解析
像上面說的過程一樣,我們先初始化,告訴Kinect我需要骨骼資料,然後建立一個骨骼事件,在開啟骨骼跟蹤功能。當骨架追蹤啟用後,執行時,庫將處理一幅影象和深度資料來傳遞包含骨架資料的幀。你可以在處理過程中的任何時候開啟或關閉骨架追蹤。這樣有骨骼資料後,系統就會通知我們了。然後我們通過呼叫NuiSkeletonGetNextFrame拿到骨骼資料。然後就開始我們的處理了:
骨骼幀資料儲存在NUI_SKELETON_FRAME結構體中,我們首先來分析下這個最重要的結構體:
typedef struct _NUI_SKELETON_FRAME {
LARGE_INTEGER liTimeStamp;
DWORD dwFrameNumber;
DWORD dwFlags; //沒用到
Vector4 vFloorClipPlane;
Vector4 vNormalToGravity;
NUI_SKELETON_DATA SkeletonData[NUI_SKELETON_COUNT];
} NUI_SKELETON_FRAME;
時間標記欄位:
SkeletonFrame的dwFrameNumber和liTimestamp欄位表示當前記錄中的幀序列資訊。FrameNumber是深度資料幀中的用來產生骨骼資料幀的幀編號。幀編號通常是不連續的,但是之後的幀編號一定比之前的要大。骨骼追蹤引擎在追蹤過程中可能會忽略某一幀深度資料,這跟應用程式的效能和每秒產生的幀數有關。例如,在基於事件獲取骨骼幀資訊中,如果事件中處理幀資料的時間過長就會導致這一幀資料還沒有處理完就產生了新的資料,那麼這些新的資料就有可能被忽略了。如果採用查詢模型獲取幀資料,那麼取決於應用程式設定的骨骼引擎產生資料的頻率,即取決於深度影像資料產生骨骼資料的頻率。
Timestap欄位記錄自Kinect感測器初始化(呼叫NuiInitialize函式)以來經過的累計毫秒時間。不用擔心FrameNumber或者Timestamp欄位會超出上限。FrameNumber是一個32位的整型,Timestamp是64位整型。如果應用程式以每秒30幀的速度產生資料,應用程式需要執行2.25年才會達到FrameNumber的限,此時Timestamp離上限還很遠。另外在Kinect感測器每一次初始化時,這兩個欄位都會初始化為0。可以認為FrameNumber和Timestamp這兩個值是唯一的。
這兩個欄位在分析處理幀序列資料時很重要,比如進行關節點值的平滑,手勢識別操作等。在多數情況下,我們通常會處理幀時間序列資料,這兩個欄位就顯得很有用。目前SDK中並沒有包含手勢識別引擎。在未來SDK中加入手勢引擎之前,我們需要自己編寫演算法來對幀時間序列進行處理來識別手勢,這樣就會大量依賴這兩個欄位。
骨骼資料段:
最重要的要數這個成員了。首先,Kinect可以檢測到6個骨骼,在NuiSensor.h中有巨集定義:#define NUI_SKELETON_COUNT ( 6 )。所以SkeletonData[NUI_SKELETON_COUNT]定義了六個骨骼的資料。骨骼資料是通過一個NUI_SKELETON_DATA型別的結構體儲存的:
typedef struct _NUI_SKELETON_DATA {
NUI_SKELETON_TRACKING_STATE eTrackingState;
DWORD dwTrackingID;
DWORD dwEnrollmentIndex;
DWORDdwUserIndex;
Vector4 Position;
Vector4 SkeletonPositions[20];
NUI_SKELETON_POSITION_TRACKING_STATE eSkeletonPositionTrackingState[20];
DWORD dwQualityFlags;
} NUI_SKELETON_DATA;
其中:
eTrackingState:
eTrackingState欄位表示當前的骨骼資料的狀態,是一個列舉型別。
typedef enum _NUI_SKELETON_TRACKING_STATE
{
NUI_SKELETON_NOT_TRACKED = 0,
NUI_SKELETON_POSITION_ONLY,
NUI_SKELETON_TRACKED
} NUI_SKELETON_TRACKING_STATE;
下表展示了SkeletonTrackingState列舉的可能值的含義:
NUI_SKELETON_NOT_TRACKED 表示骨架沒有被跟蹤到,這個狀態下,骨骼資料的Position欄位和相關的關節點資料中的每一個位置點值都是0 。
NUI_SKELETON_POSITION_ONLY 檢測到了骨骼物件,但是跟蹤沒有啟用,也就是說骨骼資料的Position欄位有值,但是相關的關節點資料中的每一個位置點值都是0(對應被動模式,被動模式只提供骨骼的位置,不提供關節點的位置)。
NUI_SKELETON_TRACKED 所有骨骼點的位置都被跟蹤,骨骼資料的Position欄位和相關的關節點資料中的每一個位置點值都非零(對應主動模式,骨骼位置和關節點位置都提供)。
dwTrackingID:
骨骼追蹤引擎對於每一個追蹤到的遊戲者的骨骼資訊都有一個唯一編號。這個值是整型,他會隨著新的追蹤到的遊戲者的產生新增增長。另外,這個編號的產生是不確定的。如果骨骼追蹤引擎失去了對遊戲者的追蹤,比如說遊戲者離開了Kinect的視野,那麼這個對應的唯一編號就會過期。當Kinect追蹤到了一個新的遊戲者,他會為其分配一個新的唯一編號。編號值為0表示這個骨骼資訊不是遊戲者的。
Position:
Position是一個Vector4型別的欄位,代表所有骨骼的中間點。身體的中間點和脊柱關節的位置相當。該欄位提供了一個最快且最簡單的所有視野範圍內的遊戲者位置的資訊,而不管其是否在追蹤狀態中。在一些應用中,如果不用關心骨骼中具體的關節點的位置資訊,那麼該欄位對於確定遊戲者的位置狀態已經足夠。該欄位對於手動選擇要追蹤的遊戲者也是一個參考。例如,應用程式可能需要追蹤距離Kinect最近的且處於追蹤狀態的遊戲者,那麼該欄位就可以用來過濾掉其他的遊戲者。
Vector4型別是一個空間座標的型別:
Vector4typedef struct _Vector4 {
FLOAT x; //X coordinate
FLOAT y; //Y coordinate
FLOAT z; //Z coordinate
FLOAT w; //W coordinate
} Vector4;
SkeletonPositions[20]:
這個陣列記錄的是主動模式下骨骼的20個關節點對應的空間位置資訊。每一個關節點都有型別為Vector4的位置屬性,他通過X,Y,Z三個值來描述關節點的位置。X,Y值是相對於骨骼平面空間的位置,他和深度影像,彩色影像的空間座標系不一樣。KinectSnesor物件有一系列的座標轉換方法,可以將骨骼座標點轉換到對應的深度資料影像中去。
SkeletonPositionTrackingState[20]:
上面說到骨骼有跟蹤的好與不好,那麼關節點也是跟蹤和分析的,那也有好與不好之分吧。而SkeletonPositionTrackingState屬性就是標示對應的每一個關節點的跟蹤狀態的:
typedef enum _NUI_SKELETON_POSITION_TRACKING_STATE
{
NUI_SKELETON_POSITION_NOT_TRACKED = 0,
NUI_SKELETON_POSITION_INFERRED,
NUI_SKELETON_POSITION_TRACKED
} NUI_SKELETON_POSITION_TRACKING_STATE;
各個狀態表示的含義:
NUI_SKELETON_POSITION_NOT_TRACKED 關節點位置沒有跟蹤到,而且也不能通過一些資訊推斷出來,所以關節點的Position值都為0 。
NUI_SKELETON_POSITION_INFERRED 骨骼的關節點位置可以由上一幀資料、已經跟蹤到的其他點位置和骨架的幾何假設這三個資訊推測出來。
NUI_SKELETON_POSITION_TRACKED 關節點被成功跟蹤,它的位置是基於當前幀的計算得到的。
NUI_SKELETON_POSITION_INDEX:
另外,SkeletonPositions[20]這個陣列是儲存20個關節點的位置的,那麼哪個陣列元素對應哪個關節點呢?實際上,這個陣列的儲存是有順序的,然後,我們可以通過下面的列舉值做為這個陣列的下標來訪問相應的關節點位置資訊:
typedef enum _NUI_SKELETON_POSITION_INDEX
{
NUI_SKELETON_POSITION_HIP_CENTER = 0,
NUI_SKELETON_POSITION_SPINE,
NUI_SKELETON_POSITION_SHOULDER_CENTER,
NUI_SKELETON_POSITION_HEAD,
NUI_SKELETON_POSITION_SHOULDER_LEFT,
NUI_SKELETON_POSITION_ELBOW_LEFT,
NUI_SKELETON_POSITION_WRIST_LEFT,
NUI_SKELETON_POSITION_HAND_LEFT,
NUI_SKELETON_POSITION_SHOULDER_RIGHT,
NUI_SKELETON_POSITION_ELBOW_RIGHT,
NUI_SKELETON_POSITION_WRIST_RIGHT,
NUI_SKELETON_POSITION_HAND_RIGHT,
NUI_SKELETON_POSITION_HIP_LEFT,
NUI_SKELETON_POSITION_KNEE_LEFT,
NUI_SKELETON_POSITION_ANKLE_LEFT,
NUI_SKELETON_POSITION_FOOT_LEFT,
NUI_SKELETON_POSITION_HIP_RIGHT,
NUI_SKELETON_POSITION_KNEE_RIGHT,
NUI_SKELETON_POSITION_ANKLE_RIGHT,
NUI_SKELETON_POSITION_FOOT_RIGHT,
NUI_SKELETON_POSITION_COUNT
} NUI_SKELETON_POSITION_INDEX;
好了,感覺把這些說完,程式碼裡面的東西就很容易讀懂了。所以也沒必要贅述了。但是還需要提到的幾點是:
平滑化:
NuiTransformSmooth(&skeletonFrame,NULL);
在骨骼跟蹤過程中,有些情況會導致骨骼運動呈現出跳躍式的變化。例如遊戲者的動作不夠連貫,Kinect硬體的效能等等。骨骼關節點的相對位置可能在幀與幀之間變動很大,這回對應用程式產生一些負面的影響。例如會影響使用者體驗和給控制造成意外等。
而這個函式就是解決這個問題的,它對骨骼資料進行平滑,通過將骨骼關節點的座標標準化來減少幀與幀之間的關節點位置差異。
HRESULT NuiTransformSmooth(
NUI_SKELETON_FRAME *pSkeletonFrame,
const NUI_TRANSFORM_SMOOTH_PARAMETERS *pSmoothingParams
)
這個函式可以傳入一個NUI_TRANSFORM_SMOOTH_PARAMETERS型別的引數:
typedef struct_NUI_TRANSFORM_SMOOTH_PARAMETERS {
FLOAT fSmoothing;
FLOAT fCorrection;
FLOAT fPrediction;
FLOAT fJitterRadius;
FLOAT fMaxDeviationRadius;
} NUI_TRANSFORM_SMOOTH_PARAMETERS;
NUI_TRANSFORM_SMOOTH_PARAMETERS這個結構定義了一些屬性:
fSmoothing:平滑值(Smoothing)屬性,設定處理骨骼資料幀時的平滑量,接受一個0-1的浮點值,值越大,平滑的越多。0表示不進行平滑
fCorrection:修正值(Correction)屬性,接受一個從0-1的浮點型。值越小,修正越多。
fJitterRadius:抖動半徑(JitterRadius)屬性,設定修正的半徑,如果關節點“抖動”超過了設定的這個半徑,將會被糾正到這個半徑之內。該屬性為浮點型,單位為米。
fMaxDeviationRadius:最大偏離半徑(MaxDeviationRadius)屬性,用來和抖動半徑一起來設定抖動半徑的最大邊界。任何超過這一半徑的點都不會認為是抖動產生的,而被認定為是一個新的點。該屬性為浮點型,單位為米。
fPrediction:預測幀大小(Prediction)屬性,返回用來進行平滑需要的骨骼幀的數目。
空間座標轉換:
NuiTransformSkeletonToDepthImage(pSkel->SkeletonPositions[i], &fx, &fy );
由於深度影象資料和彩色影象資料來自於不同的攝像頭,而這兩個攝像頭所在的位置不一樣,而且視場角等也不也一樣,所以產生的影象也會有差別(就好像你兩個眼睛看到的東西都不一樣,這樣大腦才能通過他們合成三維場景理解),也就是說這兩幅影象上的畫素點並不嚴格一一對應。例如深度影象中,你在影象的位置可能是(x1,y1),但在彩色影象中,你在影象的位置是(x2,y2),而兩個座標一般都是不相等的。另外,骨骼資料的座標又和深度影象和彩色影象的不一樣。所以就存在了彩色座標空間、深度座標空間、骨骼座標空間和你的UI座標空間四個不同的座標空間了。那麼他們各個空間之前就需要互動,例如我在骨骼空間找到這個人在(x1,y1)座標上,那麼對應的深度影象,這個人在什麼座標呢?另外,我們需要把骨骼關節點的位置等畫在我們的UI視窗上,那麼它們的對應關係又是什麼呢?
微軟SDK提供了一系列方法來幫助我們進行這幾個空間座標系的轉換。例如:
voidNuiTransformSkeletonToDepthImage(
Vector4 vPoint, //骨骼空間中某點座標
FLOAT *pfDepthX, //對應的深度空間中的座標
FLOAT *pfDepthY
)//將骨骼座標轉換到深度影象座標上去。
HRESULTNuiImageGetColorPixelCoordinatesFromDepthPixel(
NUI_IMAGE_RESOLUTION eColorResolution,
const NUI_IMAGE_VIEW_AREA *pcViewArea,
LONG lDepthX,
LONG lDepthY,
USHORT usDepthValue,
LONG *plColorX,
LONG *plColorY
)//獲取在深度圖中具體座標位置的畫素在相應彩色空間中的畫素的座標。
Vector4NuiTransformDepthImageToSkeleton(
LONG lDepthX,
LONG lDepthY,
USHORT usDepthValue
)//傳入深度影象座標,返回骨骼空間座標
至此,目標完成,效果如下:
相關文章
- Redis學習筆記(七) 資料庫Redis筆記資料庫
- 大資料之 Hadoop學習筆記大資料Hadoop筆記
- Go語言學習筆記(七)之方法Go筆記
- swoft 學習筆記之資料庫操作筆記資料庫
- hive學習筆記之七:內建函式Hive筆記函式
- laravel學習筆記之開發環境搭建Laravel筆記開發環境
- Redis阻塞(學習筆記七)Redis筆記
- HexMap學習筆記(七)——道路筆記
- Python 3 學習筆記之——資料型別Python筆記資料型別
- Python學習筆記|Python之pycache資料夾Python筆記
- 資料庫學習筆記之查詢表資料庫筆記
- 工作學習筆記(七)Java的介面筆記Java
- Unity學習筆記--資料持久化之PlayerPrefs的使用Unity筆記持久化
- 飛機的 PHP 學習筆記之資料庫篇PHP筆記資料庫
- 《資料結構與演算法之美》學習筆記之開篇資料結構演算法筆記
- Pytorch學習(七)---- 儲存提取PyTorch
- 《筆記》之學習高併發筆記
- Android 開發學習筆記Android筆記
- 資料庫學習筆記資料庫筆記
- 資料結構和演算法學習筆記七:圖的搜尋資料結構演算法筆記
- React學習筆記之雙向資料繫結React筆記
- YOLOv3學習筆記之資料處理YOLO筆記
- webpack學習筆記七:配置babelWeb筆記Babel
- Vue.js 學習筆記之七:使用現有元件Vue.js筆記元件
- 飛機的 PHP 學習筆記七:WebPHP筆記Web
- vue學習筆記(七)---- vue中的路由Vue筆記路由
- (七)Flutter學習之開發環境搭建Flutter開發環境
- Java學習筆記——陣列練習(七)Java筆記陣列
- Kubernetes學習筆記(七):訪問Pod後設資料與Kubernetes API筆記API
- springboot 開發學習筆記1Spring Boot筆記
- php 學習筆記之搭建開發環境(mac版)PHP筆記開發環境Mac
- HarmonyOS NEXT開發之ArkTS自定義元件學習筆記元件筆記
- PHP 資料加密 (學習筆記)PHP加密筆記
- 1029學習筆記 資料庫筆記資料庫
- 資料結構學習筆記資料結構筆記
- python學習筆記:資料庫Python筆記資料庫
- MySQL資料庫學習筆記MySql資料庫筆記
- Laravel學習筆記七-建立部落格Laravel筆記
- ES6學習筆記(七)【class】筆記