和其他攝像機一樣,近紅外攝像機也有視場。Kinect攝像機的視野是有限的,如下圖所示:
如圖,紅外攝像機的視場是金字塔形狀的。離攝像機遠的物體比近的物體擁有更大的視場橫截面積。這意味着影像的高度和寬度,比如640X480和攝像機視場的物理位置並不一一對應。但是每個像素的深度值是和視場中物體離攝像機的距離是對應的。深度幀數據中,每個像素占16位,這樣BytesPerPixel屬性,即每一個像素占2個字節。每一個像素的深度值只占用了16個位中的13個位。如下圖:
獲取每一個像素的距離很容易,但是要直接使用還需要做一些位操作。可能大家在實際編程中很少情況會用到位運算。如上圖所示,深度值存儲在第3至15位中,要獲取能夠直接使用的深度數據需要向右移位,將游戲者索引(Player Index)位移除。后面將會介紹游戲者索引位的重要性。下面的代碼簡要描述了如何獲取像素的深度值。代碼中pixelData變量就是從深度幀數據中獲取的short數組。PixelIndex基於待計算像素的位置就算出來的。SDK在DepthImageFrame類中定義了一個常量PlayerIndexBitmaskWidth,它定義了要獲取深度數據值需要向右移動的位數。在編寫代碼時應該使用這一常量而不是硬編碼,因為未來隨着軟硬件水平的提高,Kinect可能會增加能夠同時識別人數的個數,從而改變PlayerIndexBitmaskWidth常量的值。
1 Int32 pixelIndex = (Int32)(p.X + ((Int32)p.Y * frame.Width)); 2 Int32 depth = this.depthPixelDate[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth;
顯示深度數據最簡單的方式是將其打印出來。我們要將像素的深度值顯示到界面上,當鼠標點擊時,顯示鼠標點擊的位置的像素的深度值。第一步是在主UI界面上添加一個TextBlock:
<Window x:Class="KinectDepthImageDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="KinectDepthImage" Height="600" Width="1280" WindowStartupLocation="CenterScreen"> <Grid> <StackPanel Orientation="Horizontal"> <TextBlock x:Name="PixelDepth" FontSize="48" HorizontalAlignment="Left" /> <Image x:Name="DepthImage" Width="640" Height="480" ></Image> </StackPanel> </Grid> </Window>
接着我們要處理鼠標點擊事件。在添加該事件前,需要首先添加一個私有變量lastDepthFrame來保存每一次DepthFrameReady事件觸發時獲取到的DepthFrame值。因為我們保存了對最后一個DepthFrame對象的引用,所以事件處理代碼不會馬上釋放該對象。然后,注冊DepthFrame 圖像控件的MouseLeftButtonUp事件。當用戶點擊深度圖像時,DepthImage_MouseLeftButtonUp事件就會觸發,根據鼠標位置獲取正確的像素。最后一步將獲取到的像素值的深度值顯示到界面上,代碼如下:
void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) { if (lastDepthFrame!=null) { lastDepthFrame.Dispose(); lastDepthFrame = null; } lastDepthFrame = e.OpenDepthImageFrame(); if (lastDepthFrame != null) { depthPixelDate = new short[lastDepthFrame.PixelDataLength]; lastDepthFrame.CopyPixelDataTo(depthPixelDate); depthImageBitMap.WritePixels(depthImageBitmapRect, depthPixelDate, depthImageStride, 0); } } private void DepthImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Point p = e.GetPosition(DepthImage); if (depthPixelDate != null && depthPixelDate.Length > 0) { Int32 pixelIndex = (Int32)(p.X + ((Int32)p.Y * this.lastDepthFrame.Width)); Int32 depth = this.depthPixelDate[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth; Int32 depthInches = (Int32)(depth * 0.0393700787); Int32 depthFt = depthInches / 12; depthInches = depthInches % 12; PixelDepth.Text = String.Format("{0}mm~{1}'{2}", depth, depthFt, depthInches); } }
有一點值得注意的是,在UI界面中Image空間的屬性中,寬度和高度是硬編碼的。如果不設置值,那么空間會隨着父容器(From窗體)的大小進行縮放,如果空間的長寬尺寸和深度數據幀的尺寸不一致,當鼠標點擊圖片時,代碼就會返回錯誤的數據,在某些情況下甚至會拋出異常。像素數組中的數據是固定大小的,它是根據DepthImageStream的Enable方法中的DepthImageFormat參數值來確定的。如果不設置圖像控件的大小,那么他就會根據Form窗體的大小進行縮放,這樣就需要進行額外的計算,將鼠標的在Form中的位置換算到深度數據幀的維度上。這種縮放和空間轉換操作很常見,在后面的文章中我們將會進行討論,現在為了簡單,對圖像控件的尺寸進行硬編碼。
結果如下圖,由於截屏時截不到鼠標符號,所以用紅色點代表鼠標位置,下面最左邊圖片中的紅色點位於牆上,該點距離Kinect 2.905米,中間圖的點在我的手上,可以看出手離Kinect距離為1.221米,實際距離和這個很相近,可見Kinect的景深數據還是很准確的。
上面最右邊圖中白色點的深度數據為-1mm。這表示Kinect不能夠確定該像素的深度。在處理上數據時,這個值通常是一個特殊值,可以忽略。-1深度值可能是物體離Kinect傳感器太近了的緣故。