基於OpenCV的圖像梯度與邊緣檢測!

↑↑↑關注後"星標"Datawhale

每日干貨 & 每月組隊學習,不錯過

 Datawhale乾貨 

作者:姚童,Datawhale優秀學習者,華北電力大學

嚴格的說,梯度計算需要求導數。但是圖像梯度的計算,是通過計算像素值的差得到梯度的近似值。圖像梯度表示的是圖像變化的速度,反映了圖像的邊緣信息。

邊緣是像素值快速變化的地方。所以對於圖像的邊緣部分,其灰度值變化較大,梯度值也較大;對於圖像中較平滑的部分,其灰度值變化較小,梯度值也較小。

爲了檢測邊緣,我們需要檢測圖像中的不連續性,可以使用圖像梯度來檢測不連續性。但是,圖像梯度也會受到噪聲的影響,因此建議先對圖像進行平滑處理。

本文目錄:   

    1. 圖像梯度與幾種算子

    • Sobel算子

    • Scharr算子

    • Roberts算子

    • Laplacian算子

    2. Canny邊緣檢測

    • 高斯濾波

    • 計算梯度強度和方向

    • 非極大值抑制(NMS)

    • 用雙閾值算法檢測和連接邊緣

    3. 基於OpenCV的實現

    • Sobel算子函數

    • Scharr算子

    • Laplacian算子

一、圖像梯度與幾種算子

“濾波器”也可以稱爲“卷積核”,“掩膜”,“算子”等。

1.1 Sobel算子

Sobel算子是一個3×3的卷積核,利用局部差分尋找邊緣,計算得到梯度的近似值。x和y方向的Sobel算子分別爲: 

梯度有方向,對於一個圖像,可以通過Sobel算子分別計算水平方向和垂直方向的偏導數的近似值。

計算水平方向偏導數的近似值
設原圖像大小爲,水平方向偏導數爲:

計算像素點P5的梯度,需要利用鄰域內的像素點,公式爲:

即用像素點P5右側像素值減去左側像素值,距離P5近的點權重較大,爲2;距離P5遠的點權重較小,爲1。

計算垂直方向偏導數的近似值
設原圖像大小爲,垂直方向偏導數爲:

計算像素點P5的梯度,需要利用鄰域內的像素點,公式爲:

即用像素點P5下一行的像素值減去上一行的像素值,距離P5近的點權重較大,爲2;距離P5遠的點權重較小,爲1。

1.2 Scharr算子

x和y方向的Scharr算子分別爲: 

Sobel算子與Scharr算子比較:Sobel算子的缺點是,當結構較小是,精確度不高,Scharr算子具有更高的精度。

1.3 Roberts算子

當圖像邊緣接近於正45°或負45°時,該算法處理效果更理想。其缺點是對邊緣的定位不太準確,提取的邊緣線條較粗。正45°和負45°方向的Roberts算子分別爲: 

1.4 Laplacian算子

Laplacian算子是一種二階導數算子,具有旋轉不變性,可以滿足不同方向的邊緣檢測要求。通常其算子的係數之和需要爲0。

例如,一個3×3的Laplacian算子如下:

對原圖像使用Laplacian算子:

計算P5的近似導數值,如下:

二、Canny邊緣檢測

Canny邊緣檢測是一種多級邊緣檢測算法。於1986年由John F. Canny在論文《A Computational Approach to Edge Detection》中提出。

Canny邊緣檢測是從不同視覺對象中提取有用的結構信息並大大減少要處理的數據量的一種技術,目前已廣泛應用於各種計算機視覺系統。Canny發現,在不同視覺系統上對邊緣檢測的要求較爲類似,因此,可以實現一種具有廣泛應用意義的邊緣檢測技術。邊緣檢測的一般標準包括:

  1. 以低的錯誤率檢測邊緣,也即意味着需要儘可能準確的捕獲圖像中儘可能多的邊緣。

  2. 檢測到的邊緣應精確定位在真實邊緣的中心。

  3. 圖像中給定的邊緣應只被標記一次,並且在可能的情況下,圖像的噪聲不應產生假的邊緣。

