Kinect 感測器的核心功能之一在於產生三維景深數據,開發者可藉此建立各類創新應用。在著手開發前,了解 Kinect 的硬體架構至關重要。Kinect 配備紅外發射器與紅外攝像頭,能偵測人體及非人體物體(如椅子、咖啡杯)。許多商業機構與實驗室正積極研究利用景深數據進行物體偵測。
本文將詳細介紹 Kinect 紅外感測器原理、景深數據格式、景深影像的取得與顯示,以及景深影像的增強處理技術。
與一般輸入裝置不同,Kinect 能產生三維數據。其紅外發射器與攝像頭配合,透過紅外光投射與接收計算深度。微軟 Kinect SDK 不提供直接存取原始紅外數據流的方法,而是將紅外攝像頭捕獲的數據經運算後,輸出為景深影像幀(DepthImageFrame),由 DepthImageStream 物件提供。
DepthImageStream 的使用方式與 ColorImageStream 類似,兩者皆繼承自 ImageStream。以下示範在 WPF 應用程式中顯示景深影像的步驟:
Microsoft.Kinect.dll 參考。Image 元素,命名為 DepthImage。KinectSensor 物件的程式碼(參考前幾篇文章)。KinectSensor 並啟用景深串流:private void InitializeKinectSensor(KinectSensor kinectSensor) {
if (kinectSensor != null) {
DepthImageStream depthStream = kinectSensor.DepthStream;
depthStream.Enable();
depthImageBitMap = new WriteableBitmap(
depthStream.FrameWidth, depthStream.FrameHeight, 96, 96,
PixelFormats.Gray16, null);
depthImageBitmapRect = new Int32Rect(0, 0, depthStream.FrameWidth, depthStream.FrameHeight);
depthImageStride = depthStream.FrameWidth * depthStream.FrameBytesPerPixel;
DepthImage.Source = depthImageBitMap;
kinectSensor.DepthFrameReady += kinectSensor_DepthFrameReady;
kinectSensor.Start();
}
}
DepthFrameReady 事件,將景深像素資料寫入 WriteableBitmap:void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) {
using (DepthImageFrame depthFrame = e.OpenDepthImageFrame()) {
if (depthFrame != null) {
short[] depthPixelData = new short[depthFrame.PixelDataLength];
depthFrame.CopyPixelDataTo(depthPixelData);
depthImageBitMap.WritePixels(depthImageBitmapRect, depthPixelData, depthImageStride, 0);
}
}
}
執行程式後,畫面將顯示灰階景深影像:距離 Kinect 越近的物體顏色越深,越遠則越淡。

紅外攝像頭的視野呈金字塔狀,遠處物體的視野橫截面積較大。影像解析度(如 640×480)與現實世界的物理位置並非一一對應,但每個像素的深度值確實對應該像素所代表物體到感測器的距離。
每個深度像素佔 16 位(2 位元組),但實際深度值僅使用其中 13 位(第 3 至 15 位),低 3 位保留為玩家索引。因此讀取實際深度需將像素值右移 DepthImageFrame.PlayerIndexBitmaskWidth 位(通常為 3)。
int pixelIndex = (int)(p.X + (p.Y * frame.Width));
int depth = depthPixelData[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth;
為了示範,我們在 UI 加入 TextBlock 顯示滑鼠點擊位置的深度值,並保存最後一幀 DepthImageFrame 供查詢:
private DepthImageFrame lastDepthFrame;
void kinectSensor_DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) {
if (lastDepthFrame != null) { lastDepthFrame.Dispose(); lastDepthFrame = null; }
lastDepthFrame = e.OpenDepthImageFrame();
if (lastDepthFrame != null) {
depthPixelData = new short[lastDepthFrame.PixelDataLength];
lastDepthFrame.CopyPixelDataTo(depthPixelData);
depthImageBitMap.WritePixels(depthImageBitmapRect, depthPixelData, depthImageStride, 0);
}
}
private void DepthImage_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
Point p = e.GetPosition(DepthImage);
if (depthPixelData != null && depthPixelData.Length > 0) {
int pixelIndex = (int)(p.X + (p.Y * lastDepthFrame.Width));
int depth = depthPixelData[pixelIndex] >> DepthImageFrame.PlayerIndexBitmaskWidth;
int depthInches = (int)(depth * 0.0393700787);
int depthFt = depthInches / 12;
depthInches %= 12;
PixelDepth.Text = string.Format("{0}mm ~ {1}'{2}\"", depth, depthFt, depthInches);
}
}
注意:Image 控制項的寬高最好硬編碼為 640×480,避免因容器縮放導致座標對應錯誤。
實測結果顯示:牆面距離約 2.9 公尺,手部約 1.2 公尺,數值相當準確。深度值為 -1 表示該像素無法確定深度(可能物體過近)。

