CV 入門 1

讀取你的第一張圖片

  • opencv
  • matplotlib
  • 影像處理

還沒準備好環境嗎?先看 CV 入門 0: 環境準備

1. 用 opencv 讀取彩色影像

原圖

下載 red_apple_on_grass.png

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)

轉成 RGB 之後, 蘋果恢復紅色、天空恢復淺藍

  • 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 之後, 正常顯示成灰階影像

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

左圖未設定 vmin/vmax, 數值被自動拉伸而對比變強; 右圖固定 0 到 255, 呈現原始亮度

沒有固定範圍的話, 每張圖都會各自正規化, 比較多張影像的亮度時就失去了共同基準。

筆記摘要

這篇筆記的兩個段落, 本質上是同一件事:
函式本身都正確執行了, 只是它對資料的預設跟你想的不一樣。

  • opencv 假設通道順序是 BGR, matplotlib 假設是 RGB
  • matplotlib 假設二維陣列是「數值」, 你以為它知道那是「影像」

這類狀況不會拋出任何錯誤訊息, 程式照跑、圖照畫, 只有視覺化後才察覺不對勁。

發現非預期的結果時, 先思考「這個函式預設了什麼」。