首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Kinect for Windows SDK 開發入門(五):景深資料處理(下)─ 直方圖分析、玩家索引位、實體尺寸測量與綠幕合成

     編輯:關於.NET

前言

承接上一篇深度資料基礎操作,本文進階探討四大實用主題:

  1. 深度直方圖:統計像素深度分佈,輔助動態閾值決定與前背景分離。
  2. 玩家索引位:從深度像素低 3 位擷取玩家 ID,實現像素級人體遮罩。
  3. 實體尺寸測量:結合視場角與三角函數,將像素寬高換算為公釐真實尺寸。
  4. 綠幕合成:利用 MapDepthToColorImagePoint 將深度遮罩映射至彩色影像,達成即時抠圖合成。

一、 簡單深度影像處理與直方圖

1.1 閾值預處理

原始深度範圍 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) { /* 有效像素 */ }

1.2 直方圖繪製與應用

統計各深度值出現頻率,繪製水平長條圖:

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);
        }
    }
}

直方圖範例 1 直方圖範例 2(手持靠墊)

  • 圖中明顯雙峰:前方小峰為人體(~2 公尺),後方大峰為牆面(~3 公尺)。
  • 手持物體會在人體峰旁產生新峰值,可輔助物體偵測。
  • 應用:依直方圖波峰谷底動態設定前背景分離閾值,或作為 K-means 分群初始中心。

二、 玩家索引位與像素級人體遮罩

2.1 位元結構

每像素 16 bits:[15:3] = 深度值 (mm),[2:0] = 玩家索引 (0~6)。SDK 常數:

  • DepthImageFrame.PlayerIndexBitmaskWidth = 3
  • DepthImageFrame.PlayerIndexBitmask = 7 (0x07)
int playerIndex = pixelData[i] & DepthImageFrame.PlayerIndexBitmask; // 取低 3 位
// playerIndex == 0 → 背景;1~6 → 對應玩家

注意: 必須同時啟用 SkeletonStream,深度幀才會填入玩家索引。且索引值不固定對應特定使用者(進出視野會重新分配),切勿硬編碼假設「玩家 1 = 索引 1」。

2.2 二值化人體遮罩範例

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);
}

玩家索引遮罩效果

2.3 進階應用方向

  • 依玩家索引著色(玩家 1 紅、玩家 2 藍…)。
  • 對單一玩家像素做灰階拉伸、直方圖均衡化增強對比。
  • 結合 Blob 偵測、輪廓追蹤提取人體輪廓多邊形。

三、 實體尺寸測量:從像素到公釐

3.1 幾何原理

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

視場角三角幾何示意

W = 2 * D * tan(57°/2),單位像素寬度對應實體寬度 = W / FrameWidth。同理高度用 43° 計算。

3.2 完整實作:PlayerDepthData 類別

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;
}

3.3 深度幀處理整合

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 繫結顯示
}

測量結果 1 測量結果 2 測量結果 3 測量結果 4 測量結果 5 測量結果 6

誤差來源: 僅計算 Kinect 可見像素範圍(頭頂/腳底可能被截斷)、人體非平面、深度量化誤差。實測高度誤差約 5~10 cm,寬度誤差較小。可引入骨骼追蹤關節點修正。

四、 深度遮罩與彩色影像合成:即時綠幕效果

4.1 核心難點:雙鏡頭視差

深度鏡頭與彩色鏡頭位置不同,像素無法一一對應。SDK 提供 KinectSensor.MapDepthToColorImagePoint 依據深度值計算對應彩色座標。

4.2 輪詢模式同步幀

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 { /* 忽略拔除裝置例外 */ }
}

4.3 合成演算法

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);
}
  • 輸出解析度跟隨深度幀(640×480),彩色幀設為雙倍(1280×960)映射品質最佳。
  • Alpha 通道控制透明度:玩家像素 0xFF,背景 0x00。
  • 鋸齒/雜訊可透過多幀平均或邊緣平滑改善。

綠幕合成效果

五、 結語

本文完整涵蓋深度資料進階應用四大支柱:

  1. 直方圖統計:動態閾值、前背景分離依據。
  2. 玩家索引位:像素級人體遮罩、多玩家區隔(注意索引不固定)。
  3. 實體測量:視場角三角函數換算,封裝 PlayerDepthData 類別,即時顯示寬高。
  4. 綠幕合成:MapDepthToColorImagePoint 跨鏡頭映射、輪詢同步、Alpha 合成。

深度資料處理系列到此結束。下一階段將進入 骨骼追蹤 核心技術,敬請期待!

作者:yangecnu(博客園)|原文連結:https://www.cnblogs.com/yangecnu/

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved