Kinect 骨骼追蹤數據的處理方法


http://www.ituring.com.cn/article/196144

 

作者/ 吳國斌

博士,PMP,微軟亞洲研究院學術合作經理。負責中國高校及科研機構Kinect for Windows學術合作計划及微軟精英大挑戰Kinect主題項目。曾擔任微軟TechEd2011 Kinect論壇講師,微軟亞洲教育高峰會Kinect分論壇主席,中國計算機學會學科前沿講習班Kinect主題學術主任。

 

骨骼追蹤技術是Kinect的核心技術,它可以准確標定人體的20個關鍵點,並能對這20個點的位置進行實時追蹤。利用這項技術,可以開發出各種基於體感人機交互的有趣應用。

骨骼追蹤數據的結構

目前,Kinect for Windows SDK中的骨骼API可以提供位於Kinect前方至多兩個人的位置信息,包括詳細的姿勢和骨骼點的三維坐標信息。另外,Kinect for Windows SDK最多可以支持20個骨骼點。數據對象類型以骨骼幀的形式提供,每一幀最多可以保存20個點,如圖1所示。

圖1 20個骨骼點示意圖

在SDK中每個骨骼點都是用Joint類型來表示的,每一幀的20個骨骼點組成基於Joint類型的集合。此類型包含3個屬性,具體內容如下所示。

  • JointType:骨骼點的類型,這是一種枚舉類型,列舉出了20個骨骼點的特定名稱,比如“HAND_LEFT”表示該骨骼點是左手節點。

  • PositionSkeletonPoint類型表示骨骼點的位置信息。SkeletonPoint是一個結構體,包含X、Y、Z三個數據成員,用以存儲骨骼點的三維坐標。

  • TrackingStateJointTrackingState類型也是一種枚舉類型,表示該骨骼點的追蹤狀態。其中,Tracked表示正確捕捉到該骨骼點,NotTracked表示沒有捕捉到骨骼點,Inferred表示狀態不確定。

半身模式

如果應用程序只需要捕捉上半身的姿勢動作,就可以采用Kinect for Windows SDK提供的半身模式(Seated Mode)。在半身模式下,系統只捕捉人體上半身10個骨骼點的信息,而忽略下半身另外10個骨骼點的位置信息,這樣就解決了用戶坐在椅子上時無法被Kinect識別的問題,即使下半身骨骼點的數據不穩定或是不存在也不會對上半身的骨骼數據造成影響。而且當用戶距離Kinect設備只有0.4米時,應用程序仍能正常地進行骨骼追蹤,這就大幅提高了骨骼追蹤的性能。

半身模式定義在枚舉類型SkeletonTrackingMode中,該類型包含兩個枚舉值:Default和Seated。前者為默認的骨骼追蹤模式,會正常捕捉20個骨骼點;后者為半身模式,選擇該值則只捕捉上半身的10個骨骼點。

開發者可以通過改變SkeletonStream對象的TrackingMode屬性來設置骨骼追蹤的模式,代碼如下:

kinectSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;

骨骼追蹤數據的獲取方式

應用程序獲取下一幀骨骼數據的方式同獲取彩色圖像和深度圖像數據的方式一樣,都是通過調用回調函數並傳遞一個緩存實現的,獲取骨骼數據調用的是OpenSkeletonFrame()函數。如果最新的骨骼數據已經准備好了,那么系統就會將其復制到緩存中;但如果應用程序發出請求時,新的骨骼數據還未准備好,此時可以選擇等待下一個骨骼數據直至其准備完畢,或者立即返回稍后再發送請求。對於NUI骨骼API而言,相同的骨骼數據只會提供一次。

NUI骨骼API提供了兩種應用模型,分別是輪詢模型和時間模型,簡要介紹如下。

  • 輪詢模型是讀取骨骼事件最簡單的方式,通過調用SkeletonStream類的OpenNextFrame()函數即可實現。OpenNextFrame()函數的聲明如下所示。

    public SkeletonFrame OpenNextFrame ( int millisecondsWait )

    可以傳遞參數指定等待下一幀骨骼數據的時間。當新的數據准備好或是超出等待時間時,OpenNextFrame()函數才會返回。

  • 時間模型以事件驅動的方式獲取骨骼數據,更加靈活、准確。應用程序傳遞一個事件處理函數給SkeletonFrameReady事件,該事件定義在KinectSensor類中。當下一幀的骨骼數據准備好時,會立即調用該事件回調函數。因此Kinect應用應該通過調用OpenSkeletonFrame()函數來實時獲取骨骼數據。

