[OpenCV-Python] OpenCV 中的圖像處理 部分 IV (一)


部分 IV
OpenCV 中的圖像處理

OpenCV-Python 中文教程(搬運)目錄

 

13 顏色空間轉換

 

目標
  • 你將學習如何對圖像進行顏色空間轉換,比如從 BGR 到灰度圖,或者從BGR 到 HSV 等。
  • 我沒還要創建一個程序用來從一幅圖像中獲取某個特定顏色的物體。
  • 我們將要學習的函數有:cv2.cvtColor(),cv2.inRange() 等。


13.1 轉換顏色空間
  在 OpenCV 中有超過 150 中進行顏色空間轉換的方法。但是你以后就會、發現我們經常用到的也就兩種:BGR↔Gray 和 BGR↔HSV。
我們要用到的函數是:cv2.cvtColor(input_image ,flag),其中 flag就是轉換類型。
對於 BGR↔Gray 的轉換,我們要使用的 flag 就是 cv2.COLOR_BGR2GRAY。
同樣對於 BGR↔HSV 的轉換,我們用的 flag 就是 cv2.COLOR_BGR2HSV。
你還可以通過下面的命令得到所有可用的 flag。

import cv2
flags=[i for in dir(cv2) if i startswith('COLOR_')]
print flags

注意:在 OpenCV 的 HSV 格式中,H(色彩/色度)的取值范圍是 [0,179],S(飽和度)的取值范圍 [0,255],V(亮度)的取值范圍 [0,255]。但是不同的軟件使用的值可能不同。所以當你需要拿 OpenCV 的 HSV 值與其他軟件的 HSV 值進行對比時,一定要記得歸一化。

13.2 物體跟蹤
  現在我們知道怎樣將一幅圖像從 BGR 轉換到 HSV 了,我們可以利用這一點來提取帶有某個特定顏色的物體。在 HSV 顏色空間中要比在 BGR 空間中更容易表示一個特定顏色。在我們的程序中,我們要提取的是一個藍色的物體。下面就是就是我們要做的幾步:
  • 從視頻中獲取每一幀圖像
  • 將圖像轉換到 HSV 空間
  • 設置 HSV 閾值到藍色范圍。
  • 獲取藍色物體,當然我們還可以做其他任何我們想做的事,比如:在藍色物體周圍畫一個圈。
下面就是我們的代碼:

import cv2
import numpy as np

cap = cv2.VideoCapture(r'你的視頻文件')

while(1):

    # Take each frame
    _, frame = cap.read()

    # Convert BGR to HSV
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    # 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(frame,frame, mask= mask)

    cv2.imshow('frame',frame)
    cv2.imshow('mask',mask)
    cv2.imshow('res',res)
    k = cv2.waitKey(5) & 0xFF
    if k == 27:
        break

cv2.destroyAllWindows()

注意:這是物體跟蹤中最簡單的方法。當你學習了輪廓之后,你就會學到更多相關知識,那是你就可以找到物體的重心,並根據重心來跟蹤物體,僅僅在攝像頭前揮揮手就可以畫出同的圖形,或者其他更有趣的事。

 

13.3 怎樣找到要跟蹤對象的 HSV 值?
  這是我在stackoverflow.com上遇到的最普遍的問題。其實這真的很簡單,函數 cv2.cvtColor() 也可以用到這里。但是現在你要傳入的參數是(你想要的)BGR 值而不是一副圖。例如,我們要找到綠色的 HSV 值,我們只需在終端輸入以下命令:

import cv2
import numpy as np
green=np.uint8([0,255,0])
hsv_green=cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
error: /builddir/build/BUILD/opencv-2.4.6.1/
modules/imgproc/src/color.cpp:3541:
error: (-215) (scn == 3 || scn == 4) && (depth == CV_8U || depth == CV_32F)
in function cvtColor
#scn (the number of channels of the source),
#i.e. self.img.channels(), is neither 3 nor 4.
#
#depth (of the source),
#i.e. self.img.depth(), is neither CV_8U nor CV_32F.
# 所以不能用 [0,255,0] ,而要用 [[[0,255,0]]]
# 這里的三層括號應該分別對應於 cvArray , cvMat , IplImage
green=np.uint8([[[0,255,0]]])
hsv_green=cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
print(hsv_green)
# [[[60 255 255]]]

