Kinect產生的景深數據作用有限,要利用Kinect創建真正意義上交互,有趣和難忘的應用,還需要除了深度數據之外的其他數據。這就是骨骼追蹤技術的初衷,骨骼追蹤技術通過處理景深數據來建立人體各個關節的坐標,骨骼追蹤能夠確定人體的各個部分,如那部分是手,頭部,以及身體。骨骼追蹤產生X,Y,Z數據來確定這些骨骼點。在上文中,我們討論了景深圖像處理的一些技術。骨骼追蹤系統采用的景深圖像處理技術使用更復雜的算法如矩陣變換,機器學習及其他方式來確定骨骼點的坐標。
本文首先用一個例子展示骨骼追蹤系統涉及的主要對象,然後在此基礎上詳細討論骨骼追蹤中所涉及的對象模型。
1. 獲取骨骼數據
本節將會創建一個應用來將獲取到的骨骼數據繪制到UI界面上來。在開始編碼前,首先來看看一些基本的對象以及如何從這些對象中如何獲取骨骼數據。在進行數據處理之前了解數據的格式也很有必要。這個例子很簡單明了,只需要骨骼數據對象然後將獲取到的數據繪制出來。
彩色影像數據,景深數據分別來自ColorImageSteam和DepthImageStream,同樣地,骨骼數據來自SkeletonStream。訪問骨骼數據和訪問彩色影像數據、景深數據一樣,也有事件模式和 “拉”模式兩種方式。在本例中我們采用基於事件的方式,因為這種方式簡單,代碼量少,並且是一種很普通基本的方法。KinectSensor對象有一個名為SkeletonFrameReady事件。當SkeletonStream中有新的骨骼數據產生時就會觸發該事件。通過AllFramesReady事件也可以獲取骨骼數據。在下一節中,我們將會詳細討論骨骼追蹤對象模型,現在我們只展示如何從SkeletonStream流中獲取骨骼數據。SkeletonStream產生的每一幀數據都是一個骨骼對象集合。每一個骨骼對象包含有描述骨骼位置以及骨骼關節的數據。每一個關節有一個唯一標示符如頭(head)、肩(shoulder)、肘(dlbow)等信息和3D向量數據。
現在來寫代碼。首先創建一個新的wpf工程文件,添加Microsoft.Kinect.dll。添加基本查找和初始化傳感器的代碼,這些代碼參考之前的文章。在開始啟動傳感器之前,初始化SkeletonStream數據流,並注冊KinectSensor對象的SkeletonFrameReady事件,這個例子沒有使用彩色攝像機和紅外攝像機產生的數據,所以不需要初始化這些數據流。UI界面采用默認的,將Grid的名稱改為LayoutRoot,之後就再Grid裡面繪制。代碼如下:
<Window x:Class="KinectSkeletonTracking.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid x:Name="LayoutRoot" Background="White"> </Grid> </Window>
後台邏輯代碼如下:
private KinectSensor kinectDevice; private readonly Brush[] skeletonBrushes;//繪圖筆刷 private Skeleton[] frameSkeletons; public MainWindow() { InitializeComponent(); skeletonBrushes = new Brush[] { Brushes.Black, Brushes.Crimson, Brushes.Indigo, Brushes.DodgerBlue, Brushes.Purple, Brushes.Pink }; KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged; this.KinectDevice = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected); } public KinectSensor KinectDevice { get { return this.kinectDevice; } set { if (this.kinectDevice != value) { //Uninitialize if (this.kinectDevice != null) { this.kinectDevice.Stop(); this.kinectDevice.SkeletonFrameReady -= KinectDevice_SkeletonFrameReady; this.kinectDevice.SkeletonStream.Disable(); this.frameSkeletons = null; } this.kinectDevice = value; //Initialize if (this.kinectDevice != null) { if (this.kinectDevice.Status == KinectStatus.Connected) { this.kinectDevice.SkeletonStream.Enable(); this.frameSkeletons = new Skeleton[this.kinectDevice.SkeletonStream.FrameSkeletonArrayLength]; this.kinectDevice.SkeletonFrameReady += KinectDevice_SkeletonFrameReady; this.kinectDevice.Start(); } } } } } private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e) { switch (e.Status) { case KinectStatus.Initializing: case KinectStatus.Connected: case KinectStatus.NotPowered: case KinectStatus.NotReady: case KinectStatus.DeviceNotGenuine: this.KinectDevice = e.Sensor; break; case KinectStatus.Disconnected: //TODO: Give the user feedback to plug-in a Kinect device. this.KinectDevice = null; break; default: //TODO: Show an error state break; } }
以上代碼中,值得注意的是frameSkeletons數組以及該數組如何在流初始化時進行內存分配的。Kinect能夠追蹤到的骨骼數量是一個常量。這使得我們在整個應用程序中能夠一次性的為數組分配內存。為了方便,Kinect SDK在SkeletonStream對象中定義了一個能夠追蹤到的骨骼個數常量FrameSkeletonArrayLength,使用這個常量可以方便的對數組進行初始化。代碼中也定義了一個筆刷數組,這些筆刷在繪制骨骼時對多個游戲者可以使用不同的顏色進行繪制。也可以將筆刷數組中的顏色設置為自己喜歡的顏色。
下面的代碼展示了SkeletonFrameReady事件的響應方法,每一次事件被激發時,通過調用事件參數的OpenSkeletonFrame方法就能夠獲取當前的骨骼數據幀。剩余的代碼遍歷骨骼數據幀的Skeleton數組frameSkeletons,在UI界面通過關節點將骨骼連接起來,用一條直線代表一根骨骼。UI界面簡單,將Grid元素作為根結點,並將其背景設置為白色。
private void KinectDevice_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { using (SkeletonFrame frame = e.OpenSkeletonFrame()) { if (frame != null) { Polyline figure; Brush userBrush; Skeleton skeleton; LayoutRoot.Children.Clear(); frame.CopySkeletonDataTo(this.frameSkeletons); for (int i = 0; i < this.frameSkeletons.Length; i++) { skeleton = this.frameSkeletons[i]; if (skeleton.TrackingState == SkeletonTrackingState.Tracked) { userBrush = this.skeletonBrushes[i % this.skeletonBrushes.Length]; //繪制頭和軀干 figure = CreateFigure(skeleton, userBrush, new[] { JointType.Head, JointType.ShoulderCenter, JointType.ShoulderLeft, JointType.Spine, JointType.ShoulderRight, JointType.ShoulderCenter, JointType.HipCenter }); LayoutRoot.Children.Add(figure); figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipLeft, JointType.HipRight }); LayoutRoot.Children.Add(figure); //繪制作腿 figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipCenter, JointType.HipLeft, JointType.KneeLeft, JointType.AnkleLeft, JointType.FootLeft }); LayoutRoot.Children.Add(figure); //繪制右腿 figure = CreateFigure(skeleton, userBrush, new[] { JointType.HipCenter, JointType.HipRight, JointType.KneeRight, JointType.AnkleRight, JointType.FootRight }); LayoutRoot.Children.Add(figure); //繪制左臂 figure = CreateFigure(skeleton, userBrush, new[] { JointType.ShoulderLeft, JointType.ElbowLeft, JointType.WristLeft, JointType.HandLeft }); LayoutRoot.Children.Add(figure); //繪制右臂 figure = CreateFigure(skeleton, userBrush, new[] { JointType.ShoulderRight, JointType.ElbowRight, JointType.WristRight, JointType.HandRight }); LayoutRoot.Children.Add(figure); } } } } }
循環遍歷frameSkeletons對象,每一次處理一個骨骼,在處理之前需要判斷是否是一個追蹤好的骨骼,可以使用Skeleton對象的TrackingState屬性來判斷,只有骨骼追蹤引擎追蹤到的骨骼我們才進行繪制,忽略哪些不是游戲者的骨骼信息即過濾掉那些TrackingState不等於SkeletonTrackingState.Tracked的骨骼數據。Kinect能夠探測到6個游戲者,但是同時只能夠追蹤到2個游戲者的骨骼關節位置信息。在後面我們將會詳細討論TrackingState這一屬性。
處理骨骼數據相對簡單,首先,我們根Kinect追蹤到的游戲者的編號,選擇一種顏色筆刷。然後利用這只筆刷繪制曲線。CreateFigure方法為每一根骨骼繪制一條直線。GetJointPoint方法在繪制骨骼曲線中很關鍵。該方法以關節點的三維坐標作為參數,然後調用KinectSensor對象的MapSkeletonPointToDepth方法將骨骼坐標轉換到深度影像坐標上去。後面我們將會討論為什麼需要這樣轉換以及如何定義坐標系統。現在我們只需要知道的是,骨骼坐標系和深度坐標及彩色影像坐標系不一樣,甚至和UI界面上的坐標系不一樣。在開發Kinect應用程序中,從一個坐標系轉換到另外一個坐標系這樣的操作非常常見,GetJointPoint方法的目的就是將骨骼關節點的三維坐標轉換到UI繪圖坐標系統,返回該骨骼關節點在UI上的位置。下面的代碼展示了CreateFigure和GetJointPoint這兩個方法。
private Polyline CreateFigure(Skeleton skeleton, Brush brush, JointType[] joints) { Polyline figure = new Polyline(); figure.StrokeThickness = 8; figure.Stroke = brush; for (int i = 0; i < joints.Length; i++) { figure.Points.Add(GetJointPoint(skeleton.Joints[joints[i]])); } return figure; } private Point GetJointPoint(Joint joint) { DepthImagePoint point = this.KinectDevice.MapSkeletonPointToDepth(joint.Position, this.KinectDevice.DepthStream.Format); point.X *= (int)this.LayoutRoot.ActualWidth / KinectDevice.DepthStream.FrameWidth; point.Y *= (int)this.LayoutRoot.ActualHeight / KinectDevice.DepthStream.FrameHeight; return new Point(point.X, point.Y); }
值得注意的是,骨骼關節點的三維坐標中我們捨棄了Z值,只用了X,Y值。Kinect好不容易為我們提供了每一個節點的深度數據(Z值)而我們卻沒有使用,這看起來顯得很浪費。其實不是這樣的,我們使用了節點的Z值,只是沒有直接使用,沒有在UI界面上展現出來而已。在坐標空間轉換中是需要深度數據的。可以試試在GetJointPoint方法中,將joint的Position中的Z值改為0,然後再調用MapSkeletonPointToDepth方法,你會發現返回的對象中x和y值均為0,可以試試,將圖像以Z值進行等比縮放,可以發現圖像的大小是和Z值(深度)成反的。也就是說,深度值越小,圖像越大,即人物離Kinect越近,骨骼數據越大。
運行程序,會得到如下骨骼圖像,這個是手握鍵盤准備截圖的姿勢。一開始可能需要調整一些Form窗體的大小。程序會為每一個游戲者以一種顏色繪制骨骼圖像,可以試著在Kinect前面移動,可以看到骨骼圖像的變化,也可以走進然後走出圖像以觀察顏色的變化。仔細觀察有時候可以看到繪圖出現了一些奇怪的圖案,在討論完骨骼追蹤相關的API之後,就會明白這些現象出現的原因了。
2. 骨骼對象模型
Kinect SDK中骨骼追蹤有一些和其他對象不一樣的對象結構和枚舉。在SDK中骨骼追蹤相關的內容幾乎占據了三分之一的內容,可見Kinect中骨骼追蹤技術的重要性。下圖展示了骨骼追蹤系統中涉及到的一些主要的對象模型。有四個最主要的對象,他們是SkeletonStream,SkeletonFrame,Skeleton和Joint。下面將詳細介紹這四個對象。
2.1 SkeletonStream對象
SkeletonStream對象產生SkeletonFrame。從SkeletonStream獲取骨骼幀數據和從ColorStream及DepthStream中獲取數據類似。可以注冊SkeletonFrameReady事件或者AllFramesReady事件通過事件模型來獲取數據,或者是使用OpenNextFrame方法通過“拉”模型來獲取數據。不能對同一個SkeletonStream同時使用這兩種模式。如果注冊了SkeletonFrameReady事件然後又調用OpenNextFrame方法將會返回一個InvalidOperationException異常。
SkeletonStream的啟動和關閉
除非啟動了SkeletonStream對象,否則,不會產生任何數據,默認情況下,SkeletonStream對象是關閉的。要使SkeletonStream產生數據,必須調用對象的Enabled方法。相反,調用Disable方法能夠使SkeletonStream對象暫停產生數據。SkeletonStream有一個IsEnabled方法來描述當前SkeletonStream對象的狀態。只有SkeletonStream對象啟動了,KinectSensor對象的SkeletonFrameReady事件才能被激活。如果要使用“拉”模式來獲取數據SkeletonStream也必須啟動後才能調用OpenNextFrame方法。否則也會拋出InvalidOperationException異常。
一般地在應用程序的聲明周期中,一旦啟動了SkeletonStream對象,一般會保持啟動狀態。但是在有些情況下,我們希望關閉SkeletonStream對象。比如在應用程序中使用多個Kinect傳感器時。只有一個Kinect傳感器能夠產生骨骼數據,這也意味著,即使使用多個Kinect傳感器,同時也只能追蹤到兩個游戲者的骨骼數據信息。在應用程序執行的過程中,有可能會關閉某一個Kinect傳感器的SkeletonStream對象而開啟另一個Kinect傳感器的SkeletonStream對象。
另一個有可能關閉骨骼數據產生的原因是出於性能方面的考慮,骨骼數據處理是很耗費計算性能的操作。打開骨骼追蹤是可以觀察的到CPU的占用率明顯增加。當不需要骨骼數據時,關閉骨骼追蹤很有必要。例如,在有些游戲場景中可能在展現一些動畫效果或者播放視頻,在這個動畫效果或者視頻播放時,停止骨骼追蹤可能可以使得游戲更加流暢。
當然關閉SkeletonStream也有一些副作用。當SkeletonStream的狀態發生改變時,所有的數據產生都會停止和從新開始。SkeletonStream的狀態改變會使傳感器重新初始化,將TimeStamp和FrameNumber重置為0。在傳感器重新初始化時也有幾毫秒的延遲。
平滑化
在前面的例子中,會注意到,骨骼運動會呈現出跳躍式的變化。有幾個原因會導致出現這一問題,可能是應用程序的性能,游戲者的動作不夠連貫,也有可能是Kinect硬件的性能問題。骨骼關節點的相對位置可能在幀與幀之間變動很大,這回對應用程序產生一些負面的影像。除了會影像用戶體驗和不愉快意外,也可能會導致用戶的形象或者手的顫動抽搐而使用戶感到迷惑。
SkeletonStream對象有一種方法能夠解決這個問題。他通過將骨骼關節點的坐標標准化來減少幀與幀之間的關節點位置差異。當初始化SkeletonStream對象調用重載的Enable方法時可以傳入一個TransformSmoothParameters參數。SkeletonStream對象有兩個與平滑有關只讀屬性:IsSmoothingEnabled和SmoothParameters。當調用Enable方法傳入了TransformSmoothParameters是IsSmoothingEnabled返回true而當使用默認的不帶參數的Enable方法初始化時,IsSmoothingEnabled對象返回false。SmoothParameters屬性用來存儲定義平滑參數。TransformSmoothParameters這個結構定義了一些屬性:
修正值(Correction)屬性,接受一個從0-1的浮點型。值越小,修正越多。
抖動半徑(JitterRadius)屬性,設置修正的半徑,如果關節點“抖動”超過了設置的這個半徑,將會被糾正到這個半徑之內。該屬性為浮點型,單位為米。
最大偏離半徑(MaxDeviationRadius)屬性,用來和抖動半徑一起來設置抖動半徑的最大邊界。任何超過這一半徑的點都不會認為是抖動產生的,而被認定為是一個新的點。該屬性為浮點型,單位為米。
預測幀大小(Prediction)屬性,返回用來進行平滑需要的骨骼幀的數目。
平滑值(Smoothing)屬性,設置處理骨骼數據幀時的平滑量,接受一個0-1的浮點值,值越大,平滑的越多。0表示不進行平滑。
對骨骼關節點進行平滑處理會產生性能開銷。平滑處理的越多,性能消耗越大。設置平滑參數沒有經驗可以遵循。需要不斷的測試和調試已達到最好的性能和效果。在程序運行的不同階段,可能需要設置不同的平滑參數。
Note:SDK使用霍爾特指數平滑(Holt Double Exponential Smoothing)來對減少關節點的抖動。指數平滑數據處理與時間有關。骨骼數據是時間序列數據,因為骨骼引擎會以某一時間間隔不斷產生一幀一幀的骨骼數據。平滑處理使用統計方法進行滑動平均,這樣能夠減少時間序列數據中的噪聲和極值。類似的處理方法最開始被用於金融市場和經濟數據的預測。
骨骼追蹤對象選擇
默認情況下,骨骼追蹤引擎會對視野內的所有活動的游戲者進行追蹤。但只會選擇兩個可能的游戲者產生骨骼數據,大多數情況下,這個選擇過程不確定。如果要自己選擇追蹤對象,需要使用AppChoosesSkeletons屬性和ChooseSkeletons方法。 默認情況下AppChoosesSkeleton屬性為false,骨骼追蹤引擎追蹤所有可能的最多兩個游戲者。要手動選擇追蹤者,需要將AppChoosesSkeleton設置為true,並調用ChooseSkeletons方法,傳入TrackingIDs已表明需要追蹤那個對象。ChooseSkeletons方法接受一個,兩個或者0個TrackingIDs。當ChooseSkeletons方法傳入0個參數時,引擎停止追蹤骨骼信息。有一些需要注意的地方:
如果調用ChooseSkeletons方法時AppChoosesSkeletons的屬性為false,就會引發InvalidOperationExcepthion的異常。
如果在SkeletonStream開啟前,經AppChoosesSkeletons設置為true,只有手動調用ChooseSkeleton方法後才會開始骨骼追蹤。
在AppChoosesSkeletons設置為 true之前,骨骼引擎自動選擇追蹤的游戲者,並且繼續保持這些該游戲者的追蹤,直到用戶手動指定需要追蹤的游戲者。如果自動選擇追蹤的游戲者離開場景,骨骼引擎不會自動更換追蹤者。
將AppChoosesSkeletons沖新設置為false後,骨骼引擎會繼續對之前手動設置的游戲者進行追蹤,直到這些游戲者離開視野。當游戲這離開視野時骨骼引擎才會選擇其他的可能的游戲者進行追蹤。
2.2 SkeletonFrame
SkeletonStream產生SkeletonFrame對象。可以使用事件模型從事件參數中調用OpenSkeletonFrame方法來獲取SkeletonFrame對象,或者采用”拉”模型調用SkeletonStream的OpenNextFrame來獲取SkeletonFrame對象。SkeletonFrame對象會存儲骨骼數據一段時間。同以通過調用SkeletonFrame對象的CopySkeletonDataTo方法將其保存的數據拷貝到骨骼對象數組中。SkeletonFrame對象有一個SkeletonArrayLength的屬性,這個屬性表示追蹤到的骨骼信息的個數。
時間標記字段
SkeletonFrame的FrameNumber和Timestamp字段表示當前記錄中的幀序列信息。FrameNumber是景深數據幀中的用來產生骨骼數據幀的幀編號。幀編號通常是不連續的,但是之後的幀編號一定比之前的要大。骨骼追蹤引擎在追蹤過程中可能會忽略某一幀深度數據,這跟應用程序的性能和每秒產生的幀數有關。例如,在基於事件獲取骨骼幀信息中,如果事件中處理幀數據的時間過長就會導致這一幀數據還沒有處理完就產生了新的數據,那麼這些新的數據就有可能被忽略了。如果采用“拉”模型獲取幀數據,那麼取決於應用程序設置的骨骼引擎產生數據的頻率,即取決於深度影像數據產生骨骼數據的頻率。
Timestap字段記錄字Kinect傳感器初始化以來經過的累計毫秒時間。不用擔心FrameNumber或者Timestamp字段會超出上限。FrameNumber是一個32位的整型,Timestamp是64位整型。如果應用程序以每秒30幀的速度產生數據,應用程序需要運行2.25年才會達到FrameNumber的限,此時Timestamp離上限還很遠。另外在Kinect傳感器每一次初始化時,這兩個字段都會初始化為0。可以認為FrameNumber和Timestamp這兩個值是唯一的。
這兩個字段在分析處理幀序列數據時很重要,比如進行關節點值的平滑,手勢識別操作等。在多數情況下,我們通常會處理幀時間序列數據,這兩個字段就顯得很有用。目前SDK中並沒有包含手勢識別引擎。在未來SDK中加入手勢引擎之前,我們需要自己編寫算法來對幀時間序列進行處理來識別手勢,這樣就會大量依賴這兩個字段。
幀描述信息
FloorClipPlane字段是一個有四個元素的元組Tuple<int,int,int,int>,每一個都是Ax+By+Cz+D=0地面平面(floor plane)表達式裡面的系數項。元組中第一個元素表示A,即x前面的系數,一次類推,最後一個表示常數項,通常為負數,是Kinect距離地面高度。在可能的情況下SDK會利用圖像處理技術來確定這些系數。但是有時候這些系數不肯能能夠確定下來,可能需要預估。當地面不能確定時FloorClipPlane中的所有元素均為0.
2.3 Skeleton
Skeleton類定義了一系列字段來描述骨骼信息,包括描述骨骼的位置以及骨骼中關節可能的位置信息。骨骼數據可以通過調用SkeletonFrame對象的CopySkeletonDataTo方法獲得Skeleton數組。CopySkeletonDataTo方法有一些不可預料的行為,可能會影響內存使用和其引用的骨骼數組對象。產生的每一個骨骼數組對象數組都是唯一的。以下面代碼為例:
Skeleton[] skeletonA = new Skeleton[frame.SkeletonArrayLength]; Skeleton[] skeletonB = new Skeleton[frame.SkeletonArrayLength]; frame.CopySkeletonDataTo(skeletonA); frame.CopySkeletonDataTo(skeletonB); Boolean resultA = skeletonA[0] == skeletonB[0];//false Boolean resultB = skeletonA[0].TrackingId == skeletonB[0].TrackingId;//true
上面的代碼可以看出,使用CopySkeletonDataTo是深拷貝對象,會產生兩個不同的Skeleton數組對象。
TrackingID
骨骼追蹤引擎對於每一個追蹤到的游戲者的骨骼信息都有一個唯一編號。這個值是整型,他會隨著新的追蹤到的游戲者的產生添加增長。和之前幀序號一樣,這個值並不是連續增長的,但是能保證的是後面追蹤到的對象的編號要比之前的編號大。另外,這個編號的產生是不確定的。如果骨骼追蹤引擎失去了對游戲者的追蹤,比如說游戲者離開了Kinect的視野,那麼這個對應的唯一編號就會過期。當Kinect追蹤到了一個新的游戲者,他會為其分配一個新的唯一編號,編號值為0表示這個骨骼信息不是游戲者的,他在集合中僅僅是一個占位符。應用程序使用TrackingID來指定需要骨骼追蹤引擎追蹤那個游戲者。調用SkeletonStream對象的ChooseSkeleton能以初始化對指定游戲這的追蹤。
TrackingState
該字段表示當前的骨骼數據的狀態。下表展示了SkeletonTrackingState枚舉的可能值機器含義:
Position
Position一個SkeletonPoint類型的字段,代表所有骨骼的中間點。身體的中間點和脊柱關節的位置相當。改字段提供了一個最快且最簡單的所有視野范圍內的游戲者位置的信息,而不管其是否在追蹤狀態中。在一些應用中,如果不用關心骨骼中具體的關節點的位置信息,那麼該字段對於確定游戲者的位置狀態已經足夠。該字段對於手動選擇要追蹤的游戲者(SkeletonStream.ChooseSkeleton)也是一個參考。例如,應用程序可能需要追蹤距離Kinect最近的且處於追蹤狀態的游戲者,那麼該字段就可以用來過濾掉其他的游戲者。
ClippedEdges
ClippedEdges字段用來描述追蹤者的身體哪部分位於Kinect的視野范圍外。他大體上提供了一個追蹤這的位置信息。使用這一屬性可以通過程序調整Kinect攝像頭的俯仰角或者提示游戲者讓其返回到視野中來。該字段類型為FrameEdges,他是一個枚舉並且有一個FlagsAtrribute自定義屬性修飾。這意味著ClippedEdges字段可以一個或者多個FrameEdges值。下面列出了FrameEdges的所有可能的值。
當游戲者身體的某一部分超出Kinect視場范圍時,就需要對骨骼追蹤產生的數據進行某些改進,因為某些部位的數據可能追蹤不到或者不准確。最簡單的解決辦法就是提示游戲者身體超出了Kinect的某一邊界范圍讓游戲者回到視場中來。例如,有時候應用程序可能不關心游戲者超出Kinect視場下邊界的情況,但是如果超出了左邊界或者右邊界時就會對應用產生影響,這是可以針對性的給游戲者一些提示。另一個解決辦法是調整Kinect設備的物理位置。Kinect底座上面有一個小的馬達能夠調整Kinect的俯仰角度。俯仰角度可以通過更改KinectSensor對象的ElevationAnagle屬性來進行調整。如果應用程序對於游戲者腳部動作比較關注,那麼通過程序調整Kinect的俯仰角能夠決絕腳部超出視場下界的情況。
ElevationAnagle以度為單位。KinectSensor的MaxElevationAngle和MinElevationAngle確定了可以調整角度的上下界。任何將ElevationAngle設置超出上下界的操作將會掏出ArgumentOutOfRangeExcepthion異常。微軟建議不要過於頻繁重復的調整俯仰角以免損壞馬達。為了使得開發這少犯錯誤和保護馬達,SDK限制了每秒能調整的俯仰角的值。SDK限制了在連續15次調整之後要暫停20秒。
查看本欄目
Joints
每一個骨骼對象都有一個Joints字段。該字段是一個JointsCollection類型,它存儲了一些列的Joint結構來描述骨骼中可追蹤的關節點(如head,hands,elbow等等)。應用程序使用JointsCollection索引獲取特定的關節點,並通過節點的JointType枚舉來過濾指定的關節點。即使Kinect視場中沒有游戲者Joints對象也被填充。
2.4 Joint
骨骼追蹤引擎能夠跟蹤和獲取每個用戶的近20個點或者關節點信息。追蹤的數據以關節點數據展現,它有三個屬性。JointType屬性是一個枚舉類型。下圖描述了可追蹤的所有關節點。
每一個關節點都有類型為SkeletonPoint的Position屬性,他通過X,Y,Z三個值來描述關節點的控件位置。X,Y值是相對於骨骼平面空間的位置,他和深度影像,彩色影像的空間坐標系不一樣。KinectSnesor對象有一些列的坐標轉換方法,可以將骨骼坐標點轉換到對應的深度數據影像中去。最後每一個Skeleton對象還有一個JointTrackingState屬性,他描述了該關節點的跟蹤狀態及方式,下面列出了所有的可能值。
3. 結語
本文首先通過一個例子展示骨骼追蹤系統所涉及的主要對象,並將骨骼數據在UI界面上進行了繪制,在此基礎上詳細介紹了骨骼追蹤對象模型中涉及到的主要對象,方法和屬性。SDK中骨骼追蹤占了大概三分之一的內容,所以熟悉這些對象對於開發基於Kinect應用程序至關重要。限於篇幅,下一篇文章將會演示一個使用Kinect骨骼追蹤系統開發的小游戲,然後討論控件坐標變換,敬請期待。
作者: yangecnu(yangecnu's Blog on 博客園)
出處:http://www.cnblogs.com/yangecnu/