嚴格的說,梯度計算須要求導數。可是圖像梯度的計算,是經過計算像素值的差獲得梯度的近似值。圖像梯度表示的是圖像變化的速度,反映了圖像的邊緣信息。算法
邊緣是像素值快速變化的地方。因此對於圖像的邊緣部分,其灰度值變化較大,梯度值也較大;對於圖像中較平滑的部分,其灰度值變化較小,梯度值也較小。app
爲了檢測邊緣,咱們須要檢測圖像中的不連續性,可使用圖像梯度來檢測不連續性。可是,圖像梯度也會受到噪聲的影響,所以建議先對圖像進行平滑處理。ide
本文目錄: 函數
1. 圖像梯度與幾種算子性能
Sobel算子spa
Scharr算子設計
Roberts算子3d
Laplacian算子code
2. Canny邊緣檢測orm
高斯濾波
計算梯度強度和方向
非極大值抑制(NMS)
用雙閾值算法檢測和鏈接邊緣
3. 基於OpenCV的實現
Sobel算子函數
Scharr算子
Laplacian算子
「濾波器」也能夠稱爲「卷積核」,「掩膜」,「算子」等。
Sobel算子是一個3×3的卷積核,利用局部差分尋找邊緣,計算獲得梯度的近似值。x和y方向的Sobel算子分別爲:
梯度有方向,對於一個圖像,能夠經過Sobel算子分別計算水平方向和垂直方向的偏導數的近似值。
計算像素點P5的梯度,須要利用鄰域內的像素點,公式爲:即用像素點P5右側像素值減去左側像素值,距離P5近的點權重較大,爲2;距離P5遠的點權重較小,爲1。
計算像素點P5的梯度,須要利用鄰域內的像素點,公式爲:
即用像素點P5下一行的像素值減去上一行的像素值,距離P5近的點權重較大,爲2;距離P5遠的點權重較小,爲1。
x和y方向的Scharr算子分別爲:
Sobel算子與Scharr算子比較:Sobel算子的缺點是,當結構較小是,精確度不高,Scharr算子具備更高的精度。
當圖像邊緣接近於正45°或負45°時,該算法處理效果更理想。其缺點是對邊緣的定位不太準確,提取的邊緣線條較粗。正45°和負45°方向的Roberts算子分別爲:
1.4 Laplacian算子
Laplacian算子是一種二階導數算子,具備旋轉不變性,能夠知足不一樣方向的邊緣檢測要求。一般其算子的係數之和須要爲0。例如,一個3×3的Laplacian算子以下:對原圖像使用Laplacian算子:
計算P5的近似導數值,以下:
2、Canny邊緣檢測
Canny邊緣檢測是一種多級邊緣檢測算法。於1986年由John F. Canny在論文《A Computational Approach to Edge Detection》中提出。Canny邊緣檢測是從不一樣視覺對象中提取有用的結構信息並大大減小要處理的數據量的一種技術,目前已普遍應用於各類計算機視覺系統。Canny發現,在不一樣視覺系統上對邊緣檢測的要求較爲相似,所以,能夠實現一種具備普遍應用意義的邊緣檢測技術。邊緣檢測的通常標準包括:
以低的錯誤率檢測邊緣,也即意味着須要儘量準確的捕獲圖像中儘量多的邊緣。
檢測到的邊緣應精肯定位在真實邊緣的中心。
爲了知足這些要求,Canny使用了變分法。Canny檢測器中的最優函數使用四個指數項的和來描述,它能夠由高斯函數的一階導數來近似。在目前經常使用的邊緣檢測方法中,Canny邊緣檢測算法是具備嚴格定義的,能夠提供良好可靠檢測的方法之一。因爲它具備知足邊緣檢測的三個標準和實現過程簡單的優點,成爲邊緣檢測最流行的算法之一。
完成一個Canny邊緣檢測算法能夠分爲如下四步:
1.利用高斯濾波去噪。噪聲會影響邊緣檢測的準確性,所以要先將噪聲過濾掉。
2.計算梯度幅值和方向。
3.非極大值抑制。
4.應用雙閾值肯定真實的和可能的邊緣。
邊緣檢測很是容易受到圖像噪聲的影響,所以爲了不檢測到錯誤的邊緣信息,能夠先用高斯濾波器去除圖像噪聲。大小爲的高斯卷積核M的方程式爲:
假設爲src原圖像,dst爲高斯濾波後的圖像,M爲5×5的高斯卷積核(M不固定):(*表示卷積運算)
注意:選擇高斯核的大小會影響檢測器的性能。尺寸越大,檢測器對噪聲的靈敏度越低。此外,隨着高斯濾波器核大小的增長,用於檢測邊緣的定位偏差將略有增長。通常5×5的核是比較不錯的。梯度的方向與邊緣的方向老是垂直的。圖像中的邊緣能夠指向各個方向,一般會取水平(左、右)、垂直(上、下)、對角線(左上、右上、左下、右下)等八個不一樣的方向計算梯度。接下來使用邊緣檢測的算子(如Roberts,Sobel,Scharr等)來計算圖像中的水平、垂直和對角方向的梯度。獲得水平和垂直方向的一階導數值,由此即可以肯定像素點的梯度的大小和方向 。
其中爲梯度大小, 表示梯度方向,爲反正切函數。經過上式咱們能夠獲得一個具備梯度大小和方向的矩陣。以下圖:
角度的肯定:
獲得的角度通常不在前邊指定的放個方向上,咱們須要將角度分類到八個方向中。假設有四條線,分別是0,45,90,135度線(0度和180重合,是一條線)。須要對經過(2)式求出的進行近似,分類到這四條線分紅的八個區域中。好比計算出的,則應將其歸類到的區域,就是垂直向上方向。
八個區域以下圖:
2.3 非極大值抑制(NMS)
在每一點上,鄰域中心與沿着其對應的梯度方向的兩個像素相比,若中心像素()爲最大值,則保留,不然中心置0,這樣能夠抑制非極大值,保留局部梯度最大的點,以獲得細化的邊緣。
若是該點是方向上的局部最大值,則保留該點
對圖像進行梯度計算後,僅僅基於梯度值提取的邊緣仍然很模糊。對邊緣有且應當只有一個準確的響應。而非極大值抑制則能夠幫助將局部最大值以外的全部梯度值抑制爲0。非極大值抑制是一種邊緣稀疏技術,非極大值抑制的做用在於「瘦」邊。直觀上地看,對第二步獲得的圖片,邊原因粗變細了。通過上述處理後,對於同一個方向的若干邊緣點,基本上只保留了一個,所以實現了邊緣細化的目的。
以下圖,A,B,C三點中,梯度方向上A點的局部梯度值最大,因此保留A點,其他兩點被抑制。
通過上述步驟後,圖像內的強邊緣已經在當前獲取的邊緣內,可是,一些虛邊緣也在內。咱們設置兩個閾值,高閾值maxVal和低閾值minVal,根據當前邊緣點的梯度值與這兩個閾值的關係,判斷邊緣的屬性:
若是當前邊緣點的梯度值大於或等於maxVal,,則將當前邊緣標記爲強邊緣。
若是當前邊緣點的梯度值介於maxVal與minVal之間,則將當前邊緣標記爲虛邊緣。
對獲得的虛邊緣,再作如下處理:
與強邊緣相連,該邊緣爲邊緣
能夠確定的是,強邊緣必然是邊緣點,所以必須將maxVal設置的足夠高,以要求像素點的梯度值足夠大(變化足夠劇烈),而弱邊緣多是邊緣,也多是噪聲,當虛邊緣與強邊緣相連時,就認爲該虛邊緣點是邊緣點,以此來實現對強邊緣的補充。
實際中maxVal:minVal=2:1的比例效果比較好,其中maxVal能夠指定,也能夠設計算法來自適應的指定,好比定義梯度直方圖的前30%的分界線爲maxVal。
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
關於參數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()
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
在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,不然語句是錯誤的。
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()
OpenCV使用Laplacian算子的函數是cv2.Laplacian()
dst = cv2.Laplacian(src,ddepth,ksize,scale,delta,borderType)
參數:
src 原圖像
ddepth 輸出圖像的深度,該值與函數cv2.Sobel()中的參數ddepth的含義相同。
ksize 計算二階導數的核尺寸大小,必須爲正的奇數。
scale 計算導數時採用的縮放因子,默認爲1,是沒有縮放的
delta 加在目標圖像dst上的值,默認爲0
該函數分別對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()
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()