卷積與影像濾波

影像處理入門1 分鐘2026年10月4日▶ 在 Colab 開啟

先備知識: 影像就是陣列

你將學到

  • 什麼是卷積核,以及它如何在影像上滑動
  • 為什麼方框模糊和高斯模糊看起來不一樣
  • Sobel 卷積核如何把亮度變化轉成邊緣

直覺

每一個輸出像素,都是它周圍鄰居的加權總和。這些權重排成一個小網格,稱為卷積核(kernel)。只要改變這九個數字,同一個運算就能做到模糊、銳化或強調邊緣。

數學

對輸入影像 II 與大小為 (2k+1)×(2k+1)(2k+1) \times (2k+1) 的卷積核 KK,輸出 GG 為

G(x,y)=∑i=−kk∑j=−kkK(i,j) I(x+i,  y+j)G(x, y) = \sum_{i=-k}^{k} \sum_{j=-k}^{k} K(i, j)\, I(x + i,\; y + j)

嚴格來說這是互相關(cross-correlation)。真正的卷積會先把卷積核翻轉成 K(−i,−j)K(-i, -j);對於模糊這類對稱的卷積核,兩者沒有差別。

模糊卷積核的權重總和為 11,所以平坦區域的亮度不變;邊緣卷積核的權重總和為 00,平坦區域會變成 00,只留下變化的地方。

程式碼

用 NumPy 從頭實作:

import numpy as np

def conv2d(img, K):
    """img: (H, W) float32,K: (3, 3),邊界以複製邊緣像素處理。"""
    H, W = img.shape
    p = np.pad(img, 1, mode="edge")
    out = np.zeros_like(img)
    for i in range(3):
        for j in range(3):
            out += K[i, j] * p[i:i + H, j:j + W]
    return out

用 OpenCV 做同樣的事:

import cv2
import numpy as np

img = cv2.imread("cameraman.png", cv2.IMREAD_GRAYSCALE).astype(np.float32) / 255
sobel_x = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]], np.float32)
edges = cv2.filter2D(img, -1, sobel_x)

動手試試

選一個預設,再修改個別格子。試著讓 Sobel 卷積核改成偵測水平邊緣。

卷積核實驗室修改任一格或選擇預設,輸出會即時更新。
輸入 I
輸出 G = K ⋆ I

結果與常見陷阱

拖曳滑桿,比較輸入影像與 5×55 \times 5 高斯模糊(σ=1.4\sigma = 1.4)的結果。右下角的細條紋最先消失,因為模糊是一種低通濾波器。

輸入高斯 σ=1.4

常見錯誤:

  • 整數溢位。 用銳化卷積核處理 uint8 影像時,負值會被截成 0。請改用 float32 計算。
  • 邊界處理。 補零會讓模糊後的影像邊緣變暗。請使用 mode="edge" 或 cv2.BORDER_REFLECT。

練習

  1. 設計一個只偵測水平邊緣的卷積核。
  2. 為什麼模糊卷積核的權重總和必須是 1?如果總和是 2 會發生什麼事?
  3. 證明連續套用兩次 3×33 \times 3 方框模糊,等同於一個 5×55 \times 5 卷積核。這個卷積核長什麼樣子?