承接上一篇深度資料基礎操作,本文進階探討四大實用主題:
MapDepthToColorImagePoint 將深度遮罩映射至彩色影像,達成即時抠圖合成。原始深度範圍 0~4095 mm,其中 0 表無效。微軟建議有效互動區間為 1220 mm (4 英尺) ~ 3810 mm (12.5 英尺)。處理前先濾除範圍外雜訊:
const int LoDepthThreshold = 1220;
const int HiDepthThreshold = 3810;
int depth = pixelData[i] >> DepthImageFrame.PlayerIndexBitmaskWidth; // 右移 3 位取得深度
if (depth >= LoDepthThreshold && depth <= HiDepthThreshold) { /* 有效像素 */ }
統計各深度值出現頻率,繪製水平長條圖:
private void CreateDepthHistogram(DepthImageFrame frame, short[] pixelData)
{
int[] depths = new int[4096];
foreach (short p in pixelData)
{
int d = p >> DepthImageFrame.PlayerIndexBitmaskWidth;
if (d >= LoDepthThreshold && d <= HiDepthThreshold) depths[d]++;
}
int max = depths.Max();
double barWidth = Math.Max(3, HistogramPanel.ActualWidth / depths.Length);
HistogramPanel.Children.Clear();
for (int i = 0; i < depths.Length; i++)
{
if (depths[i] > 0)
{
var rect = new Rectangle
{
Fill = Brushes.Black,
Width = barWidth,
Height = HistogramPanel.ActualHeight * depths[i] / (double)max,
VerticalAlignment = VerticalAlignment.Bottom,
Margin = new Thickness(1,0,1,0)
};
HistogramPanel.Children.Add(rect);
}
}
}

每像素 16 bits:[15:3] = 深度值 (mm),[2:0] = 玩家索引 (0~6)。SDK 常數:
DepthImageFrame.PlayerIndexBitmaskWidth = 3DepthImageFrame.PlayerIndexBitmask = 7 (0x07)int playerIndex = pixelData[i] & DepthImageFrame.PlayerIndexBitmask; // 取低 3 位
// playerIndex == 0 → 背景;1~6 → 對應玩家
注意: 必須同時啟用
SkeletonStream,深度幀才會填入玩家索引。且索引值不固定對應特定使用者(進出視野會重新分配),切勿硬編碼假設「玩家 1 = 索引 1」。
private void CreatePlayerDepthImage(DepthImageFrame frame, short[] pixelData)
{
int bpp = 4; // BGRA
byte[] output = new byte[frame.Width * frame.Height * bpp];
for (int i = 0, j = 0; i < pixelData.Length; i++, j += bpp)
{
int pid = pixelData[i] & DepthImageFrame.PlayerIndexBitmask;
if (pid == 0) { output[j] = output[j+1] = output[j+2] = 0xFF; } // 白背景
else { output[j] = output[j+1] = output[j+2] = 0x00; } // 黑前景
output[j+3] = 0xFF;
}
EnhancedDepthImage.WritePixels(rect, output, stride, 0);
}

Kinect 水平視場角 57°、垂直 43°。深度 D 處的可視寬度 W:

