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

C# unsafe 指標操作圖像處理:效能提升實戰與中值濾波實作

     編輯:關於C#

為什麼用指標處理圖像?

Bitmap.GetPixel/SetPixel 每次呼叫都有邊界檢查、鎖定/解鎖、格式轉換等開銷,處理大圖極慢。unsafe 程式碼直接操作記憶體,繞過 CLR 托管檢查,效能可提升 10-100 倍。

核心步驟:LockBits 取得記憶體指標


Bitmap bmp = new Bitmap("image.jpg");

// 1. 鎖定位圖記憶體區域
BitmapData data = bmp.LockBits(
    new Rectangle(0, 0, bmp.Width, bmp.Height),
    ImageLockMode.ReadWrite,
    PixelFormat.Format24bppRgb); // 必須指定像素格式

try
{
    unsafe
    {
        // 2. 取得首行掃描線指標
        byte* scan0 = (byte*)data.Scan0.ToPointer();
        int stride = data.Stride; // 一行實際位元組數 (含記憶體對齊填充)
        int width = bmp.Width;
        int height = bmp.Height;

        // 3. 雙層迴圈逐像素處理
        for (int y = 0; y < height; y++)
        {
            byte* row = scan0 + y * stride; // 當前行起始位址
            for (int x = 0; x < width; x++)
            {
                byte* pixel = row + x * 3; // Format24bppRgb: BGR 順序,每像素 3 位元組
                
                // 範例:灰階轉換 (加權平均法)
                byte b = pixel[0];
                byte g = pixel[1];
                byte r = pixel[2];
                byte gray = (byte)((r * 0.299 + g * 0.587 + b * 0.114));
                
                pixel[0] = pixel[1] = pixel[2] = gray;
            }
        }
    }
}
finally
{
    // 4. 必須解鎖
    bmp.UnlockBits(data);
}

關鍵技術細節

概念說明
Stride (跨距)一行記憶體位元組數,通常 ≥ Width × BytesPerPixel,因 4 位元組對齊補齊。不可假設 = Width × 3。
Scan0影像資料起始位址 (第一行第一像素)。
像素格式Format24bppRgb 記憶體順序為 B G R (藍綠紅),非 RGB。
指標運算byte* p = scan0 + y * stride + x * 3 定位像素;p[0],p[1],p[2] 存取 BGR。
專案設定專案屬性 → Build → 勾選 Allow unsafe code。

實戰:3x3 中值濾波去雜訊


public void MedianFilter3x3(BitmapData data)
{
    unsafe
    {
        byte* scan0 = (byte*)data.Scan0.ToPointer();
        int stride = data.Stride;
        int width = data.Width;
        int height = data.Height;
        byte[] window = new byte[9]; // 3x3 窗口

        // 略過邊緣 1 像素
        for (int y = 1; y < height - 1; y++)
        {
            byte* row = scan0 + y * stride;
            for (int x = 1; x < width - 1; x++)
            {
                byte* center = row + x * 3; // 當前像素 B 分量位址

                // 收集 3x3 窗口的亮度值 (以綠通道為例,或自行計算灰階)
                int idx = 0;
                for (int dy = -1; dy <= 1; dy++)
                {
                    byte* r = scan0 + (y + dy) * stride + (x - 1) * 3;
                    for (int dx = -1; dx <= 1; dx++)
                    {
                        window[idx++] = r[dx * 3 + 1]; // 取 G 通道
                    }
                }

                // 簡易排序取中值 (9 元素插入排序即可)
                for (int i = 1; i < 9; i++)
                {
                    byte val = window[i];
                    int j = i - 1;
                    while (j >= 0 && window[j] > val)
                        window[j + 1] = window[j--];
                    window[j + 1] = val;
                }
                byte median = window[4]; // 中位數

                // 寫回三通道
                center[0] = center[1] = center[2] = median;
            }
        }
    }
}

效能比較參考 (1000x1000 圖片灰階)

方法耗時相對速度
GetPixel/SetPixel~2500 ms1x (基準)
LockBits + unsafe 指標~15 ms~160x
Parallel.For + 指標~5 ms (8 核心)~500x

安全性與最佳實踐

  1. 最小化 unsafe 範圍:僅將核心運算包在 unsafe { ... } 區塊。
  2. 檢查像素格式:執行時驗證 bmp.PixelFormat == PixelFormat.Format24bppRgb,否則轉換。
  3. 邊界處理:濾波器略過邊緣或採用鏡像/複製邊緣策略。
  4. 記憶體對齊:永遠使用 stride 計算行位移,不要假設緊湊排列。
  5. 替代方案:.NET 6+ 可考慮 System.Drawing.Common 或 ImageSharp、OpenCvSharp 等專業影像庫。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved