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

Kinect for Windows SDK 進階開發指南(上):影像處理工具與近距離偵測技術

     編輯:關於.NET

前言:從入門邁向進階

前幾篇文章已系統介紹了 Kinect for Windows SDK 的基礎開發知識,涵蓋影像資料流、景深攝影機、骨骼追蹤、麥克風陣列與語音辨識等核心功能。然而,掌握這些基礎後,若要開發出具備優質使用者體驗的應用程式,往往仍感力有未逮。Kinect SDK 提供的功能與其他類庫大同小異,真正的應用潛力在於整合其他技術。本文將介紹協助處理 Kinect 感測器資料的第三方類庫與進階技術,幫助開發者突破基礎框架的限制。

一、影像處理輔助類庫:解決類型轉換難題

在 .NET 架構下,System.Windows.Media (WPF) 與 System.Drawing (WinForms/GDI+) 兩大圖形命名空間擁有各自獨立的形狀與顏色類別,彼此不相容。引入 Kinect 後,情況更複雜:Kinect 專有的 ImageFrame 必須轉為 WPF 的 ImageSource 才能顯示,而第三方影像處理庫 (如 Emgu CV) 又只認識 System.Drawing 類型。這導致開發者必須在多種類型間繞彎轉換:

Kinect (Microsoft.Kinect) → System.Drawing → Emgu CV → System.Drawing → System.Windows.Media (WPF)

1.1 Coding4Fun Kinect Toolkit:官方推薦的轉換橋樑

Coding4Fun Kinect Toolkit 專為簡化此類轉換而設計,包含三個獨立 DLL:

  • Coding4Fun.Kinect.Wpf.dll:提供擴充方法,在 Microsoft.Kinect 與 System.Windows.Media 間雙向轉換。
  • Coding4Fun.Kinect.WinForm.dll:提供擴充方法,在 Microsoft.Kinect 與 System.Drawing 間轉換。

可惜它缺少 System.Drawing 與 System.Windows.Media 直接互轉的方法,且部分深度影像處理邏輯被封裝在簡單轉換方法中,有時行為不如預期。但它有兩大優點:開源可參考原始碼、皆為擴充方法,方便開發者自行擴充。

1.2 自建擴充方法庫:掌控核心轉換邏輯

為了獲得更穩定、符合預期的轉換行為,建議建立專屬的擴充方法類別。以下示範核心轉換邏輯:

步驟 1:建立 WPF 專案並引用必要組件

  • 專案類型:WPF Application
  • 參考:Microsoft.Kinect.dll、System.Drawing.dll
  • 命名空間別名:using Media = System.Windows.Media; 避免類別名稱衝突

步驟 2:核心擴充方法實作 (ImageExtensions.cs)

namespace ImageManipulationExtensionMethods {
    public static class ImageExtensions {
        // byte[] → System.Drawing.Bitmap
        public static Bitmap ToBitmap(this byte[] data, int width, int height, PixelFormat format) {
            var bitmap = new Bitmap(width, height, format);
            var bmpData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format);
            Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
            bitmap.UnlockBits(bmpData);
            return bitmap;
        }

        // short[] (深度資料) → Bitmap
        public static Bitmap ToBitmap(this short[] data, int width, int height, PixelFormat format) {
            var bitmap = new Bitmap(width, height, format);
            var bmpData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, format);
            Marshal.Copy(data, 0, bmpData.Scan0, data.Length);
            bitmap.UnlockBits(bmpData);
            return bitmap;
        }

        // byte[] → WPF BitmapSource
        public static Media.Imaging.BitmapSource ToBitmapSource(this byte[] data, Media.PixelFormat format, int width, int height) {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96, format, null, data, width * format.BitsPerPixel / 8);
        }

        // short[] → WPF BitmapSource
        public static Media.Imaging.BitmapSource ToBitmapSource(this short[] data, Media.PixelFormat format, int width, int height) {
            return Media.Imaging.BitmapSource.Create(width, height, 96, 96, format, null, data, width * format.BitsPerPixel / 8);
        }

        // ColorImageFrame → BitmapSource (Bgr32)
        public static Media.Imaging.BitmapSource ToBitmapSource(this ColorImageFrame frame) {
            if (frame == null || frame.PixelDataLength == 0) return null;
            var data = new byte[frame.PixelDataLength];
            frame.CopyPixelDataTo(data);
            return data.ToBitmapSource(Media.PixelFormats.Bgr32, frame.Width, frame.Height);
        }

        // DepthImageFrame → BitmapSource (Bgr555)
        public static Media.Imaging.BitmapSource ToBitmapSource(this DepthImageFrame frame) {
            if (frame == null || frame.PixelDataLength == 0) return null;
            var data = new short[frame.PixelDataLength];
            frame.CopyPixelDataTo(data);
            return data.ToBitmapSource(Media.PixelFormats.Bgr555, frame.Width, frame.Height);
        }

        // Bitmap ↔ BitmapSource 互轉 (含記憶體釋放)
        [DllImport("gdi32")] private static extern int DeleteObject(IntPtr o);
        public static Media.Imaging.BitmapSource ToBitmapSource(this Bitmap bitmap) {
            if (bitmap == null) return null;
            IntPtr hBitmap = bitmap.GetHbitmap();
            var source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(hBitmap, IntPtr.Zero, Int32Rect.Empty, Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
            DeleteObject(hBitmap); // 防止記憶體洩漏
            return source;
        }
        public static Bitmap ToBitmap(this Media.Imaging.BitmapSource source) {
            using (var ms = new MemoryStream()) {
                var encoder = new Media.Imaging.PngBitmapEncoder();
                encoder.Frames.Add(Media.Imaging.BitmapFrame.Create(source));
                encoder.Save(ms);
                return new Bitmap(ms);
            }
        }
    }
}

步驟 3:在事件處理中一鍵呼叫

void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) {
    this.depthImage.Source = e.OpenDepthImageFrame().ToBitmapSource();
}
void ColorFrameReady(object sender, ColorImageFrameReadyEventArgs e) {
    this.rgbImage.Source = e.OpenColorImageFrame().ToBitmapSource();
}

自建類庫的好處在於:邏輯透明、參數可控、易於除錯,避免第三方封裝的「黑盒子」行為差異。

二、近距離偵測:讓應用程式具備環境感知能力

近距離偵測不只是互動的觸發器,更能讓應用程式「感知環境」。例如:數位看板在無人時播放廣告,有人靠近切換互動模式;安防系統偵測移動物體自動錄影。Kinect 的深度與骨骼資料是實現此功能的關鍵。

2.1 基於骨骼追蹤的偵測(適合人體互動場景)

利用 SkeletonFrameReady 事件搭配 DispatcherTimer 判斷追蹤中斷:

private void Pulse(object sender, SkeletonFrameReadyEventArgs e) {
    using (var frame = e.OpenSkeletonFrame()) {
        if (frame == null) return;
        var skeletons = new Skeleton[frame.SkeletonArrayLength];
        frame.CopySkeletonDataTo(skeletons);
        foreach (var s in skeletons) {
            if (s.TrackingState == SkeletonTrackingState.Tracked) {
                OnDetection(); // 觸發偵測到事件
                _lastTrackTime = DateTime.Now;
                if (!_timer.IsEnabled) _timer.Start();
                break;
            }
        }
    }
}
void _timer_Tick(object sender, EventArgs e) {
    if (DateTime.Now.Subtract(_lastTrackTime).TotalMilliseconds > _threshold) {
        _timer.Stop();
        OnDetectionStopped(); // 觸發離開事件
    }
}

優點:精準鎖定人體;缺點:非人體物體 (寵物、機器人) 會被忽略,且需額外計時器處理中斷邏輯。

2.2 基於深度資料的偵測(通用物體偵測、速度快)

直接分析 DepthImageFrame 的像素深度值,設定距離閾值區間 (如 1.0m~1.2m):

void DepthFrameReady(object sender, DepthImageFrameReadyEventArgs e) {
    bool inRange = false;
    using (var frame = e.OpenDepthImageFrame()) {
        if (frame == null) return;
        var bits = new short[frame.PixelDataLength];
        frame.CopyPixelDataTo(bits);
        // 深度值儲存於高位元,需右移 PlayerIndexBitmaskWidth
        for (int i = 0; i < bits.Length; i += frame.BytesPerPixel) {
            int depth = bits[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
            if (depth > 1000 && depth < 1200) { // 1000~1200 毫米
                inRange = true; OnDetection(); break;
            }
        }
    }
    if (!inRange) OnDetectionStopped();
}

優點:資料連續無需計時器、可偵測非人體物體、演算法簡單速度快;缺點:靜止物體也會觸發、需精確設定深度閾值。

2.3 綜合方案:深度 + 玩家索引 (Player Index)

結合骨骼追蹤的「人體辨識」與深度資料的「距離篩選」,僅當 player > 0 (偵測到玩家) 且深度在範圍內才觸發:

var depth = bits[i] >> DepthImageFrame.PlayerIndexBitmaskWidth;
var player = bits[i] & DepthImageFrame.PlayerIndexBitmask;
if (player > 0 && depth > 1700 && depth < 2000) { ... }

此法最適合公開展示場域:劃定互動區域,只有真人進入特定距離才觸發,兼具精準度與彈性。

三、結語與下篇預告

本文介紹了進階開發的兩大基石:影像類型轉換的自主掌控與近距離偵測的多種策略。下一篇將深入探討:

  • 動作偵測:如何判斷「移動」而非單純「存在」
  • 影像擷取與儲存:錄製偵測觸發前後的畫面
  • 臉部辨識與全像投影 (Hologram) 基礎
  • 其他必備類庫:Unity 3D、FAAST、Microsoft Robotics Developer Studio 等

敬請期待《Kinect for Windows SDK 進階開發指南(下)》!

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