實例——調用API獲取骨骼數據並實時繪制

本實例程序將實現獲取骨骼數據,然后將骨骼點的坐標作為Ellipse控件的20個位置坐標,同時用線段將相應的點連接起來,最后將繪制出的骨架映射到彩色圖像上。讀者可以在實例1的基礎上開始本實例,具體操作步驟如下所示。

1. 在Window_Loaded()函數中添加下列骨骼數據流的啟動函數,並添加kinectSensor_SkeletonFrameReady事件處理函數相應的SkeletonFrameReady事件。

kinectSensor.SkeletonStream.Enable(); kinectSensor.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinectSensor_SkeletonFrameReady);

2. 准備WPF界面。通過以下代碼在界面上添加20個小圓點,分別跟蹤由Kinect for Windows SDK獲取到的人體的20個關鍵點,並將這20個點標記為不同的顏色。

<Canvas Name="SkeletonCanvas" Visibility="Visible"> <Ellipse Canvas.Left="0" Canvas.Top="0" Height="10" Name="headPoint" Width="10" Fill="Red" /> <Ellipse Canvas.Left="10" Canvas.Top="0" Height="10" Name="shouldercenterPoint" Width="10" Fill="Blue" /> <Ellipse Canvas.Left="20" Canvas.Top="0" Height="10" Name="shoulderrightPoint" Width="10" Fill="Orange" /> …省略中間的Ellipse定義 <Image Canvas.Left="303" Canvas.Top="161" Height="150" Name="image1" Stretch="Fill" Width="200" /> </Canvas>

此時,設計窗口如圖2所示。

圖2 WPF設計界面

3. 編寫kinectSensor_SkeletonFrameReady()事件處理函數。正確連接Kinect后,當用戶站在Kinect前並且Kinect能夠正確識別人體時,將觸發該事件處理函數,其代碼如下:

private void kinectSensor_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e) { using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame()) { if (skeletonFrame != null) { skeletonData = new Skeleton[kinectSensor.SkeletonStream.FrameSkeletonArrayLength]; skeletonFrame.CopySkeletonDataTo(this.skeletonData); Skeleton skeleton = (from s in skeletonData where s.TrackingState == SkeletonTrackingState.Tracked select s).FirstOrDefault(); if (skeleton!=null) { SetAllPointPosition(skeleton); } } } }

上述代碼使用LINQ語句來獲取TrackingState等於Tracked的骨骼數據。目前SDK最多可以追蹤兩幅骨骼。為了簡化起見,本實例只對捕捉到的第一幅骨骼進行追蹤和顯示。

4. 在Skeleton對象的Joints屬性集合中保存了所有骨骼點的信息,每個骨骼點的信息都是一個Joint對象。為了得到特定的骨骼點,同樣使用LINQ語句對JointJointType屬性進行篩選,相關代碼如下:

Joint headJoint = (from j in skeleton.Joints where j.JointType == JointType.Head select j).FirstOrDefault();

在本實例程序中,需要遍歷每個骨骼點,並分別對其進行處理。這里使用foreach語句來實現,並根據JointType屬性進行處理。在SetAllPointPosition()函數中可以看到具體的實現細節。

foreach (Joint joint in skeleton.Joints) { Point jointPoint = GetDisplayPosition(joint); switch (joint.JointType) { case JointType.Head: SetPointPosition(headPoint, joint); headPolyline.Points.Add(jointPoint); break; ... } }

5. 前面提到,JointPosition屬性的X、Y、Z表示該骨骼點的三維位置,其中X和Y的范圍都是-1~1,而Z是Kinect到識別物體的距離。

為了能更好地將這20個點顯示出來,需要對Position的X值和Y值進行縮放,可以通過以下函數實現。

private Point GetDisplayPosition(Joint joint) { var scaledJoint = joint.ScaleTo(640, 480); return new Point(scaledJoint.Position.X, scaledJoint.Position.Y); }

