影像模糊 Kernel (Image Blur)

重點總覽 (Overview)

項目 內容
任務 每個輸出像素 = 輸入影像中以該像素為中心的 N×N patch 平均值
數學本質 簡化版 convolution(box filter / 等權重平均,無 Gaussian 加權)
Thread→Data 映射 一個 thread 算一個 output pixel(與 colorToGrayscaleConversion 相同)
patch 半徑 BLUR_SIZE(每一邊的像素數);一維寬度 = 2*BLUR_SIZE+1
核心結構 巢狀 for 迴圈掃 patch + 內層 if 過濾出界像素
邊界處理 pixels 計數實際累加的合法像素,再除以它求平均
累加像素數 (3×3) 內部 9、邊 6、角 4
新觀念 thread 需做較複雜運算讀取鄰近多個元素(非一對一獨立運算)
Important

這是本書第一個 thread 需要讀取一片鄰域 (neighborhood) 而非單一元素的 kernel,是邁向 ConvolutionStencil pattern 的前奏。


影像模糊的概念 (Image Blurring Concept)

模式 權重 章節
本章 box blur 全部像素等權重(平均) Ch.3
Gaussian / weighted blur 依與中心距離加權 Ch.7 Convolution

BLUR_SIZE 與 Patch 幾何 (Radius & Patch Geometry)

BLUR_SIZE patch 尺寸 總像素數
1 3×3 9
2 5×5 25
3 7×7 49

patch 以 (row, col) 為中心,列範圍 row-BLUR_SIZE … row+BLUR_SIZE,行範圍 col-BLUR_SIZE … col+BLUR_SIZE

            col-1   col   col+1            (BLUR_SIZE = 1 → 3x3 patch)
          +-------+-------+-------+
   row-1  | r-1   | r-1   | r-1   |
          | c-1   | c     | c+1   |
          +-------+-------+-------+
   row    | r     | (row, | r     |   <-- 中心 = 要計算的 output pixel
          | c-1   |  col) | c+1   |
          +-------+-------+-------+
   row+1  | r+1   | r+1   | r+1   |
          | c-1   | c     | c+1   |
          +-------+-------+-------+

  output[row][col] = average(以上所有「合法」像素)
Tip

對 output pixel (25,50) 且 BLUR_SIZE=1:外層迴圈 curRow 走 24→25→26,內層 curCol 走 49→50→51,恰好掃過 9 個像素。


Blur Kernel 程式碼 (The blurKernel)

__global__ void blurKernel(unsigned char *in, unsigned char *out, int w, int h) {
    int col = blockIdx.x * blockDim.x + threadIdx.x;   // 行索引 (line 03)
    int row = blockIdx.y * blockDim.y + threadIdx.y;   // 列索引 (line 04)

    if (col < w && row < h) {                          // 外層邊界檢查 (line 05)
        int pixVal = 0;
        int pixels = 0;

        // 掃過 (2*BLUR_SIZE+1) x (2*BLUR_SIZE+1) 的 patch
        for (int blurRow = -BLUR_SIZE; blurRow < BLUR_SIZE+1; ++blurRow) {     // line 10
            for (int blurCol = -BLUR_SIZE; blurCol < BLUR_SIZE+1; ++blurCol) { // line 11
                int curRow = row + blurRow;
                int curCol = col + blurCol;

                // 內層邊界檢查:patch 可能超出影像範圍 (line 15)
                if (curRow >= 0 && curRow < h && curCol >= 0 && curCol < w) {
                    pixVal += in[curRow * w + curCol];  // 累加合法像素 (line 16)
                    ++pixels;                            // 計數 (line 17)
                }
            }
        }
        out[row * w + col] = (unsigned char)(pixVal / pixels);  // 寫回平均 (line 22)
    }
}

兩層 if 的分工(這是本 kernel 的關鍵):

位置 條件 防止的事
外層 if (line 05) col < w && row < h thread 對應到影像外(grid 多生的 thread)→ 不計算任何 output pixel
內層 if (line 15) curRow/curCol 都在 [0, h)/[0, w) patch 超出邊界的鄰居像素 → 跳過累加、不計數
Warning

平均一定要除以 pixels(實際累加數),不可直接除以 (2*BLUR_SIZE+1)^2。否則角落/邊緣像素會把不存在的「黑邊」算進去,導致邊緣偏暗。


邊界條件處理 (Boundary Conditions)

當 output pixel 靠近影像邊緣,patch 會延伸到影像外,內層 if 把這些不存在的像素直接略過,並讓 pixels 只計入合法像素。

3×3 (BLUR_SIZE=1) 三種情形:

  角落 (corner)            邊緣 (edge)              內部 (interior)
  patch 中心=(0,0)         patch 中心=(0,c)         patch 中心=(r,c)

  X X X                    X X X                    o o o
  X o o   合法=4           o o o   合法=6           o o o   合法=9
  X o o                    o o o                    o o o

  X = 出界(被 if 跳過)      o = 合法像素累加
位置 合法像素數 (3×3) 一般式
內部 (interior) 9 (2B+1)^2,B=BLUR_SIZE
邊 (edge, 非角) 6 (2B+1)*(B+1) 之類,依鄰近邊數遞減
角 (corner) 4 (B+1)^2
Warning

角落範例:output (0,0) 的 9 次迭代中,curRow/curCol 會出現 -1,內層 ifcurRow >= 0 && curCol >= 0 攔截,最終只累加 4 個合法像素、pixels==4

Tip

多數 thread 的 patch 完全落在影像內(走 fast path),只有邊與角的少數 thread 走進「跳過」分支 → 形成 control divergence,但比例極小。對照 halo cells 的進階處理方式。


與其他 Kernel 的對照 (Comparison)

特性 colorToGrayscaleConversion blurKernel
Thread→Data 映射 1 thread → 1 output pixel 相同
每 thread 讀取量 單一像素 (r,g,b) 整片 N×N patch
運算複雜度 O(1) 固定 O((2·BLUR_SIZE+1)²)
邊界 if 只有外層 (col/row 範圍) 外層 + 內層 patch 邊界
是否需 pixels 計數 (邊角才能正確平均)

考試/面試重點 (Exam / Test Patterns)

情境 / 關鍵字 答案 / 技巧
BLUR_SIZE 代表什麼? patch 半徑(每邊像素數);patch 一維寬度 = 2*BLUR_SIZE+1
7×7 patch 的 BLUR_SIZE 3(因 2*3+1 = 7
為何需要兩個 if? 外層擋「thread 對應影像外」;內層擋「patch 鄰居出界」
為何用 pixels 而不用固定 patch 面積除? 邊/角 patch 部分出界,合法像素數不同;除以實際 pixels 才正確
3×3 時,角落像素累加幾個? 4;邊緣 6;內部 9
記憶體索引怎麼寫? row-major:in[curRow*w + curCol]out[row*w + col]
與 grayscale kernel 的差別? thread 需讀一片鄰域並做較複雜運算(非一對一獨立)
這屬於哪種 parallel pattern? 簡化的 convolution(等權重 box filter)
邊界 thread 造成什麼效能議題? control divergence(少數 thread 走 skip 分支)
Gaussian blur 與本章差別? Gaussian 依距離加權;本章等權重平均