CV 入門 2

了解你的第一張圖片

  • numpy
  • opencv
  • matplotlib
  • 影像處理

CV 入門 1: 讀取你的第一張圖片

1. 影像的三個基本資訊

import cv2
import numpy as np
import matplotlib.pyplot as plt
image = cv2.imread("/content/red_apple_on_grass.png")
image = image[:,:,::-1]  # 等於 cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
print(type(image))   # <class 'numpy.ndarray'>
print(image.shape)   # (960, 1360, 3) = (Height, Width, Channel)
print(image.dtype)   # uint8
  • image[:,:,::-1] —— ::-1 把 Channel 順序反轉, 可以達到 BGR → RGB 的效果
  • type —— cv2 讀進來的就是 numpy.ndarray, 所有 numpy 的操作都能直接用, 所以才可以使用 ::-1
  • shape —— HWC 順序, 高度在前、寬度在後, 跟一般常講的順序「寬x高的圖片」相反
  • dtype —— uint8 是 unsigned integer 8 bits (沒有正負號的 8 個 bit),
    8 個 bit 可以代表的數值上限為 2 的 8 次方, 值域為 0 ~ 255

2. uint8 影像的數值加減

2.1 unsigned 的邊界: overflow 與 underflow

unsigned 型態當數值超出邊界時會把數值繞回去, 而且 numpy 不會拋出任何錯誤

# Example
import numpy as np
a = np.array([200], dtype=np.uint8)
b = np.array([50], dtype=np.uint8)
print(a + 100)   # [ 44]
print(b - 100)   # [206]
  • overflow —— 300 超過上限, 繞回 44 = 300 mod 256
  • underflow —— -50 低於下限, 繞回 206 = -50 mod 256

2.2 用 np.clip 修正值域

這個現象發生在 unsigned int 型態本身, RGB 影像也會有相同效果。
用灰階影像舉例只是因為肉眼對明暗的變化最直接, 對比看得最清楚。
overflow 時最亮的地方會變成最暗, underflow 時最暗的地方會變成最亮。

import cv2
import numpy as np
import matplotlib.pyplot as plt
gray = cv2.cvtColor(cv2.imread("/content/red_apple_on_grass.png"), cv2.COLOR_BGR2GRAY)
# 錯誤作法: uint8 型態直接加減
brighter_wrong = gray + 100
darker_wrong = gray - 100
# 先轉成範圍夠大的型態, 運算過程才不會繞回
gray_int16 = gray.astype(np.int16)
# np.clip 限制數值範圍之後再轉回 uint8
brighter_right = np.clip(gray_int16 + 100, 0, 255).astype(np.uint8)
darker_right = np.clip(gray_int16 - 100, 0, 255).astype(np.uint8)

fig, axes = plt.subplots(2, 3, figsize=(15, 8))
panels = [
    ("original", gray),
    ("gray + 100  (sky overflow)", brighter_wrong),
    ("brighter image (clip to 0-255)", brighter_right),
    ("original", gray),
    ("gray - 100  (apple underflow)", darker_wrong),
    ("darker image (clip to 0-255)", darker_right),
]
for ax, (title, im) in zip(axes.ravel(), panels):
    ax.imshow(im, cmap="gray", vmin=0, vmax=255)
    ax.set_title(title)
    ax.axis("off")
plt.tight_layout()
plt.show()

上排加法下排減法; 中間欄位因為 uint8 繞回而出現大片反轉的色塊, 天空由白轉黑、蘋果由黑轉白, 右側欄位用 clip 限制範圍後才是預期的提亮與變暗

  • int16 的範圍夠大, 值域 -32768 ~ 32767,
    原本uint8數值是 0~255 加減 100 後的也不會 overflow / underflow 。
  • np.clip 值域限制在 VAL_MIN ~ VAL_MAX 之間, 因此可以再限制成 uint8 的值域
  • 圖片中間欄都是錯誤的結果 —— 讓天空從白變黑, 讓蘋果從黑變白, 右側欄是正確的結果
  • 可以觀察到右上角的太陽在 +100 後跟天空融為一體, 因為兩者的數值都是 255 的最高值了 (白色)

筆記摘要

一樣是程式沒有報錯, 只有畫面看得出不對。

  • 影像基礎資訊 —— numpy.ndarray, shape 是 (H, W, C), dtype 是 uint8
  • uint8邊界在哪 —— 0 ~ 255, 超出不會報錯, 而是繞回
  • 如何修正邊界問題 —— 先轉成 int16 運算, 再 np.clip(0, 255) 轉回 uint8

做數值運算之前, 先確認資料型態和數值邊界在哪。