原始景深影像多集中在暗部,對比度低。最簡單的增強方法是將像素值按位取反(~pixelData[i]),並過濾掉過近(<0)與過遠(>3.5 公尺)的雜訊,將其設為白色(0xFF)。
private void CreateLighterShadesOfGray(DepthImageFrame depthFrame, short[] pixelData) {
short[] enhPixelData = new short[depthFrame.Width * depthFrame.Height];
for (int i = 0; i < pixelData.Length; i++) {
int depth = pixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
if (depth < 0 || depth > 3500) enhPixelData[i] = 0xFF;
else enhPixelData[i] = (short)~pixelData[i];
}
EnhancedDepthImage.Source = BitmapSource.Create(
depthFrame.Width, depthFrame.Height, 96, 96,
PixelFormats.Gray16, null, enhPixelData,
depthFrame.Width * depthFrame.BytesPerPixel);
}

將 16 位深度值(0~4095)線性映射至 0~255 灰階,再以 RGB 三通道相同值顯示,可獲得更豐富的細節:
private void CreateBetterShadesOfGray(DepthImageFrame depthFrame, short[] pixelData) {
byte[] enhPixelData = new byte[depthFrame.Width * depthFrame.Height * 4];
for (int i = 0, j = 0; i < pixelData.Length; i++, j += 4) {
int depth = pixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
byte gray = (depth < 0 || depth > 3500) ? (byte)0xFF : (byte)(255 * depth / 0xFFF);
enhPixelData[j] = enhPixelData[j+1] = enhPixelData[j+2] = gray;
}
EnhancedDepthImage.Source = BitmapSource.Create(
depthFrame.Width, depthFrame.Height, 96, 96,
PixelFormats.Bgr32, null, enhPixelData,
depthFrame.Width * 4);
}
將深度值對應至色相環(Hue),飽和度與亮度固定,再轉為 RGB 顯示,能直觀呈現距離層次:近處藍、中間紫、遠處紅。
private void CreateColorDepthImage(DepthImageFrame depthFrame, short[] pixelData) {
byte[] enhPixelData = new byte[depthFrame.Width * depthFrame.Height * 4];
for (int i = 0, j = 0; i < pixelData.Length; i++, j += 4) {
int depth = pixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
if (depth < 1200 || depth > 3500) {
enhPixelData[j] = enhPixelData[j+1] = enhPixelData[j+2] = 0;
} else {
double hue = (360.0 * depth / 0xFFF) + 1200; // 簡化計算
byte[] rgb = new byte[3];
ConvertHslToRgb(hue, 100, 100, rgb);
enhPixelData[j] = rgb[2]; // Blue
enhPixelData[j+1] = rgb[1]; // Green
enhPixelData[j+2] = rgb[0]; // Red
}
}
EnhancedDepthImage.Source = BitmapSource.Create(
depthFrame.Width, depthFrame.Height, 96, 96,
PixelFormats.Bgr32, null, enhPixelData,
depthFrame.Width * 4);
}
ConvertHslToRgb 為標準 HSL 轉 RGB 演算法(略)。執行結果:

HSL 轉 RGB 需對 30 萬像素逐一運算,若在 UI 執行緒執行會嚴重阻塞介面。建議將運算移至 BackgroundWorker 或 Task.Run,完成後透過 Dispatcher.BeginInvoke 更新 UI。
本文介紹了 Kinect 紅外感測器產生景深資料流的原理、像素深度值的提取與單位換算、景深影像的基本顯示,以及三種增強視覺化手法:灰階反轉、16 位映射至 32 位灰階、偽彩色渲染。下一篇將深入探討玩家索引位的應用,以及如何結合景深數據擷取人體輪廓與尺寸資訊。