1.2 色彩空間與轉換#
概念說明#
同一種顏色可以用不同的「座標系統」描述,這些系統稱為色彩空間 (color space)。最常見的是 RGB:以紅、綠、藍三個亮度值疊加成色,符合顯示器與相機感光原理。但 RGB 也不是唯一的選擇,不同任務適合不同空間。
灰階 (Grayscale):只保留亮度。由於人眼對綠光最敏感、紅光次之、藍光最弱,世界統一採用的加權公式為:
Y = 0.299 R + 0.587 G + 0.114 B
注意權重不是 1/3,直接取三通道平均會讓影像看起來「比正確的灰階更暗且對比錯誤」。
HSV (色相、飽和度、明度):把顏色解讀為「什麼顏色(H)、多純(S)、多亮(V)」。做顏色篩選(例如「找出畫面中所有的紅色」)時,在 HSV 的色相通道上設定範圍遠比在 RGB 上設定三個Ranges直覺。
YCbCr (Y 是亮度、Cb/Cr 是色差):人眼對亮度變化的敏感度遠高於色彩變化,因此 JPEG 壓縮會把影像轉到 YCbCr,對色差通道做較粗的取樣(色度二次取樣)。
CIELAB:設計上模擬人眼感知,兩個顏色在 Lab 空間的幾何距離接近人感覺到「差多少」。這使得以 Lab 上「距離近」來分群顏色,比較符合直覺。
轉換演算法步驟#
以 RGB → 灰階為例:
- 讀入三通道陣列,確認資料型別為浮點數(避免整數截斷)。
- 依加權公式 R×0.299 + G×0.587 + B×0.114 計算。
- 取四捨五入(或直接截斷)轉回 uint8。
- 輸出單通道影像。
RGB ↔ HSV 的轉換涉及 max/min 運算與條件分支,公式較長,實務上多使用函式庫。
Python 程式碼範例#
import numpy as np
from PIL import Image
# 產生一張漸層彩色影像:x 方向由黑到白,y 方向偏綠
H, W = 100, 200
r = np.tile(np.linspace(0, 255, W), (H, 1))
g = np.tile(np.linspace(30, 255, W), (H, 1))
b = np.full((H, W), 40)
img = np.dstack([r, g, b]).astype(np.uint8)
Image.fromarray(img).save("gradient.png")
# 手作 RGB -> 灰階(正確加權)
gray = (0.299*img[:,:,0] + 0.587*img[:,:,1] + 0.114*img[:,:,2]).astype(np.uint8)
Image.fromarray(gray).save("gray.png")
print("灰階平均亮度:", gray.mean())
# 錯誤做法:三分平均
wrong = img.mean(axis=2).astype(np.uint8)
print("平均法灰階平均亮度:", wrong.mean())
print("兩者平均差:", abs(gray.mean() - wrong.mean()))
# 用 PIL 轉 HSV
hsv = np.array(Image.fromarray(img).convert("HSV"))
print("HSV 形狀:", hsv.shape, "色相範圍:", hsv[:,:,0].min(), "-", hsv[:,:,0].max())
# 簡單色相篩選:找出偏綠色(G hue~85 左右)的像素
mask = (hsv[:,:,0] > 60) & (hsv[:,:,0] < 110) & (hsv[:,:,1] > 50)
print("被判為綠色的像素比例:", mask.mean())
執行後,正確加權與「三分平均」的灰階會產生可測量的亮度差,並透過 HSV 色相篩選示範為何顏色辨識任務常轉換到 HSV。
複雜度分析#
RGB → 灰階:每像素 3 次乘法加 2 次加法,O(H×W) 時間、O(H×W) 空間(輸出影像)。RGB ↔ HSV/Lab:涉及 max/min 與三角函數或除法,每像素常數個浮點運算,仍是 O(H×W),但常數較大,實務上可注意轉換成本。
練習#
- 讀取你自己的一張照片,分別用「正確加權」與「三分平均」轉成灰階,計算兩者的平均絕對差,並解釋為何有差別。
- 在 HSV 空間寫一個色相篩選程式,把一張圖中「偏黃(色相約 30—60 度,但 PIL 中為 15—30)」的區域畫白、其他畫黑。
- 查資料說明:為什麼 JPEG 要先把 RGB 轉成 YCbCr 再做壓縮?若直接對 RGB 壓縮會有什麼缺點?