上面語句中,ScaleTo函數的最后兩個參數640和480分別代表原始數據X和Y的最大值,通過該語句可以將X坐標放大到0~640范圍內的任意值,將Y坐標放大到0~480范圍內的任意值。該坐標是相對於應用程序窗口的左上角(0,0)而言的,窗口的寬和高分別是640和480,以保證彩色圖像和骨骼繪制的結果相匹配。

其中,ScaleTo()函數是Coding4FunHelp類中的方法。Coding4Fun是一個Kinect開發輔助類庫。讀者可以從http://c4fkinect.codeplex.com/下載該類庫,並通過“Add Reference”菜單項將Coding4Fun.Kinect.Wpf.dll添加到項目中。

6. 編寫一個函數,將每個骨骼點轉換后的(X,Y)坐標值分別映射到相應的Ellipse控件的LeftTop屬性上,其代碼如下:

private void SetPointPosition(FrameworkElement ellipse, Joint joint) { var scaledJoint = joint.ScaleTo(640, 480); Canvas.SetLeft(ellipse, scaledJoint.Position.X); Canvas.SetTop(ellipse, scaledJoint.Position.Y); SkeletonCanvas.Children.Add(ellipse); }

使用Polyline類表示骨架線,顯而易見,骨架由5條多段線組成,分別定義它們,並在遍歷所有骨骼點時分類存儲相應的點。詳見SetAllPointPosition()函數,相關代碼如下:

Polyline headPolyline = new Polyline(); Polyline handleftPolyline = new Polyline(); Polyline handrightPolyline = new Polyline(); Polyline footleftPolyline = new Polyline(); Polyline footrightPolyline = new Polyline(); private void SetAllPointPosition(Skeleton skeleton) { SkeletonCanvas.Children.Clear(); headPolyline.Points.Clear(); handleftPolyline.Points.Clear(); handrightPolyline.Points.Clear(); footleftPolyline.Points.Clear(); footrightPolyline.Points.Clear(); foreach (Joint joint in skeleton.Joints) { Point jointPoint = GetDisplayPosition(joint); switch (joint.JointType) { case JointType.Head: SetPointPosition(headPoint, joint); headPolyline.Points.Add(jointPoint); break; case JointType.ShoulderCenter: SetPointPosition(shouldercenterPoint, joint); headPolyline.Points.Add(jointPoint); handleftPolyline.Points.Add(jointPoint); handrightPolyline.Points.Add(jointPoint); break; case JointType.ShoulderLeft: SetPointPosition(shoulderleftPoint, joint); handleftPolyline.Points.Add(jointPoint); break; ... case JointType.FootRight: SetPointPosition(footrightPoint, joint); footrightPolyline.Points.Add(jointPoint); break; default: ; break; } } headPolyline.Stroke = new SolidColorBrush(Colors.Blue); headPolyline.StrokeThickness = 5; SkeletonCanvas.Children.Add(headPolyline); handleftPolyline.Stroke = new SolidColorBrush(Colors.Blue); handleftPolyline.StrokeThickness = 5; SkeletonCanvas.Children.Add(handleftPolyline); handrightPolyline.Stroke = new SolidColorBrush(Colors.Blue); handrightPolyline.StrokeThickness = 5; SkeletonCanvas.Children.Add(handrightPolyline); footleftPolyline.Stroke = new SolidColorBrush(Colors.Blue); footleftPolyline.StrokeThickness = 5; SkeletonCanvas.Children.Add(footleftPolyline); footrightPolyline.Stroke = new SolidColorBrush(Colors.Blue); footrightPolyline.StrokeThickness = 5; SkeletonCanvas.Children.Add(footrightPolyline); }

7. 運行程序,顯示結果如圖3所示。

圖3 全身骨骼點運行結果

8. 若要使用半身模式,只需在初始化kinectSensor對象時添加以下語句即可。

kinectSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;

運行結果如圖4所示。

圖4 半身模式運行結果

由於RGB圖像數據與深度圖像數據(骨骼數據)的空間坐標系是不同的,前者的原點是RGB攝像頭,后者的原點是紅外攝像頭,因此本實例中使用獲取的骨骼點坐標直接繪制在RGB圖像上會有相應的誤差。若要修正這些誤差,可以調用Kinect for Windows SDK提供的映射函數,將骨骼點坐標映射到RGB圖像坐標上。具體做法為將上面用的ScaleTo函數替換為MapSkeletonPointToColorPoint,使用方法如下所示:

