點擊上方「AI與計算機視覺」,選擇加"星標"或「置頂」
重磅乾貨,第一時間送達
本文轉載自:https://sheng.blog.csdn.net/article/details/53213515
如有不妥之處,請聯繫作者刪除。
https://sheng.blog.csdn.net/article/details/53213515一. Canny基本思想
1. 邊緣檢測
解析:邊緣是對象和背景之間的邊界,還能表示重疊對象之間的邊界。邊緣檢測是圖像分割的一部分,圖像分割的目的是識別出圖像中的區域。邊緣檢測是定位邊緣像素的過程,而邊緣增強是增加邊緣和背景之間的對比度以便能夠更清楚地看清邊緣的過程。邊緣跟蹤是沿著邊緣進行跟蹤的過程,這個過程通常會把邊緣像素採集到一個列表中,鏈碼算法是邊緣跟蹤算法的一個特例。
2. 最優邊緣準則 [1]
Canny邊緣檢測算子是John F. Canny於1986年開發出來的一個多級邊緣檢測算法。Canny的目標是找到一個最優的邊緣檢測算法,最優邊緣檢測的含義,如下所示:
(1)最優檢測:算法能夠儘可能多地標識出圖像中的實際邊緣,漏檢真實邊緣的概率和誤檢非邊緣的概率都儘可能小;
(2)最優定位準則:檢測到的邊緣點的位置距離實際邊緣點的位置最近,或者是由於噪聲影響引起檢測出的邊緣偏離物體的真實邊緣的程度最小;
(3)檢測點與邊緣點一一對應:算子檢測的邊緣點與實際邊緣點應該是一一對應。
二. Canny算法實現
1. 高斯濾波圖像去噪
垃圾進,垃圾出,數據預處理工作是非常重要的,圖像處理也不例外。這裡使用高斯濾波進行圖像去噪,比如blur = cv2.GaussianBlur(img, (5,5), 0),處理後的圖像與原始圖像相比稍微有些模糊。這樣單獨的一個像素噪聲在經過高斯濾波的圖像上變得幾乎沒有影響。
2. 計算圖像梯度
Canny算法的基本思想是找尋一幅圖相中灰度強度變化最強的位置。所謂變化最強,即指梯度方向。對平滑後的圖像使用Sobel算子計算水平方向和豎直方向的一階導數(圖像梯度)(Gx和Gy)。根據得到的這兩幅梯度圖(Gx和Gy)找到邊界的梯度和方向。如下所示:
梯度的方向一般總是與邊界垂直。梯度方向被歸為四類:垂直,水平,和兩個對角線。
3. 非極大值抑制
在獲得梯度的方向和大小之後,應該對整幅圖像做一個掃描,去除那些非邊界上的點。對每一個像素進行檢查,看這個點的梯度是不是周圍具有相同梯度方向的點中最大的。如下所示:
上圖中的數字代表了像素點的梯度強度,箭頭方向代表了梯度方向。以第二排第三個像素點為例,由於梯度方向向上,則將這一點的強度(7)與其上下兩個像素點的強度(5和4)比較,由於這一點強度最大,則保留。
4. 滯後閾值
現在要確定那些邊界才是真正的邊界。這時我們需要設置兩個閾值:minVal和maxVal。當圖像的灰度梯度高於maxVal時被認為是真的邊界,那些低於minVal的邊界會被拋棄。如果介於兩者之間的話,就要看這個點是否與某個被確定為真正的邊界點相連,如果是就認為它也是邊界點,如果不是就拋棄。如下所示:
A高於閾值maxVal所以是真正的邊界點,C雖然低於maxVal但高於minVal並且與A相連,所以也被認為是真正的邊界點。而B就會被拋棄,因為它不僅低於maxVal而且不與真正的邊界點相連。所以選擇合適的maxVal和minVal對於能否得到好的結果非常重要。在這一步一些小的噪聲點也會被除去,因為我們假設邊界都是一些長的線段。
三. Canny代碼應用
import cv2import numpy as np
if __name__ == '__main__':
def nothing(*arg):pass
cv2.namedWindow('edge')cv2.createTrackbar('thrs1', 'edge', 2000, 5000, nothing)cv2.createTrackbar('thrs2', 'edge', 4000, 5000, nothing)
cap = cv2.VideoCapture(0)while True:flag, img = cap.read()img = cv2.GaussianBlur(img, (3,3), 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)thrs1 = cv2.getTrackbarPos('thrs1', 'edge')thrs2 = cv2.getTrackbarPos('thrs2', 'edge')edge = cv2.Canny(gray, thrs1, thrs2, apertureSize=5)vis = img.copy()vis = np.uint8(vis/2.)vis[edge != 0] = (0, 255, 0)cv2.imshow('edge', vis)ch = cv2.waitKey(5) & 0xFFif ch == 27:breakcv2.destroyA解析:
1. cv2.Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) → edges
(1)其中較大的threshold2用於檢測圖像中明顯的邊緣,但一般情況下檢測的效果不會那麼完美,邊緣檢測出來是斷斷續續的,所以這時候用較小的threshold1用於將這些間斷的邊緣連接起來。
(2)可選參數apertureSize是Sobel算子的大小(默認值為3),而參數L2gradient是一個布爾值,如果為真,則使用更精確的L2範數進行計算(即兩個方向的倒數的平方和再開方),否則使用L1範數(直接將兩個方向導數的絕對值相加)。
2. cv2.Sobel(src, ddepth, dx, dy[, dst[, ksize[, scale[, delta[, borderType]]]]]) → dst
(1)ddepth表示圖像的深度,-1表示採用的是與原圖像相同的深度。目標圖像的深度必須大於等於原圖像的深度;
(2)dx和dy表示求導的階數,0表示這個方向上沒有求導,一般為0、1、2。
(3)ksize表示Sobel算子的大小,必須為1、3、5、7。
(4)scale:optional scale factor for the computed derivative values; by default, no scaling is applied。
(5)delta:optional delta value that is added to the results prior to storing them in dst.
(6)borderType:pixel extrapolation method.
3. cv2.convertScaleAbs(src[, dst[, alpha[, beta]]]) → dst
(1)Scales, calculates absolute values, and converts the result to 8-bit, namely dst = src*alpha + beta.
(2)alpha:optional scale factor.
(3)beta:optional delta added to the scaled values.4. cv2.addWeighted(src1, alpha, src2, beta, gamma[, dst[, dtype]]) → dst
(1)alpha表示src1的權重。
(2)beta表示src2的權重。
(3)gamma:scalar added to each sum.
(4)dtype:optional depth of the output array; when both input arrays have the same depth, dtype can be set to -1, which will be equivalent to src1.depth().
cv2.addWeighted表示計算兩個數組的權重和,即dst = src1*alpha + src2*beta + gamma。
說明:邊緣檢測常用方法:Canny算子,Sobel算子,Laplace算子,Scharr濾波器。
參考文獻:
[1] Canny算法:http://baike.baidu.com/link?url=AAePc4GAMaZkiEOS17ldzT0DAIKre8w55f7oR56mqniSpQSnc8tDMwXEU2BQ0aGGaaPrsaiNbhTIMohvfgj6aPENnr0VHVXnmF2xPGrnlju
[2] OpenCV官方教程中文版
[3] OpenCV-Python教程:http://blog.csdn.net/sunny2038/article/details/9170013