現在你可以分別用 [H-100,100,100] 和 [H+100,255,255] 做上下閾值。除了這個方法之外,你可以使用任何其他圖像編輯軟件(例如 GIMP)或者在線轉換軟件找到相應的 HSV 值,但是最后別忘了調節 HSV 的范圍。


練習
1. 嘗試同時提取多個不同的顏色物體,比如同時提取紅,藍,綠三個不同顏色的物體。

 

 

14 幾何變換


目標
  • 學習對圖像進行各種幾個變換,例如移動,旋轉,仿射變換等。
  • 將要學到的函數有:cv2.getPerspectiveTransform。


變換
OpenCV 提供了兩個變換函數,cv2.warpAffine 和 cv2.warpPerspective,使用這兩個函數你可以實現所有類型的變換。cv2.warpAffine 接收的參數是2 × 3 的變換矩陣,而 cv2.warpPerspective 接收的參數是 3 × 3 的變換矩陣。
14.1 擴展縮放
  擴展縮放只是改變圖像的尺寸大小。OpenCV 提供的函數 cv2.resize()可以實現這個功能。圖像的尺寸可以自己手動設置,你也可以指定縮放因子。我們可以選擇使用不同的插值方法。在縮放時我們推薦使用 cv2.INTER_AREA,在擴展時我們推薦使用 v2.INTER_CUBIC(慢) 和 v2.INTER_LINEAR。默認情況下所有改變圖像尺寸大小的操作使用的插值方法都是 cv2.INTER_LINEAR。你可以使用下面任意一種方法改變圖像的尺寸:

import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
# 下面的 None 本應該是輸出圖像的尺寸,但是因為后邊我們設置了縮放因子
# 因此這里為 None
res=cv2.resize(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)
while(1):
    cv2.imshow('res',res)
    cv2.imshow('img',img)
    if cv2.waitKey(1) & 0xFF == 27:
        break
cv2.destroyAllWindows()
# Resize(src, dst, interpolation=CV_INTER_LINEAR)

 


14.2 平移
  平移就是將對象換一個位置。如果你要沿(x,y)方向移動,移動的距離是(t x ,t y ),你可以以下面的方式構建移動矩陣:

      M = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y  \end{bmatrix}
你可以使用 Numpy 數組構建這個矩陣(數據類型是 np.float32),然后把它傳給函數 cv2.warpAffine()。看看下面這個例子吧,它被移動了(100,50)個像素。

import cv2
import numpy as np

img = cv2.imread('messi5.jpg',0)
rows,cols = img.shape

M = np.float32([[1,0,100],[0,1,50]])
dst = cv2.warpAffine(img,M,(cols,rows))

cv2.imshow('img',dst)
cv2.waitKey(0)
cv2.destroyAllWindows()

     Translation


警告:函數 cv2.warpAffine() 的第三個參數的是輸出圖像的大小,它的格式應該是圖像的(寬,高)。應該記住的是圖像的寬對應的是列數,高對應的是行數。
下面就是結果:

14.3 旋轉
  對一個圖像旋轉角度 θ, 需要使用到下面形式的旋轉矩陣。
      M = \begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta   \end{bmatrix}
但是 OpenCV 允許你在任意地方進行旋轉,但是旋轉矩陣的形式應該修
改為
      \begin{bmatrix} \alpha &  \beta & (1- \alpha )  \cdot center.x -  \beta \cdot center.y \\ - \beta &  \alpha &  \beta \cdot center.x + (1- \alpha )  \cdot center.y \end{bmatrix}
其中:
      \begin{array}{l} \alpha =  scale \cdot \cos \theta , \\ \beta =  scale \cdot \sin \theta \end{array}
為了構建這個旋轉矩陣,OpenCV 提供了一個函數:cv2.getRotationMatrix2D。
下面的例子是在不縮放的情況下將圖像旋轉 90 度。

import cv2
import numpy as np
img=cv2.imread('messi5.jpg',0)
rows,cols=img.shape
# 這里的第一個參數為旋轉中心,第二個為旋轉角度,第三個為旋轉后的縮放因子
# 可以通過設置旋轉中心,縮放因子,以及窗口大小來防止旋轉后超出邊界的問題
M=cv2.getRotationMatrix2D((cols/2,rows/2),45,0.6)
# 第三個參數是輸出圖像的尺寸中心
dst=cv2.warpAffine(img,M,(2*cols,2*rows))
while(1):
    cv2.imshow('img',dst)
    if cv2.waitKey(1)&0xFF==27:
        break
