文章目錄
- 1.1 發現串連的Kinect裝置
- 1.2 開啟感應器
- 1.3 停止感應器
上篇文章介紹了Kinect開發的環境配置,這篇文章和下一篇文章將介紹Kinect開發的基本知識,為深入研究Kinect for Windows SDK做好基礎。
每一個Kinect應用都有一些基本元素。應用程式必須探測和發現連結到裝置上的Kinect感應器。在使用這些感應器之前,必須進行初始化,一旦初始化成功後,就能產生資料,我們的程式就能處理這些資料。最後當應用程式關閉是,必須合理的釋放這些感應器。
本文第一部分將會介紹如何探測初始化幾釋放感應器,這是非常基礎的話題,但是對於基於Kinect開發的應用程式非常重要。一旦初始化好了之後,Kinect的各種感應器就能夠產生資料。我們的程式可以讀取這些資料流。Kinect產生的資料流類類似於System.IO命名空間下面的IO資料流。
第二部分將詳細介紹資料流的基礎,並示範如何從Kinect中使用ColorImageStream擷取彩色網路攝影機產生的資料。資料流能夠生產基於像素的資料,使得能夠像從相機或者基本的相片那樣生產彩色映像。可以對這些資料進行各種有趣的處理。
本文是整個Kinect SDK開發的基礎部分,瞭解了這些之後,對於熟悉SDK中其他部分比較有協助。
1. Kinect感應器
基於Kinect開發的應用程式最開始需要用到的對象就是KinectSensor對象,該對象直接表示Kinect硬體裝置。KinectSensor對象是我們想要擷取資料,包括彩色影像資料,景深資料和骨骼追蹤資料的源頭。本文將詳細介紹ColorImageStream,後面的文章將詳細討論DepthImageStream和SkeletonStream。
從KinectSensor擷取資料最常用的方式是通過監聽該對象的一系列事件。每一種資料流都有對應的事件,當改類型資料流可用時,就會觸發改時間。每一個資料流以幀(frame)為單位。例如:ColorImageStream當擷取到了新的資料時就會觸發ColorFrameReady事件。當在討論各個具體的感應器資料流是我們將會詳細討論這些事件。
每一種資料流(Color,Depth,Skeleton)都是以資料點的方式在不同的座標系中顯示的,在後面的討論中我們能夠清楚的看到這一點。將一個資料流中的點資料轉換到另一個資料流中是一個很常見的操作,在本文的後面將會討論如何轉換以及為什麼這種轉換很有必要。KinectSensor對象有一些列的方法能夠進行資料流到資料點陣的轉換,他們是MapDepthToColorImagePoint,MapDepthToSkeletonPoint以及MapSkeletonPointToDepth。在擷取Kinect資料前,我們必須先發現串連的Kinect裝置。發現Kinect裝置很簡單,但是也有需要主注意的地方。
1.1 發現串連的Kinect裝置
KinectObject對象沒有公用的構造器,應用程式不能直接建立它。相反,該對象是SDK在探測到有串連的Kinect裝置時建立的。當有Kinect裝置串連到電腦上時,應用程式應該得到通知或者提醒。KinectSeneor對象有一個靜態屬性KinectSensors,該屬性是一個KinectSensorCollection集合,該集合繼承自ReadOnlyCollection,ReadOnlyCollection集合很簡單,他只有一個索引器和一個稱之為StatusChanged的事件。
使用集合中的索引器來擷取KinectSensor對象。集合中元素的個數就是Kinect裝置的個數。也就是說,一台電腦上可以串連多個Kinect裝置來從不同的方向擷取資料。應用程式可以使用多個Kinect裝置來擷取多方面的資料,Kinect個數的限制 只有電腦配置的限制。由於每個Kinect是通過USB來進行資料轉送的,所以每一個Kinect裝置需要一條USB線與電腦相連。此外,更多的Kinect裝置需要更多的CPU和記憶體消耗。
尋找Kinect裝置可以通過簡單的遍曆集合找到;但是KinectSensor集合中的裝置不是都能直接使用,所以KinectSensor對象有一個Status屬性,他是一個枚舉類型,標識了當前Kinect裝置的狀態。下表中列出了感應器的狀態及其含義:
只有裝置在Connected狀態下時,KinectSensor對象才能初始化。在應用的整個生命週期中,感應器的狀態可能會發生變化,這意味著我們開發的應用程式必須監控裝置的串連狀態,並且在裝置串連狀態發生變化時能夠採取相應的措施來提高使用者體驗。例如,如果串連Kinect的USB線從電腦拔出,那麼感應器的串連狀態就會變為Disconnected,通常,應用程式在這種情況下應該暫停,並提示使用者將Kinect裝置插入到電腦上。應用程式不應該假定在一開始時Kinect裝置就處於可用狀態,也不應該假定在整個程式啟動並執行過程中,Kinect裝置會一直與電腦串連。
下面,首先建立一個WPF應用程式來展示如何發現,擷取Kinect感應器的狀態。先建按一個WPF項目,並添加Microsoft.Kinect.dll。在MainWindows.xaml.cs中寫下如下代碼:
public partial class MainWindow : Window{ //私人Kinectsensor對象 private KinectSensor kinect; public KinectSensor Kinect { get { return this.kinect;} set { //如果帶賦值的感應器和目前的不一樣 if (this.kinect!=value) { //如果當前的感測對象不為null if (this.kinect!=null) { //uninitailize當前對象 this.kinect=null; } //如果傳入的對象不為空白,且狀態為串連狀態 if (value!=null&&value.Status==KinectStatus.Connected) { this.kinect=value; } } } } public MainWindow() { InitializeComponent(); this.Loaded += (s, e) => DiscoverKinectSensor(); this.Unloaded += (s, e) => this.kinect = null; } private void DiscoverKinectSensor() { KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged; this.Kinect = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected); } private void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e) { switch (e.Status) { case KinectStatus.Connected: if (this.kinect == null) this.kinect = e.Sensor; break; case KinectStatus.Disconnected: if (this.kinect == e.Sensor) { this.kinect = null; this.kinect = KinectSensor.KinectSensors.FirstOrDefault(x => x.Status == KinectStatus.Connected); if (this.kinect == null) { //TODO:通知用於Kinect已拔出 } } break; //TODO:處理其他情況下的狀態 } }}
上面的代碼注釋很詳細,首先定義了一個私人變數kinect,應用程式應該定義一個私人的變數來儲存對擷取到的KincectSensor對象的引用,當應用程式不在需要KinectSensor產生資料時,可以使用這個局部變數來釋放對KinectSensor對象的引用從而釋放資源。我們還定義了一個Kinect屬性來對這個私人變數進行封裝,使用屬性的目的是保證能夠以正確的方式初始化和反初始化KinectSensor對象。在Set方法中我們可以看到,自由待賦值的對象的組航太是Connected的時候我們才進行賦值操作,任何將沒有處在Connected狀態的感應器對象複製給KinectSensor對象時都會拋出InvalidOperationException異常。
在建構函式中有兩個匿名方法,一個用來監聽Loaded事件,一個用來監聽Unloaded事件。當卸載時應該將Kinect屬性置為空白。在視窗的Loaded事件中程式通過DiscoverKinectSensor方法試圖調用一個串連了的感應器。在表單的Loaded和Unloaded事件中註冊這兩個事件用來初始化和釋放Kinect對象,如果應用程式沒有找到Kinect對象,將會通知使用者。
DiscoverKinectSensor方法只有兩行代碼,第一行代碼註冊StatusChanged事件,第二行代碼通過lambda運算式查詢集合中第一個處在Connected狀態的感應器對象,並將該對象複製給Kinect屬性。Kinect屬性的set方法確保能都賦值一個合法的Kinect對象。
StatusChanged事件中值得注意的是,當狀態為KinectSensor.Connected的時候,if語句限制了應用程式只能有一個kinect感應器,他忽略了電腦中可能串連的其他Kinect感應器。
以上代碼展示了用於發現和引用Kinect裝置的最精簡的代碼,隨著應用的複雜,可能需要更多的代碼來保證安全執行緒以及能讓記憶體回收行程及時釋放資源以防止記憶體泄露。
1.2 開啟感應器
一旦發現了感應器,在應用程式能夠使用感應器之前必須對其進行初始化。感應器的初始化包括三個步驟。首先,應用程式必須設定需要使用的資料流,並將其狀態設為可用。每一中類型的資料流都有一個Enable方法,該方法可以初始化資料流。每一種資料流都完全不同,在使用之前需要進行一些列的設定。在一些情況下這些設定都在Enable方法中處理了。在下面,我們將會討論如何初始化ColorImageStream資料流,在以後的文章中還會討論如何初始化DepthImageStream資料流和SkeletonStream資料流。
初始化之後,接下來就是要確定應用程式如何使用產生的資料流。最常用的方式是使用Kinect對象的一些列事件,每一種資料流都有對應的事件,他們是:ColorImageStream對應ColorFrameReady事件、DepthImageStream對應DepthFrameReady事件、SkeletonStream對象對應SkeletonFrameReady事件。以及AllFramesReady事件。各自對應的事件只有在對應的資料流enabled後才能使用,AllFramesReady事件在任何一個資料流狀態enabled時就能使用。
最後,應用程式調用KinectSensor對象的Start方法後,frame-ready事件就會觸發從而產生資料。
1.3 停止感應器
一旦感應器開啟後,可以使用KinectSensor對象的Stop方法停止。這樣所有的資料產生都會停止,因此在監聽frameready事件時要先檢查感應器是否不為null。
KinectSensor對象以及資料流都會使用系統資源,應用程式在不需要使用KinectSensor對象時必須能夠合理的釋放這些資源。在這種情況下,程式不僅要停止傳單器,還用登出frameready事件。注意,不要去調用KinectSensor對象的Dispose方法。這將會阻止應用程式再次擷取感應器。應用程式必須從啟或者將Kinect從新拔出然後插入才能再次獲得並使用對象。
2. 彩色影像資料流
Kinect有兩類網路攝影機,近紅外網路攝影機和普通的視頻網路攝影機。視頻網路攝影機提供了一般網路攝影機類似的彩色影像。這種資料流是三中資料流中使用和設定最簡單的。因此我將他作為Kinect資料流介紹的例子。
使用Kinect資料流也有三部。首先是資料流必須可用。一旦資料流可用,應用程式就可以從資料量中讀取資料並對資料進行處理和展現。一旦有新的資料幀可用,這兩個步驟就會一直進行,下面的代碼展現了如何初始化ColorImage對象。
public KinectSensor Kinect{ get { return this.kinect;} set { //如果帶賦值的感應器和目前的不一樣 if (this.kinect!=value) { //如果當前的感測對象不為null if (this.kinect!=null) { UninitializeKinectSensor(this.kinect); //uninitailize當前對象 this.kinect=null; } //如果傳入的對象不為空白,且狀態為串連狀態 if (value!=null&&value.Status==KinectStatus.Connected) { this.kinect=value; InitializeKinectSensor(this.kinect); } } }}private void InitializeKinectSensor(KinectSensor kinectSensor){ if (kinectSensor != null) { kinectSensor.ColorStream.Enable(); kinectSensor.ColorFrameReady += new EventHandler<ColorImageFrameReadyEventArgs>(kinectSensor_ColorFrameReady); kinectSensor.Start(); }}private void UninitializeKinectSensor(KinectSensor kinectSensor){ if (kinectSensor != null) { kinectSensor.Stop(); kinectSensor.ColorFrameReady -= new EventHandler<ColorImageFrameReadyEventArgs>(kinectSensor_ColorFrameReady); }}
上面的代碼對之前Kinect屬性進行了修改,加粗為修改部分。新添加的兩行調用了兩個方法,分別初始化和釋放KinectSensor和ColorImageStream對象。InitializeKinectSensor對象調用ColorImageStream的Enable方法,註冊ColorFrameReady事件並調用start方法。一旦開啟了感應器,當新資料幀大道是就會觸發frameready事件,該事件觸發頻率是每秒30次。
在實現Kinect_ColorFrameReady方法前,我們先在XAML表單中添加一些空間來展現擷取到的資料,代碼如下:
<Window x:Class="KinectApplicationFoundation.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="ColorImageStreamFromKinect" Height="350" Width="525"> <Grid> <Image x:Name="ColorImageElement"></Image> </Grid></Window>
然後,在Kinect_ColorFrameReady方法中,我們首先通過開啟或者擷取一個frame來提取獲Frame資料。ColorImageFrameReadyEventArgs對象的OpenColorImageFrame屬性返回一個當前的ColorImageFrame對象。這個對象實現了IDisposable介面。所以可以將這個對象抱在using語句中的原因,在提取像素資料之前需要使用一個Byte數組儲存擷取到的資料。FrameObject對象的PixelDataLength對象返回資料和序列的具體大小。調用CopyPixelDataTo方法可以填充像素資料,然後將資料展示到image控制項上,具體代碼如下:
void kinectSensor_ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e){ using (ColorImageFrame frame = e.OpenColorImageFrame()) { if (frame != null) { byte[] pixelData = new byte[frame.PixelDataLength]; frame.CopyPixelDataTo(pixelData); ColorImageElement.Source = BitmapImage.Create(frame.Width, frame.Height, 96, 96, PixelFormats.Bgr32, null, pixelData, frame.Width * frame.BytesPerPixel); } }}
運行程式,就能得到從Kinect擷取的視頻資訊,如所示這是從Kinect彩色網路攝影機擷取的我房間的照片。和一般的視頻沒什麽兩樣,只不過這個是從Kinect的視頻網路攝影機產生的。
3. 結語
本文簡要介紹了Kinect開發會遇到的基本對象,Kinect物理裝置的發現,KinectSensor對象的初始化,開啟KinectSensor對象以及如何擷取資料流,最後以ColorImageStream對象為例展示了如何從Kinect擷取資料並展現出來。
由於Kinect的彩色網路攝影機預設每秒產生30副ColorImageFrame,所以上面的應用程式會產生30個Bitmap對象,而且這些對象初始化後很快將變成垃圾等待記憶體回收行程進行收集,當採集的資料量很大時,將會對效能產生影響。限於篇幅原因,下篇文章將會介紹如何對這一點進行改進,並將討論擷取Kinect感應器產生資料的兩種編程模式:基於事件的模式和輪詢的模式。本文範例程式碼點擊此處下載,希望以上內容希望對你瞭解Kinect SDK有所協助。