爲了滿足這些要求,Canny使用了變分法。Canny檢測器中的最優函數使用四個指數項的和來描述,它可以由高斯函數的一階導數來近似。

在目前常用的邊緣檢測方法中,Canny邊緣檢測算法是具有嚴格定義的,可以提供良好可靠檢測的方法之一。由於它具有滿足邊緣檢測的三個標準和實現過程簡單的優勢,成爲邊緣檢測最流行的算法之一。

完成一個Canny邊緣檢測算法可以分爲以下四步:

  • 1.利用高斯濾波去噪。噪聲會影響邊緣檢測的準確性,因此要先將噪聲過濾掉。

  • 2.計算梯度幅值和方向。       

  • 3.非極大值抑制。

  • 4.應用雙閾值確定真實的和可能的邊緣。             

2.1 高斯濾波

邊緣檢測非常容易受到圖像噪聲的影響,因此爲了避免檢測到錯誤的邊緣信息,可以先用高斯濾波器去除圖像噪聲。

大小爲的高斯卷積核M的方程式爲:

假設爲src原圖像,dst爲高斯濾波後的圖像,M爲5×5的高斯卷積核(M不固定):(*表示卷積運算)

注意:選擇高斯核的大小會影響檢測器的性能。尺寸越大,檢測器對噪聲的靈敏度越低。此外,隨着高斯濾波器核大小的增加,用於檢測邊緣的定位誤差將略有增加。一般5×5的核是比較不錯的。

2.2 計算梯度強度和方向

梯度的方向與邊緣的方向總是垂直的。圖像中的邊緣可以指向各個方向,通常會取水平(左、右)、垂直(上、下)、對角線(左上、右上、左下、右下)等八個不同的方向計算梯度。

接下來使用邊緣檢測的算子(如Roberts,Sobel,Scharr等)來計算圖像中的水平、垂直和對角方向的梯度。得到水平和垂直方向的一階導數值,由此便可以確定像素點的梯度的大小和方向

其中爲梯度大小, 表示梯度方向,爲反正切函數。通過上式我們可以得到一個具有梯度大小和方向的矩陣。如下圖:

角度的確定

得到的角度一般不在前邊指定的放個方向上,我們需要將角度分類到八個方向中。假設有四條線,分別是0,45,90,135度線(0度和180重合,是一條線)。需要對通過(2)式求出的進行近似,分類到這四條線分成的八個區域中。

比如計算出的,則應將其歸類到的區域,就是垂直向上方向。

八個區域如下圖:

2.3 非極大值抑制(NMS)

在每一點上,鄰域中心與沿着其對應的梯度方向的兩個像素相比,若中心像素()爲最大值,則保留,否則中心置0,這樣可以抑制非極大值,保留局部梯度最大的點,以得到細化的邊緣。

  • 如果該點是方向上的局部最大值,則保留該點

  • 如果不是,則將其置爲0

對圖像進行梯度計算後,僅僅基於梯度值提取的邊緣仍然很模糊。對邊緣有且應當只有一個準確的響應。而非極大值抑制則可以幫助將局部最大值之外的所有梯度值抑制爲0。非極大值抑制是一種邊緣稀疏技術,非極大值抑制的作用在於“瘦”邊。直觀上地看,對第二步得到的圖片,邊緣由粗變細了。

經過上述處理後,對於同一個方向的若干邊緣點,基本上只保留了一個,因此實現了邊緣細化的目的。

如下圖,A,B,C三點中,梯度方向上A點的局部梯度值最大,所以保留A點,其餘兩點被抑制。

2.4 用雙閾值算法檢測和連接邊緣

經過上述步驟後,圖像內的強邊緣已經在當前獲取的邊緣內,但是,一些虛邊緣也在內。

我們設置兩個閾值,高閾值maxVal和低閾值minVal,根據當前邊緣點的梯度值與這兩個閾值的關係,判斷邊緣的屬性:

  • 如果當前邊緣點的梯度值大於或等於maxVal,,則將當前邊緣標記爲強邊緣。

  • 如果當前邊緣點的梯度值介於maxVal與minVal之間,則將當前邊緣標記爲虛邊緣。

  • 如果當前邊緣點的梯度值小於minVal,,則抑制當前邊緣。