cv2.destroyAllWindows()   

下面是結果:

    Rotation of Image


14.4 仿射變換
  在仿射變換中,原圖中所有的平行線在結果圖像中同樣平行。為了創建這個矩陣我們需要從原圖像中找到三個點以及他們在輸出圖像中的位置。然后cv2.getAffineTransform 會創建一個 2x3 的矩陣,最后這個矩陣會被傳給函數 cv2.warpAffine。
來看看下面的例子,以及我選擇的點(被標記為綠色的點)

img = cv2.imread('drawing.png')
rows,cols,ch = 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(img,M,(cols,rows))

plt.subplot(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

下面是結果:

     Affine Transformation


14.5 透視變換
  對於視角變換,我們需要一個 3x3 變換矩陣。在變換前后直線還是直線。要構建這個變換矩陣,你需要在輸入圖像上找 4 個點,以及他們在輸出圖像上對應的位置。這四個點中的任意三個都不能共線。這個變換矩陣可以有函數 cv2.getPerspectiveTransform() 構建。然后把這個矩陣傳給函數cv2.warpPerspective。
代碼如下:

img = cv2.imread('sudokusmall.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(121),plt.imshow(img),plt.title('Input')
plt.subplot(122),plt.imshow(dst),plt.title('Output')
plt.show()

結果如下:

    Perspective Transformation


15 圖像閾值


目標
  • 本節你將學到簡單閾值,自適應閾值,Otsu’s 二值化等
  • 將要學習的函數有 cv2.threshold,cv2.adaptiveThreshold 等。


15.1 簡單閾值
  與名字一樣,這種方法非常簡單。但像素值高於閾值時,我們給這個像素賦予一個新值(可能是白色),否則我們給它賦予另外一種顏色(也許是黑色)。這個函數就是 cv2.threshhold()。這個函數的第一個參數就是原圖像,原圖像應該是灰度圖。第二個參數就是用來對像素值進行分類的閾值。第三個參數就是當像素值高於(有時是小於)閾值時應該被賦予的新的像素值。OpenCV提供了多種不同的閾值方法,這是有第四個參數來決定的。這些方法包括:
  • cv2.THRESH_BINARY
  • cv2.THRESH_BINARY_INV
  • cv2.THRESH_TRUNC
  • cv2.THRESH_TOZERO
  • cv2.THRESH_TOZERO_INV
上圖摘選自《學習 OpenCV》中文版,其實這些在文檔中都有詳細介紹了,你也可以直接查看文檔。
這個函數有兩個返回值,第一個為 retVal,我們后面會解釋。第二個就是閾值化之后的結果圖像了。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('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 xrange(6):
    plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])

plt.show()

注意:為了同時在一個窗口中顯示多個圖像,我們使用函數 plt.subplot()。你可以通過查看 Matplotlib 的文檔獲得更多詳細信息。
結果如下:

    

 



15.2 自適應閾值
  在前面的部分我們使用是全局閾值,整幅圖像采用同一個數作為閾值。當時這種方法並不適應與所有情況,尤其是當同一幅圖像上的不同部分的具有不同亮度時。這種情況下我們需要采用自適應閾值。此時的閾值是根據圖像上的每一個小區域計算與其對應的閾值。因此在同一幅圖像上的不同區域采用的是不同的閾值,從而使我們能在亮度不同的情況下得到更好的結果。
這種方法需要我們指定三個參數,返回值只有一個。
  • Adaptive Method- 指定計算閾值的方法。
  – cv2.ADPTIVE_THRESH_MEAN_C:閾值取自相鄰區域的平均值
  – cv2.ADPTIVE_THRESH_GAUSSIAN_C:閾值取值相鄰區域的加權和,權重為一個高斯窗口。
  • Block Size - 鄰域大小(用來計算閾值的區域大小)。
  • C - 這就是是一個常數,閾值就等於的平均值或者加權平均值減去這個常數。
我們使用下面的代碼來展示簡單閾值與自適應閾值的差別:

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('dave.jpg',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 xrange(4):
    plt.subplot(2,2,i+1),plt.imshow(images[i],'gray')
    plt.title(titles[i])
    plt.xticks([]),plt.yticks([])
plt.show()

結果:

    Adaptive Thresholding

15.3 Otsu’ ’s 二值化
  在第一部分中我們提到過 retVal,當我們使用 Otsu 二值化時會用到它。那么它到底是什么呢?
  在使用全局閾值時,我們就是隨便給了一個數來做閾值,那我們怎么知道我們選取的這個數的好壞呢?答案就是不停的嘗試。如果是一副雙峰圖像(簡單來說雙峰圖像是指圖像直方圖中存在兩個峰)呢?我們豈不是應該在兩個峰之間的峰谷選一個值作為閾值?這就是 Otsu 二值化要做的。簡單來說就是對一副雙峰圖像自動根據其直方圖計算出一個閾值。(對於非雙峰圖像,這種方法得到的結果可能會不理想)。
這里用到到的函數還是 cv2.threshold(),但是需要多傳入一個參數(flag):cv2.THRESH_OTSU。這時要把閾值設為 0。然后算法會找到最優閾值,這個最優閾值就是返回值 retVal。如果不使用 Otsu 二值化,返回的retVal 值與設定的閾值相等。
  下面的例子中,輸入圖像是一副帶有噪聲的圖像。第一種方法,我們設127 為全局閾值。第二種方法,我們直接使用 Otsu 二值化。第三種方法,我們首先使用一個 5x5 的高斯核除去噪音,然后再使用 Otsu 二值化。看看噪音去除對結果的影響有多大吧。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('noisy2.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 xrange(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()

     Otsu's Thresholding


15.4 Otsu’ ’s 二值化是如何工作的?
  在這一部分我們會演示怎樣使用 Python 來實現 Otsu 二值化算法,從而告訴大家它是如何工作的。如果你不感興趣的話可以跳過這一節。因為是雙峰圖,Otsu 算法就是要找到一個閾值(t), 使得同一類加權方差最小,需要滿足下列關系式:

      \sigma_w^2(t) = q_1(t)\sigma_1^2(t)+q_2(t)\sigma_2^2(t)
  其中:

  q_1(t) = \sum_{i=1}^{t} P(i) \quad \& \quad q_1(t) = \sum_{i=t+1}^{I} P(i)

\mu_1(t) = \sum_{i=1}^{t} \frac{iP(i)}{q_1(t)} \quad \& \quad \mu_2(t) = \sum_{i=t+1}^{I} \frac{iP(i)}{q_2(t)}

\sigma_1^2(t) = \sum_{i=1}^{t} [i-\mu_1(t)]^2 \frac{P(i)}{q_1(t)} \quad \& \quad \sigma_2^2(t) = \sum_{i=t+1}^{I} [i-\mu_1(t)]^2 \frac{P(i)}{q_2(t)}
其實就是在兩個峰之間找到一個閾值 t,將這兩個峰分開,並且使每一個峰內的方差最小。實現這個算法的 Python 代碼如下:

img = cv2.imread('noisy2.png',0)
blur = cv2.GaussianBlur(img,(5,5),0)

# find normalized_histogram, and its cumulative distribution function
hist = cv2.calcHist([blur],[0],None,[256],[0,256])
hist_norm = hist.ravel()/hist.max()
Q = hist_norm.cumsum()

bins = np.arange(256)

fn_min = np.inf
thresh = -1

for i in xrange(1,256):
    p1,p2 = np.hsplit(hist_norm,[i]) # probabilities
    q1,q2 = Q[i],Q[255]-Q[i] # cum sum of classes
    b1,b2 = np.hsplit(bins,[i]) # weights

    # finding means and variances
    m1,m2 = np.sum(p1*b1)/q1, np.sum(p2*b2)/q2
    v1,v2 = np.sum(((b1-m1)**2)*p1)/q1,np.sum(((b2-m2)**2)*p2)/q2

    # calculates the minimization function
    fn = v1*q1 + v2*q2
    if fn < fn_min:
        fn_min = fn
        thresh = i

# find otsu's threshold value with OpenCV function
ret, otsu = cv2.threshold(blur,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
prin(thresh,ret)

(這里有些新的函數,我們會在后面的章節中講到他們)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM