想像你在畫畫:
Kernel = 畫筆的大小和形狀
├── 3×3 kernel = 小畫筆
├── 5×5 kernel = 中畫筆
└── 7×7 kernel = 大畫筆
膨脹 = 用畫筆「塗白色」的動作
侵蝕 = 用畫筆「擦掉白色」的動作
關鍵理解:
- Kernel 決定影響範圍
- 膨脹/侵蝕 決定操作類型
- 兩者必須配合使用
Kernel(結構元素) 是一個矩陣,定義了每次操作時要檢查的「鄰域範圍」。
# 建立一個 5×5 的 kernel
kernel = np.ones((5, 5), np.uint8)
# 視覺化:
●●●●●
●●●●●
●●☆●● ← ☆ 是中心點
●●●●●
●●●●●
Kernel 的作用:
- 定義檢查範圍的大小(3×3, 5×5, 7×7...)
- 定義檢查範圍的形狀(方形、圓形、十字形)
- 不決定要做什麼操作(膨脹還是侵蝕)
3×3 Kernel 5×5 Kernel 7×7 Kernel
●●● ●●●●● ●●●●●●●
●☆● ●●●●● ●●●●●●●
●●● ●●☆●● ●●●●●●●
●●●●● ●●●☆●●●
●●●●● ●●●●●●●
●●●●●●●
●●●●●●●
影響範圍小 影響範圍中等 影響範圍大
重點:Kernel 只是定義「要看多大範圍」,不決定「要做什麼」。
使用同一個 Kernel 進行膨脹:
kernel = np.ones((5, 5), np.uint8)
result = cv2.dilate(mask, kernel)
操作邏輯:
1. Kernel 在圖像上滑動
2. 如果 Kernel 覆蓋區域內有任何白色像素
3. 則將中心點標記為白色
視覺化範例:
原始圖像: Kernel 掃描: 膨脹結果:
┌─────────┐ ┌─────────┐ ┌─────────┐
│○○○○○○○│ │[●●●●●]○○│ │●●●●●○○│
│○○●●○○○│ → │[●●●●●]○○│ → │●●●●●○○│
│○○●●○○○│ │[●●●●●]○○│ │●●●●●○○│
│○○○○○○○│ └─────────┘ │○○●●○○○│
└─────────┘ └─────────┘
Kernel 檢測到白色 ●
→ 中心點位置變白色
→ 白色區域擴張
關鍵點:
- Kernel 越大,擴張範圍越大
- 膨脹的「敏感度」:只要有一個白點就觸發
使用同一個 Kernel 進行侵蝕:
kernel = np.ones((5, 5), np.uint8)
result = cv2.erode(mask, kernel)
操作邏輯:
1. Kernel 在圖像上滑動
2. 如果 Kernel 覆蓋區域內全部是白色像素
3. 則將中心點標記為白色
4. 否則標記為黑色
視覺化範例:
原始圖像: Kernel 掃描: 侵蝕結果:
┌─────────┐ ┌─────────┐ ┌─────────┐
│●●●●●●●│ │[●●●●●]●●│ │●●●●●●●│
│●●●●●●●│ → │[●●●●●]●●│ → │●○○○○●●│
│●●●●●●●│ │[●●●●●]●●│ │●○○○○●●│
│●●○○○●●│ └─────────┘ │●○○○○●●│
└─────────┘ Kernel 區域全白 ✓ └─────────┘
→ 保留白色
┌─────────┐
│●●●●●[●●]│ 邊緣區域:
│●●●●●[●●]│ Kernel 超出邊界
│●●●●●[●●]│ 或遇到黑色
│●●○○○[●●]│ → 變黑色
└─────────┘
關鍵點:
- Kernel 越大,收縮範圍越大
- 侵蝕的「嚴格度」:必須全部是白色才保留
原始圖像:
┌───────────┐
│○○○○○○○○○│
│○●●●●●○○○│
│○●●●●●○○○│
│○●●●●●○○○│
│○○○○○○○○○│
└───────────┘
3×3 Kernel 膨脹:
kernel_3x3 = np.ones((3, 3), np.uint8)
result = cv2.dilate(mask, kernel_3x3)
結果 (擴張 1 圈):
┌───────────┐
│○●●●●●●○○│
│●●●●●●●○○│
│●●●●●●●○○│
│●●●●●●●○○│
│○●●●●●●○○│
└───────────┘
5×5 Kernel 膨脹:
kernel_5x5 = np.ones((5, 5), np.uint8)
result = cv2.dilate(mask, kernel_5x5)
結果 (擴張 2 圈):
┌───────────┐
│●●●●●●●●○│
│●●●●●●●●○│
│●●●●●●●●○│
│●●●●●●●●○│
│●●●●●●●●○│
└───────────┘
7×7 Kernel 膨脹:
kernel_7x7 = np.ones((7, 7), np.uint8)
result = cv2.dilate(mask, kernel_7x7)
結果 (擴張 3 圈):
┌───────────┐
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
└───────────┘
結論:
- Kernel 越大 → 膨脹範圍越大
- 同樣的操作(膨脹),不同的 Kernel 產生不同的效果
原始圖像:
┌───────────┐
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
│●●●●●●●●●│
└───────────┘
3×3 Kernel 侵蝕:
kernel_3x3 = np.ones((3, 3), np.uint8)
result = cv2.erode(mask, kernel_3x3)
結果 (收縮 1 圈):
┌───────────┐
│○○○○○○○○○│
│○●●●●●●●○│
│○●●●●●●●○│
│○●●●●●●●○│
│○○○○○○○○○│
└───────────┘
5×5 Kernel 侵蝕:
kernel_5x5 = np.ones((5, 5), np.uint8)
result = cv2.erode(mask, kernel_5x5)
結果 (收縮 2 圈):
┌───────────┐
│○○○○○○○○○│
│○○○○○○○○○│
│○○●●●●●○○│
│○○○○○○○○○│
│○○○○○○○○○│
└───────────┘
7×7 Kernel 侵蝕:
kernel_7x7 = np.ones((7, 7), np.uint8)
result = cv2.erode(mask, kernel_7x7)
結果 (收縮 3 圈):
┌───────────┐
│○○○○○○○○○│
│○○○○○○○○○│
│○○○○○○○○○│
│○○○○○○○○○│
│○○○○○○○○○│
└───────────┘
完全消失!
結論:
- Kernel 越大 → 侵蝕範圍越大
- 可能導致物體完全消失
| Kernel 大小 | 膨脹效果 | 侵蝕效果 | 應用場景 |
|---|---|---|---|
| 3×3 | 輕微擴張 | 輕微收縮 | 細微雜訊處理 |
| 5×5 | 中等擴張 ✓ | 中等收縮 ✓ | 一般推薦 |
| 7×7 | 強烈擴張 | 強烈收縮 | 嚴重雜訊或大間隙 |
| 9×9 | 非常強烈擴張 | 非常強烈收縮 | 極端情況(可能變形) |
同一個 Kernel,不同的操作:
kernel = np.ones((5, 5), np.uint8)
↓
┌────┴────┐
↓ ↓
膨脹操作 侵蝕操作
cv2.dilate cv2.erode
↓ ↓
白色擴張 白色收縮
同一個操作,不同的 Kernel:
膨脹操作 cv2.dilate
↓
┌───┴────┬────────┐
↓ ↓ ↓
3×3 5×5 7×7
kernel kernel kernel
↓ ↓ ↓
輕微擴張 中等擴張 強烈擴張
import cv2
import numpy as np
from google.colab.patches import cv2_imshow
# 建立測試圖像
img = np.zeros((200, 200), dtype=np.uint8)
cv2.circle(img, (100, 100), 40, 255, -1)
print("原始圖像:")
cv2_imshow(img)
# 測試不同大小的 Kernel
kernel_sizes = [3, 5, 7, 9]
print("\n=== 膨脹操作 ===")
for size in kernel_sizes:
kernel = np.ones((size, size), np.uint8)
dilated = cv2.dilate(img, kernel)
print(f"\nKernel {size}×{size} 膨脹結果:")
cv2_imshow(dilated)
print("\n=== 侵蝕操作 ===")
for size in kernel_sizes:
kernel = np.ones((size, size), np.uint8)
eroded = cv2.erode(img, kernel)
print(f"\nKernel {size}×{size} 侵蝕結果:")
cv2_imshow(eroded)
import cv2
import numpy as np
from google.colab.patches import cv2_imshow
# 建立測試圖像 (有雜訊)
img = np.zeros((200, 200), dtype=np.uint8)
cv2.circle(img, (100, 100), 40, 255, -1) # 主要物體
cv2.circle(img, (95, 95), 5, 0, -1) # 內部黑洞
cv2.circle(img, (150, 80), 8, 255, -1) # 外部雜點
print("原始圖像 (有問題):")
cv2_imshow(img)
# 建立一個 Kernel
kernel = np.ones((5, 5), np.uint8)
print("\n使用的 Kernel: 5×5")
# 1. 用這個 Kernel 做膨脹
dilated = cv2.dilate(img, kernel)
print("\n膨脹操作 (填補黑洞):")
cv2_imshow(dilated)
# 2. 用這個 Kernel 做侵蝕
eroded = cv2.erode(img, kernel)
print("\n侵蝕操作 (去除雜點):")
cv2_imshow(eroded)
# 3. 用這個 Kernel 做閉運算 (先膨脹後侵蝕)
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
print("\n閉運算 (填補黑洞但保持大小):")
cv2_imshow(closed)
# 4. 用這個 Kernel 做開運算 (先侵蝕後膨脹)
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
print("\n開運算 (去除雜點但保持大小):")
cv2_imshow(opened)
import cv2
import numpy as np
def process_traffic_light_mask(mask):
"""
處理紅綠燈偵測後的遮罩
參數:
mask: 顏色分割後的二值化遮罩
返回:
處理後的乾淨遮罩
"""
# 步驟 1: 建立 Kernel
# 為什麼選 5×5?
# - 3×3 太小,無法處理明顯的雜訊
# - 7×7 太大,可能讓燈號變形
# - 5×5 平衡效果與副作用 ✓
kernel = np.ones((5, 5), np.uint8)
# 步驟 2: 閉運算 (填補燈內黑洞)
# 操作: 先膨脹後侵蝕
# Kernel 作用: 定義填補範圍 (5×5 鄰域)
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)
# 步驟 3: 開運算 (去除背景雜點)
# 操作: 先侵蝕後膨脹
# Kernel 作用: 定義去除範圍 (小於 5×5 的雜點會被去除)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
return mask
# 使用範例
# red_mask = cv2.inRange(hsv, lower_red, upper_red)
# clean_red_mask = process_traffic_light_mask(red_mask)
Kernel 的角色:
├── 定義檢查範圍的大小
├── 定義檢查範圍的形狀
└── 影響操作的強度
Kernel 不決定:
└── 要做什麼操作 (膨脹/侵蝕由函數決定)
膨脹 (Dilation):
├── 使用 Kernel 掃描
├── 檢測規則: 有白色就觸發
└── 效果: 白色區域擴張
侵蝕 (Erosion):
├── 使用 Kernel 掃描
├── 檢測規則: 全白色才保留
└── 效果: 白色區域收縮
cv2.dilate(img, kernel)
↑ ↑
| └─ Kernel: 定義範圍
└─ 函數: 定義操作
關係:
- Kernel 是膨脹/侵蝕操作的「必要參數」
- 沒有 Kernel,無法執行膨脹/侵蝕
- 同一個 Kernel 可用於膨脹或侵蝕
- Kernel 大小直接影響操作強度
🔧 Kernel = 工具 (畫筆大小)
- 3×3 = 小筆刷
- 5×5 = 中筆刷
- 7×7 = 大筆刷
🎨 膨脹 = 動作 (塗白色)
- 有白就塗
- 越塗越大
🧹 侵蝕 = 動作 (擦白色)
- 全白才留
- 越擦越小
問題: 我的圖像有什麼問題?
↓
┌───┴────────────────────────────┐
↓ ↓
內部有小黑洞 外部有小雜點
(使用膨脹或閉運算) (使用侵蝕或開運算)
↓ ↓
問: 黑洞有多大? 問: 雜點有多大?
↓ ↓
├─ 很小 (1-2 像素) ├─ 很小 (1-2 像素)
│ → 使用 3×3 kernel │ → 使用 3×3 kernel
├─ 中等 (3-5 像素) ✓ ├─ 中等 (3-5 像素) ✓
│ → 使用 5×5 kernel │ → 使用 5×5 kernel
└─ 很大 (>5 像素) └─ 很大 (>5 像素)
→ 使用 7×7 或更大 kernel → 使用 7×7 或更大 kernel
A: 平衡考量:
- 紅綠燈圓形直徑通常 > 20 像素
- 內部反光黑洞通常 3-5 像素
- 背景雜點通常 2-4 像素
- 5×5 Kernel 剛好能處理這些問題,又不會過度變形
A: 可以!根據問題特性調整:
# 範例: 黑洞大,雜點小
kernel_large = np.ones((7, 7), np.uint8) # 填補大黑洞
kernel_small = np.ones((3, 3), np.uint8) # 去除小雜點
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel_large)
mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel_small)
A:
- 1 = 這個位置參與運算
- 0 = 這個位置忽略
範例:
# 十字形 Kernel (只檢查上下左右)
kernel_cross = np.array([
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0],
[1, 1, 1, 1, 1],
[0, 0, 1, 0, 0],
[0, 0, 1, 0, 0]
], dtype=np.uint8)
A: 幾乎會,但不完全:
- 如果用同樣大小的 Kernel → 大小接近原始
- 但細節部分可能略有不同 (邊角會變圓滑)
- 這正是閉運算和開運算的特性
原圖 膨脹 侵蝕 結果
■■■ ■■■■ ■■■ ■■■
■□■ → ■■■■ → ■■■ → ■■■ (黑洞消失)
■■■ ■■■■ ■■■ ■■■
希望這個說明讓您更清楚 Kernel 與膨脹侵蝕的關係!
核心概念:
- Kernel = 工具 (定義範圍)
- 膨脹/侵蝕 = 動作 (定義操作)
- 兩者配合使用才能產生效果