Opencv-Python學習筆記十——影像梯度、邊緣檢測 Gradient, Edge Detection

weixin_34236497發表於2018-08-27

影像梯度 邊緣檢測

影像梯度,影像邊界
使用到的函式有: cv2.Sobel(), cv2.Schar(), cv2.Laplacian()

梯度簡單來說就是求導,OpenCV 提供了三種不同的梯度濾波器,或者說高通濾波器: Sobel,Scharr 和 Laplacian。

  • Sobel, Scharr 其實就是求一階或二階導數。
  • Scharr 是對 Sobel(使用小的卷積核求解求解梯度角度時)的優化。
  • Laplacian 是求二階導數。

Sobel運算元 cv2.Sobel()

dst = cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]])

前四個是必須的引數:

  • src引數是需要處理的影像;
  • ddepth引數是影像的深度,-1表示採用的是與原影像相同的深度。目標影像的深度必須大於等於原影像的深度;
  • dx和dy表示的是求導的階數,0表示這個方向上沒有求導,一般為0、1、2。

其後是可選的引數:

  • ksize是Sobel運算元的大小,必須為1、3、5、7。
  • scale是縮放導數的比例常數,預設情況下沒有伸縮係數;
  • delta是一個可選的增量,將會加到最終的dst中,同樣,預設情況下沒有額外的值加到dst中;
  • borderType是判斷影像邊界的模式。這個引數預設值為cv2.BORDER_DEFAULT。

Sobel運算元是高斯平滑與微分操作的結合體,所以它的抗噪聲能力很好,可以設定求導的方向(xorder 或 yorder),還可以設定使用的卷積核的大小(ksize),如果 ksize=-1,會使用 3x3 的 Scharr 濾波器,它的的效果要比 3x3 的 Sobel 濾波器好(而且速度相同,所以在使用 3x3 濾波器時應該儘量使用 Scharr 濾波器)。

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

img = cv2.imread('edage.jpg', 0)
 
"""
在Sobel函式的第二個引數這裡使用了cv2.CV_16S。
因為OpenCV文件中對Sobel運算元的介紹中有這麼一句:
“in the case of 8-bit input images it will result in truncated derivatives”。
即Sobel函式求完導數後會有負值,還有會大於255的值。而原影像是uint8,即8位無符號數,
所以Sobel建立的影像位數不夠,會有截斷。因此要使用16位有符號的資料型別,即cv2.CV_16S
"""
x = cv2.Sobel(img,cv2.CV_16S,1,0)
y = cv2.Sobel(img,cv2.CV_16S,0,1)

"""
在經過處理後,別忘了用convertScaleAbs()函式將其轉回原來的uint8形式。否則將無法顯示影像,而只是一副灰色的視窗。

dst = cv2.convertScaleAbs(src[, dst[, alpha[, beta]]])
可選引數alpha是伸縮係數,beta是加到結果上的一個值。結果返回uint8型別的圖片
"""
absX = cv2.convertScaleAbs(x)   # 轉回uint8
absY = cv2.convertScaleAbs(y)

"""
由於Sobel運算元是在兩個方向計算的,最後還需要用cv2.addWeighted(...)函式將其組合起來
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]])
其中alpha是第一幅圖片中元素的權重,beta是第二個的權重,gamma是加到最後結果上的一個值。
"""
dst = cv2.addWeighted(absX,0.5,absY,0.5,0)

img_h1 = np.hstack([img, absX])
img_h2 = np.hstack([absY, dst])
img_all = np.vstack([img_h1, img_h2])

plt.figure(figsize=(20,10))
plt.imshow(img_all, cmap=plt.cm.gray)
plt.show()
1676906-612e1b646f79edb0.png
sobel()

Laplacian 運算元

拉普拉斯運算元可以使用二階導數的形式定義,可假設其離散實現類似於二階Sobel導數,事實上,OpenCV在計算拉普拉斯運算元時直接呼叫Sobel 運算元。
Laplacian運算元:影像中的邊緣區域,畫素值會發生“跳躍”,對這些畫素求導,在其一階導數在邊緣位置為極值,這就是Sobel運算元使用的原理——極值處就是邊緣。

dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])

前兩個是必須的引數:

  • src引數是需要處理的影像;
  • ddepth引數是影像的深度,-1表示採用的是與原影像相同的深度。目標影像的深度必須大於等於原影像的深度;

其後是可選的引數:

  • ksize是運算元的大小,必須為1、3、5、7。預設為1。
  • scale是縮放導數的比例常數,預設情況下沒有伸縮係數;
  • delta是一個可選的增量,將會加到最終的dst中,同樣,預設情況下沒有額外的值加到dst中;
  • borderType是判斷影像邊界的模式。這個引數預設值為cv2.BORDER_DEFAULT。