W = 2 * D * tan(57°/2),單位像素寬度對應實體寬度 = W / FrameWidth。同理高度用 43° 計算。
class PlayerDepthData
{
const double MmPerInch = 25.4;
static readonly double HorTan = Math.Tan(57.0 / 2 * Math.PI / 180);
static readonly double VerTan = Math.Tan(43.0 / 2 * Math.PI / 180);
int depthSum, depthCnt, minX, maxX, minY, maxY;
public int PlayerId { get; }
public double FrameWidth { get; }, FrameHeight { get; }
public PlayerDepthData(int id, double fw, double fh)
{ PlayerId = id; FrameWidth = fw; FrameHeight = fh; minX = minY = int.MaxValue; maxX = maxY = int.MinValue; }
public void Update(int x, int y, int depth)
{ depthSum += depth; depthCnt++; minX = Math.Min(minX, x); maxX = Math.Max(maxX, x); minY = Math.Min(minY, y); maxY = Math.Max(maxY, y); }
public double Depth => depthSum / (double)depthCnt;
public int PixelWidth => maxX - minX;
public int PixelHeight => maxY - minY;
public string RealWidth => $"{RealWidthInches * MmPerInch:0.0}mm";
public string RealHeight => $"{RealHeightInches * MmPerInch:0.0}mm";
double RealWidthInches => PixelWidth * 2 * Depth * HorTan / FrameWidth / MmPerInch;
double RealHeightInches => PixelHeight * 2 * Depth * VerTan / FrameHeight / MmPerInch;
}
private void CalculatePlayerSize(DepthImageFrame frame, short[] pixelData)
{
var players = new PlayerDepthData[6];
for (int y = 0; y < frame.Height; y++)
for (int x = 0; x < frame.Width; x++)
{
int idx = x + y * frame.Width;
int depth = pixelData[idx] >> DepthImageFrame.PlayerIndexBitmaskWidth;
if (depth == 0) continue;
int pid = (pixelData[idx] & DepthImageFrame.PlayerIndexBitmask) - 1;
if (pid < 0) continue;
if (players[pid] == null) players[pid] = new PlayerDepthData(pid + 1, frame.Width, frame.Height);
players[pid].Update(x, y, depth);
}
PlayerDepthDataItemsControl.ItemsSource = players; // UI 繫結顯示
}

誤差來源: 僅計算 Kinect 可見像素範圍(頭頂/腳底可能被截斷)、人體非平面、深度量化誤差。實測高度誤差約 5~10 cm,寬度誤差較小。可引入骨骼追蹤關節點修正。
深度鏡頭與彩色鏡頭位置不同,像素無法一一對應。SDK 提供 KinectSensor.MapDepthToColorImagePoint 依據深度值計算對應彩色座標。
AllFramesReady 無法保證深度/彩色幀同步。採用 CompositionTarget.Rendering + OpenNextFrame(100ms) 輪詢,盡量取得時間接近的幀對。
private void CompositionTarget_Rendering(object sender, EventArgs e)
{
DiscoverKinect(); // 自動偵測連接/斷線
if (KinectDevice == null) return;
try
{
using (var colorFrame = KinectDevice.ColorStream.OpenNextFrame(100))
using (var depthFrame = KinectDevice.DepthStream.OpenNextFrame(100))
RenderGreenScreen(KinectDevice, colorFrame, depthFrame);
}
catch { /* 忽略拔除裝置例外 */ }
}
private void RenderGreenScreen(KinectSensor sensor, ColorImageFrame colorFrame, DepthImageFrame depthFrame)
{
if (colorFrame == null || depthFrame == null) return;
depthFrame.CopyPixelDataTo(_DepthPixelData);
colorFrame.CopyPixelDataTo(_ColorPixelData);
int colorStride = colorFrame.Width * colorFrame.BytesPerPixel;
byte[] output = new byte[depthFrame.Height * _GreenScreenStride]; // 深度解析度輸出
int outIdx = 0;
for (int dy = 0; dy < depthFrame.Height; dy++)
for (int dx = 0; dx < depthFrame.Width; dx++, outIdx += 4)
{
int dIdx = dx + dy * depthFrame.Width;
int pid = _DepthPixelData[dIdx] & DepthImageFrame.PlayerIndexBitmask;
if (pid == 0) continue; // 背景透明 (Alpha=0)
var cp = sensor.MapDepthToColorImagePoint(depthFrame.Format, dx, dy, _DepthPixelData[dIdx], colorFrame.Format);
int cIdx = cp.X * colorFrame.BytesPerPixel + cp.Y * colorStride;
output[outIdx] = _ColorPixelData[cIdx]; // B
output[outIdx + 1] = _ColorPixelData[cIdx + 1]; // G
output[outIdx + 2] = _ColorPixelData[cIdx + 2]; // R
output[outIdx + 3] = 0xFF; // Alpha 不透明
}
_GreenScreenImage.WritePixels(_GreenScreenRect, output, _GreenScreenStride, 0);
}

本文完整涵蓋深度資料進階應用四大支柱:
PlayerDepthData 類別,即時顯示寬高。MapDepthToColorImagePoint 跨鏡頭映射、輪詢同步、Alpha 合成。深度資料處理系列到此結束。下一階段將進入 骨骼追蹤 核心技術,敬請期待!
作者:yangecnu(博客園)|原文連結:https://www.cnblogs.com/yangecnu/