對得到的虛邊緣,再做以下處理:

  • 與強邊緣相連,該邊緣爲邊緣

  • 與強邊緣無連接,該邊緣爲弱邊緣,將其抑制

可以肯定的是,強邊緣必然是邊緣點,因此必須將maxVal設置的足夠高,以要求像素點的梯度值足夠大(變化足夠劇烈),而弱邊緣可能是邊緣,也可能是噪聲,當虛邊緣與強邊緣相連時,就認爲該虛邊緣點是邊緣點,以此來實現對強邊緣的補充。

實際中maxVal:minVal=2:1的比例效果比較好,其中maxVal可以指定,也可以設計算法來自適應的指定,比如定義梯度直方圖的前30%的分界線爲maxVal。

三、基於OpenCV的實現

3.1 Sobel算子函數

OpenCV使用Sobel 算子的方法是cv2.Sobel()

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

參數:

  • src 原圖像

  • ddepth 輸出圖像的深度,具體關係:

輸入圖像深度(src.depth())輸出圖像深度(ddepth)
cv2.CV_8U-1/cv2.CV_16S/cv2.CV_32F/cv2.CV_64F
cv2.CV_16U/cv2.CV_16S-1/cv2.CV_32F/cv2.CV_64F
cv2.CV_32F-1/cv2.CV_32F/cv2.CV_64F
cv2.CV_64F-1/cv2.CV_64F
  • dx:x方向上的求導階數

  • dy:y方向上的求導階數

  • ksize:Sobel核的大小。該值爲-1時,會使用Scharr算子進行運算

  • scale:計算導數時採用的縮放因子,默認爲1,是沒有縮放的

  • delta:加在目標圖像dst上的值,默認爲0

  • borderType:邊界樣式,默認值爲cv2.BORDER_DEFAULT。

關於參數ddepth

該值爲-1時,讓處理結果與原始圖像保持一致,但是直接將ddepth設置爲-1,得到的結果可能是錯誤的。計算梯度值可能出現負數,當處理的圖像是8位圖類型,ddepth的值爲-1時,運算結果也是8位圖類型,負數會自動截斷爲0,發生信息丟失。爲了避免信息丟失,要先使用更高的數據類型cv2.CV_64F,再通過取絕對值將其映射到cv2.CV_8U類型。所以,通常將ddepth值設置爲“cv2.CV_8U”,並使用函數cv2.convertScaleAbs()對函數cv2.Sobel()的計算結果取絕對值。

注意:x方向和y方向的邊緣疊加時,應先令dx=1,dy=0,得到一個結果;再令dx=0,dy=1,得到一個結果。將兩個結果相加,而不是同時令dx=1和dy=1。

代碼示例:

# -*- coding: utf-8 -*-
import cv2


#讀取圖像
img = cv2.imread('D:/yt/picture/Sobel/laplacian.bmp',0)


#計算x方向邊緣信息
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=5)
#計算y方向邊緣信息
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=5)
#求絕對值
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
#x方向和y方向的邊緣疊加
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)


#顯示圖像
cv2.imshow("origin image",img)
cv2.imshow("x",sobelx)
cv2.imshow("y",sobely)
cv2.imshow("xy",sobelxy)


cv2.waitKey(0)
cv2.destroyAllWindows()

結果:

計算x方向的邊緣

計算y方向的邊緣

計算x方向和y方向的邊緣疊加

同時令dx=1和dy=1時

3.2 Scharr算子

OpenCV使用Scharr算子的函數是cv2.Scharr()

dst = cv2.Scharr(src,ddepth,dx,dy,scale,delta,borderType)

參數:

  • src 原圖像

  • ddepth 輸出圖像的深度,該值與函數cv2.Sobel()中的參數ddepth的含義相同。

  • dx x方向上的求導階數

  • dy y方向上的求導階數

  • scale 計算導數時採用的縮放因子,默認爲1,是沒有縮放的

  • delta 加在目標圖像dst上的值,默認爲0

  • borderType 邊界樣式,默認值爲cv2.BORDER_DEFAULT。