ColorImagePoint colorImagePoint = kinectSensor.CoordinateMapper.MapSkeletonPointToColorPoint (joint.Position, ColorImageFormat.RgbResolution640x480Fps30);

骨骼點旋轉信息

除了跟蹤骨骼點的位置,Kinect SDK還能計算出骨骼點的旋轉信息。這是Kinect SDK 1.5版本新增的功能,利用此功能可以計算出人體骨骼在Yaw軸的旋轉情況,在此之前,僅通過骨骼點位置是無法實現此類計算的。根據相對參照系不同,旋轉信息可以分為相對旋轉信息和絕對旋轉信息,這兩種信息均包含了其旋轉的矩陣參數和四元數參數。開發者可以使用這些數據方便地進行動作識別以及控制人形3D模型。

骨骼點旋轉信息存儲方式

在Kinect SDK中,骨骼點旋轉信息定義為BoneOrientation類,包含以下數據成員。

  • StartJoint:起始骨骼點;

  • EndJoint:結束骨骼點;

  • HierarchicalRotation:相對旋轉信息;

  • AbsoluteRotation:絕對旋轉信息。

在學習相對旋轉信息和絕對旋轉信息的具體含義之前,我們首先要定義骨骼點坐標系。對Kinect跟蹤到的20個骨骼點進行分層:將“髖部中心”作為初始骨骼點,相鄰的骨骼點逐層向下延伸,如圖5所示。

圖5 骨骼點分層圖

而骨骼點坐標系即為以該骨骼點為原點,以其上層骨骼點到它的直線方向為y軸正方向的坐標系,如圖6所示。

圖6 相對旋轉信息

其中,相對旋轉信息就代表了一段骨骼中,起始骨骼點和結束骨骼點的兩個坐標系之間的轉移參數。相應地,絕對旋轉信息代表了結束骨骼點坐標系和Kinect空間坐標系之間的轉移參數,如圖7所示。

圖7 絕對旋轉信息

在骨骼數據回調函數中獲取骨骼點旋轉信息

由於骨骼點旋轉信息包含在骨骼數據流中,因此需要在骨骼數據的回調函數中獲取相應的數據。在獲取了一幀SkeletonFrame中的SkeletonData之后,可以使用下列代碼讀取骨骼點旋轉信息:

foreach (Skeleton skeleton in this.skeletonData) { if (skeleton.TrackingState == SkeletonTrackingState.Tracked) { foreach (BoneOrientation orientation in skeleton.BoneOrientations) { BoneRotation hierarchical = orientation.HierarchicalRotation; BoneRotation absolute = orientation.AbsoluteRotation; } } }

可以看出,讀取骨骼點旋轉信息的方法和讀取骨骼數據類似,其中BoneRotation類型的數據記錄了旋轉信息的矩陣和四元數。

綜述

雖然骨骼點旋轉信息僅僅是依靠骨骼數據計算出來的,但是利用這一數據可以極其簡便地完成人體姿態和動作的識別以及三維模型控制。不過需要注意的是,這里得到的旋轉信息是由骨骼數據計算而來的原始數據,由於骨骼跟蹤數據本身的抖動原因,旋轉信息也會產生很大的噪聲。因此,必須要先對其進行一定程度的降噪和平滑處理,才能在應用程序中使用。

小結

本文主要介紹了骨骼追蹤數據的結構,並結合實例3講解了骨骼數據的獲取和處理方式,實例4通過Kinect實現了對PPT播放的控制,這能夠幫助讀者更形象地理解如何利用骨骼追蹤數據實現體感應用程序。另外,本文所介紹的半身模式和骨骼點旋轉信息是Kinect最新版的SDK中新增的特性,半身模式支持只需上體姿勢控制的應用;骨骼點旋轉信息的加入有效地提高了特定姿勢識別的准確度,這使得應用都更具真實感。

 

Kinect是微軟公司推出的最新的基於體感交互的人機交互設備。《Kinect體感人機交互開發基礎》分為3個部分,首先介紹了Kinect的結構和功能以及如何配置相關的開發環境,接着結合實例介紹如何使用Kinect for Windows SDK提供的API,最后通過4個實例詳細講述了使用Kinect for Windows SDK開發項目的實現過程。本文節選自《Kinect體感人機交互開發基礎》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM