CV 入門 1
讀取你的第一張圖片
還沒準備好環境嗎?先看 CV 入門 0: 環境準備
1. 用 opencv 讀取彩色影像

1.1 直接讀取影像
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("/content/red_apple_on_grass.png")
plt.imshow(image)
plt.show()

先用 cv2.imread() 讀取影像, 再用 plt.imshow() 畫出來, 函式用法完全正確
但是蘋果卻變成藍色、天空也變成米色了!
問題出在兩個 packages 預期接受到的來源不同:
- opencv 讀取影像後會把 Channel 順序排成
BGR - matplotlib 預期收到的是 Channel 順序為
RGB
→ 如果不經過任何轉換, matplotlib 會把 cv2 讀到的 image 中的
「藍色的數值」當成紅色來畫, 把「紅色的數值」當成藍色來畫。
每個像素內部的 R 與 B 對調的數值結果:
| 圖片中 | RGB 數值 | cv2 讀成 BGR | 視覺顏色 |
|---|---|---|---|
| 蘋果 | (211, 30, 28) | (28, 30, 211) | 紅 → 藍 |
| 天空 | (190, 227, 244) | (244, 227, 190) | 淺藍 → 米色 |
| 草地 | (95, 168, 58) | (58, 168, 95) | 仍然是綠色 |
因為 G 通道不會被對調, 視覺上草地的綠色不會受到很大影響, 但仍有些微色調偏移。
1.2 讓 opencv 跟 matplotlib 的通道順序對齊
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("/content/red_apple_on_grass.png")
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
plt.imshow(image)
plt.show()
print(image.shape) # (960,1360,3) = (Height, Width, Channel)

cv2.cvtColor搭配cv2.COLOR_BGR2RGB, 就是明確告訴 opencv 把通道順序調整成 RGB
→ matplotlib 畫出來的影像符合預期結果image.shape可以知道 opencv 的順序是 HWC (960, 1360, 3), 這個順序跟平常講「1360×960 的圖片」是相反的, 很容易搞混。- Channel = 3 只知道數量, 實際上不知道順序為 RGB 或 BGR, 所以可以透過 matplotlib 畫出來確認。
2. matplotlib 畫出灰階影像
2.1 把彩色影像轉成灰階
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("/content/red_apple_on_grass.png")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.imshow(image)
plt.show()
print(image.shape) # (960, 1360) = (Height, Width)

明明 shape 已經顯示只剩兩個維度、沒有 channel 了, 畫出來卻還是有顏色!
問題就是出在二維陣列裡的數值本身不帶顏色資訊。
- matplotlib 會有一個對照表 colormap (cmap), 決定數值該怎麼上色
cmap預設為viridis, 數值 小 (藍) → 中 (綠) → 大 (黃)- colormap 是為了讓數值差異在視覺上可以辨認, 常用於資料視覺化。
→ matplotlib 只是不知道你想要的是「灰階影像」而不是「數值分布圖」。
2.2 設定 cmap
import cv2
import matplotlib.pyplot as plt
image = cv2.imread("/content/red_apple_on_grass.png")
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
plt.imshow(image, cmap="gray", vmin=0, vmax=255)
plt.show()

cmap="gray"可以成功顯示灰階影像imshow預設會取 image 的 [min, max], 線性映射到 [0, 1] 之後才交給 colormap 上色vmin=0, vmax=255是為了呈現 uint8 的原始數值, 而不是線性映射後的數值

沒有固定範圍的話, 每張圖都會各自正規化, 比較多張影像的亮度時就失去了共同基準。
筆記摘要
這篇筆記的兩個段落, 本質上是同一件事:
函式本身都正確執行了, 只是它對資料的預設跟你想的不一樣。
- opencv 假設通道順序是 BGR, matplotlib 假設是 RGB
- matplotlib 假設二維陣列是「數值」, 你以為它知道那是「影像」
這類狀況不會拋出任何錯誤訊息, 程式照跑、圖照畫, 只有視覺化後才察覺不對勁。
發現非預期的結果時, 先思考「這個函式預設了什麼」。