拉普拉斯對噪聲敏感,會產生雙邊效果。不能檢測出邊的方向。通常不直接用於邊的檢測,只起輔助的角色,檢測一個畫素是在邊的亮的一邊還是暗的一邊利用零跨越,確定邊的位置。

%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread("edage.jpg", 0)
 
gray_lap = cv2.Laplacian(img, cv2.CV_16S, ksize=3)
dst = cv2.convertScaleAbs(gray_lap)
 
plt.imshow(dst, cmap=plt.cm.gray)
plt.show()
1676906-c763470d472e2399.png
Laplacian.png

邊緣檢測

邊緣檢測的一般步驟:

  1. 濾波——消除噪聲
  2. 增強——使邊界輪廓更加明顯
  3. 檢測——選出邊緣點

Canny邊緣檢測

影像的邊緣檢測的原理是檢測出影像中所有灰度值變化較大的點,而且這些點連線起來就構成了若干線條,這些線條就可以稱為影像的邊緣。
Canny邊緣檢測運算元是John F. Canny於 1986 年開發出來的一個多級邊緣檢測演算法。

Canny運算元檢測原理是通過影像訊號函式的極大值來判定影像的邊緣畫素點。邊緣檢測的演算法主演是基於影像強度的一階和二階微分操作,但導數通常對噪聲很敏感,邊緣檢測演算法常常需要根據影像源的資料進行預處理操作,因此必須採用濾波器來改善與噪聲有關的邊緣檢測的效能。在進行Canny運算元邊緣檢測前,應當先對原始資料與高斯模板進行卷積操作,得到的影像與原影像相比有些模糊。通常使用高斯平滑濾波器卷積降噪。

edge = cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient ]]])

必要引數:

  • 第一個引數是需要處理的原影像,該影像必須為單通道的灰度圖;
  • 第二個引數是閾值1;
  • 第三個引數是閾值2。 其中較大的閾值2用於檢測影像中明顯的邊緣,但一般情況下檢測的效果不會那麼完美,邊緣檢測出來是斷斷續續的。所以這時候用較小的第一個閾值用於將這些間斷的邊緣連線起來.

可選引數中apertureSize就是Sobel運算元的大小。

而L2gradient引數是一個布林值,如果為真,則使用更精確的L2範數進行計算(即兩個方向的倒數的平方和再開放),否則使用L1範數(直接將兩個方向導數的絕對值相加)

Canny邊緣檢測基本原理:

  1. 圖象邊緣檢測必須滿足兩個條件:一能有效地抑制噪聲;二必須儘量精確確定邊緣的位置。
  2. 根據對訊雜比與定位乘積進行測度,得到最優化逼近運算元。這就是Canny邊緣檢測運算元。
  3. 類似與Marr(LoG)邊緣檢測方法,也屬於先平滑後求導數的方法。

Canny 的目標是找到一個最優的邊緣檢測演算法,最優邊緣檢測的含義是:

  • 好的檢測 - 演算法能夠儘可能多地標識出影像中的實際邊緣。
  • 好的定位 - 標識出的邊緣要儘可能與實際影像中的實際邊緣儘可能接近。
  • 最小響應 - 影像中的邊緣只能標識一次,並且可能存在的影像雜訊不應標識為邊緣。

canny 演算法五步驟

  1. 高斯模糊
  2. 灰度轉換
  3. 計算梯度
  4. 非最大訊號抑制
  5. 高低閾值輸出二值影像

可參考,opencv入門12:梯度和邊緣檢測……

%matplotlib inline
import cv2
import numpy as np
import matplotlib.pyplot as plt
 
 
def edge_detect(img):
    #高斯模糊,降低噪聲
    blurred = cv2.GaussianBlur(img,(3,3),0)
    #灰度影像
    gray = cv2.cvtColor(blurred,cv2.COLOR_RGB2GRAY)
    #影像梯度
    xgrad = cv2.Sobel(gray,cv2.CV_16SC1,1,0)
    ygrad = cv2.Sobel(gray,cv2.CV_16SC1,0,1)
    #計算邊緣
    #50和150引數必須符合1:3或者1:2
    edge_output = cv2.Canny(xgrad,ygrad,50,150)
 
    dst = cv2.bitwise_and(img,img,mask=edge_output)
    
    return edge_output, dst
    
    
img = cv2.imread('edage.jpg')

edge_output, canny_edge = edge_detect(img.copy())

plt.figure(figsize=(20, 8))

plt.subplot(131)
plt.imshow(img[:,:,::-1])

plt.subplot(132)
plt.imshow(canny_edge[:,:,::-1])

plt.subplot(133)
plt.imshow(edge_output, cmap=plt.cm.gray)

plt.tight_layout()
plt.show()
1676906-067b30cd2cb12a87.png
canny.png

相關文章