Opencv-Python 學習之旅 - 3

Image Processing in OpenCV

Posted by Bobson Lin on Friday, December 14, 2018

色彩空間轉換


Opencv-Python 學習之旅 - 1 起步 - 照片(影像)imread函數就有選擇讀取照片時你想要的色彩標誌參數 (BGR, HSV … 等)。
在 OpenCV 中還有其他的函數來作色彩空間轉換的處理。

import cv2

img = cv2.imread('data/starry_night.jpg')

# Convert BGR to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# (600, 752, 3) 1353600 <class 'numpy.ndarray'>
print(hsv.shape, hsv.size, type(hsv))

# define range of blue color in HSV
lower_blue = np.array([110, 50, 50])
upper_blue = np.array([130, 255, 255])

# Threshold the HSV image to get only blue colors
mask = cv2.inRange(hsv, lower_blue, upper_blue)

# Bitwise-AND mask and original image
res = cv2.bitwise_and(img, img, mask=mask)

cv2.imshow('frame', img)
cv2.imshow('mask', mask)
cv2.imshow('res', res)
k = cv2.waitKey(0)
if k == 27:         # wait for ESC key to exit
    cv2.destroyAllWindows()
  • cv2.cvtColor 可以轉換色彩空間,用 cv2.inRange 來定義藍色上下限值製作遮罩圖。
  • 遮罩圖中白色為1;黑色為0,最後再例用 cv2.bitwise_and 將圖片中藍色值過濾出來。
  • 相關 API 文檔 Operations on Arraysmiscellaneous transformations
  • 結果: change_colorspace

影像閾值(門檻值)


固定閾值(簡單閾值)

  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV
import cv2

from matplotlib import pyplot as plt

img = cv2.imread('data/gradient.png', 0)
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

titles = ['Original Image', 'BINARY',
          'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]

for i in range(6):
    plt.subplot(2, 3, i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])

plt.show()

調整性閾值

有時候由於拍照時光線不均,所以影像每個區域的灰階值基準不同,這時很難找到一個閾值能適用整張影像,然後得到良好的二值化結果,這時我們可以將影像分成幾個區域,每個區域有各自的閾值,再分別將各個區域進行二值化,OpenCV用adaptiveThreshold()函式來進行此作法。

  • cv2.ADAPTIVE_THRESH_MEAN_C : 以鄰近格的平均值減去 C 當作閾值。
  • cv2.ADAPTIVE_THRESH_GAUSSIAN_C : 以鄰近格的高斯加權平均值減去 C 當作閾值。
import cv2

from matplotlib import pyplot as plt

img = cv2.imread('data/sudoku.png', 0)
img = cv2.medianBlur(img, 5)

ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
th2 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C,
                            cv2.THRESH_BINARY, 11, 2)
th3 = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                            cv2.THRESH_BINARY, 11, 2)

titles = ['Original Image', 'Global Thresholding (v = 127)',
          'Adaptive Mean Thresholding', 'Adaptive Gaussian Thresholding']
images = [img, th1, th2, th3]

for i in range(4):
    plt.subplot(2, 2, i+1), plt.imshow(images[i], 'gray')
    plt.title(titles[i])
    plt.xticks([]), plt.yticks([])
plt.show()

大津演算法二值化

Otsu流程:
* 先計算影像的直方圖
* 把直方圖強度大於閾值的像素分成一組,把小於閾值的像素分成另一組。
* 分別計算這兩組的組內變異數,並把兩個組內變異數相加。
* 將0~255依序當作閾值來計算組內變異數和,總和值最小的就是結果閾值。

import cv2

from matplotlib import pyplot as plt

img = cv2.imread('data/sudoku.png', 0)

# global thresholding
ret1, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)

# Otsu's thresholding
ret2, th2 = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# Otsu's thresholding after Gaussian filtering
blur = cv2.GaussianBlur(img, (5, 5), 0)
ret3, th3 = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)

# plot all the images and their histograms
images = [img, 0, th1,
          img, 0, th2,
          blur, 0, th3]
titles = ['Original Noisy Image', 'Histogram', 'Global Thresholding (v=127)',
          'Original Noisy Image', 'Histogram', "Otsu's Thresholding",
          'Gaussian filtered Image', 'Histogram', "Otsu's Thresholding"]

for i in range(3):
    plt.subplot(3, 3, i*3+1), plt.imshow(images[i*3], 'gray')
    plt.title(titles[i*3]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i*3+2), plt.hist(images[i*3].ravel(), 256)
    plt.title(titles[i*3+1]), plt.xticks([]), plt.yticks([])
    plt.subplot(3, 3, i*3+3), plt.imshow(images[i*3+2], 'gray')
    plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([])

plt.show()
  • 此方式較複雜,利用大津演算法算出閾值
  • 結果: image_threshold_otsu_binarization

影像的幾何轉換


import cv2
import numpy as np

from matplotlib import pyplot as plt

ori_img = cv2.imread('data/messi5.jpg')

# Resize
resize = cv2.resize(ori_img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# OR
# height, width = img.shape[:2]
# res = cv2.resize(img, (2*width, 2*height), interpolation=cv2.INTER_CUBIC)

grey_img = cv2.imread('data/messi5.jpg', 0)
rows, cols = grey_img.shape

# Translation
M = np.float32([[1, 0, 100], [0, 1, 50]])
translation = cv2.warpAffine(grey_img, M, (cols, rows))

# Rotate
M = cv2.getRotationMatrix2D((cols/2, rows/2), 90, 1)
rotate = cv2.warpAffine(grey_img, M, (cols, rows))

plt.subplot(331), plt.imshow(ori_img), plt.title('Origin')
plt.subplot(332), plt.imshow(resize), plt.title('Resize')
plt.subplot(333), plt.imshow(grey_img), plt.title('Grey')
plt.subplot(334), plt.imshow(translation), plt.title('Translation')
plt.subplot(335), plt.imshow(rotate), plt.title('Rotate')

# Affine Transformation
rows, cols, ch = ori_img.shape

pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])

M = cv2.getAffineTransform(pts1, pts2)

dst = cv2.warpAffine(ori_img, M, (cols, rows))

plt.subplot(336), plt.imshow(dst), plt.title('Affine Transform')

# Perspective Transformation
img = cv2.imread('data/sudoku.png')
rows, cols, ch = img.shape

pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])

M = cv2.getPerspectiveTransform(pts1, pts2)

dst = cv2.warpPerspective(img, M, (300, 300))

plt.subplot(337), plt.imshow(img), plt.title('Origin')
plt.subplot(338), plt.imshow(dst), plt.title('Perspective Transform')

plt.show()
  • 將一幅影像的座標位置,映射到新座標,但不改變像素值。簡單說就是矩陣的幾何運算。
  • cv2.warpAffine 為仿射轉換函數,利用 2 x 3 的仿射(轉置)矩陣,可以達成圖片的放大、縮小、旋轉、左右反轉、扭曲。
  • 仿射(轉置)矩陣可以用 cv2.getAffineTransformcv2.getRotationMatrix2D… 等函數取得。
  • cv2.warpPerspective 為透視轉換函數,與仿射轉換函數雷同,不過透視矩陣為 2 x 4 大小;此矩陣可用 cv2.getPerspectiveTransform 取得。
  • 相關 API 文檔 geometric transformations
  • 結果: geometric_transform

你也可以瞧瞧…

參考

系列文