在cv2.Sobel()中,ksize=-1時,則會使用Scharr算子。所以下面兩個語句等價:

dst = cv2.Scharr(src,ddepth,dx,dy)
dst = cv2.Sobel(src,ddepth,dx,dy,-1)

注意:

  • 參數ddepth的值應該設置爲“cv2.CV_64F”,並對函數cv2.Scharr()的計算結果取絕對值。

  • dx和dy不能同時爲1,否則語句是錯誤的。

  • 計算x方向和y方向的邊緣疊加時,應先令dx=1,dy=0,得到一個結果;再令dx=0,dy=1,得到一個結果。將兩個結果相加,而不是同時令dx=1和dy=1。

代碼示例:

import cv2


#讀取圖像
img = cv2.imread('D:/yt/picture/Sobel/lena.bmp',0)


#計算水平方向邊緣信息
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
#計算垂直方向邊緣信息
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
#求絕對值
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
#水平方向和垂直方向的邊緣疊加
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)


#顯示圖像
cv2.imshow("origin image",img)
cv2.imshow("x",scharrx)
cv2.imshow("y",scharry)
cv2.imshow("xy",scharrxy)


cv2.waitKey(0)
cv2.destroyAllWindows()

結果:

計算x方向的邊緣

計算y方向的邊緣

計算x方向和y方向的邊緣疊加

3.3 Laplacian算子

OpenCV使用Laplacian算子的函數是cv2.Laplacian()

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

參數:

  • src 原圖像

  • ddepth 輸出圖像的深度,該值與函數cv2.Sobel()中的參數ddepth的含義相同。

  • ksize 計算二階導數的核尺寸大小,必須爲正的奇數。

  • scale 計算導數時採用的縮放因子,默認爲1,是沒有縮放的

  • delta 加在目標圖像dst上的值,默認爲0

  • borderType 邊界樣式,默認值爲cv2.BORDER_DEFAULT。

該函數分別對x和y方向進行二次求導:

注意:當ksize=1時,計算時採用如下3×3的核:

代碼示例:

import cv2


#讀取圖像
img = cv2.imread('D:/yt/picture/Sobel/laplacian.bmp',0)


#計算邊緣信息
laplace = cv2.Laplacian(img,cv2.CV_64F)
#求絕對值
laplace = cv2.convertScaleAbs(laplace)


#顯示圖像
cv2.imshow("origin image",img)
cv2.imshow("laplace",laplace)


cv2.waitKey(0)
cv2.destroyAllWindows()

結果

4. Canny函數

OpenCV使用函數cv2.Cannyl()實現Canny邊緣檢測

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

參數:

  • image 輸入圖像,必須爲8位圖像

  • threshold1 第一個閾值

  • threshold2 第二個閾值

  • apertureSize Sobel算子的大小

  • L2gradient 計算圖像梯度幅度的表示。默認值爲False,使用L1範數計算;如果爲True,則使用更精確的L2範數計算。

代碼示例:

# -*- coding: utf-8 -*-
import cv2


#讀取圖像,爲8位灰度圖像
img = cv2.imread('D:/yt/picture/Sobel/lena.bmp',cv2.IMREAD_GRAYSCALE)


#canny邊緣檢測
#去噪
img = cv2.GaussianBlur(img,(3,3),0)
#threshold1爲128,threshold2爲200時的邊緣檢測結果
canny1 = cv2.Canny(img, 128, 200)
#threshold1爲32,threshold2爲128時的邊緣檢測結果
canny2 = cv2.Canny(img, 32, 128)


#顯示圖像
cv2.imshow("origin image",img)
cv2.imshow("canny1",canny1)
cv2.imshow("canny2",canny2)


cv2.waitKey(0)
cv2.destroyAllWindows()

結果:

threshold1爲128,threshold2爲200時的邊緣檢測結果

threshold1爲32,threshold2爲128時的邊緣檢測結果

本文電子版 後臺回覆 邊緣檢測 獲取

“感謝你的在看,點贊,分享三

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章