人眼怎么識別圖像邊緣?
比如有一幅圖,圖里面有一條線,左邊很亮,右邊很暗,那人眼就很容易識別這條線作為邊緣.也就是像素的灰度值快速變化的地方.
sobel算子
對於f(t),其導數f'(t)反映了每一處的變化趨勢.在變化最快的位置其導數最大. sobel算子的思路就是模擬求一階導數.
sobel算子是一個離散差分算子.它計算圖像像素點亮度值的近似梯度.
圖像是二維的,即沿着寬度/高度兩個方向.
我們使用兩個卷積核對原圖像進行處理:
-
水平方向
很好理解,原始像素灰度值-->(右邊像素值-左邊像素值),反映了水平方向的變化情況. -
垂直方向
這樣的話,我們就得到了兩個新的矩陣,分別反映了每一點像素在水平方向上的亮度變化情況和在垂直方向上的亮度變換情況.
綜合考慮這兩個方向的變化,我們使用
反映某個像素的梯度變化情況.
有時候為了簡單起見,也直接用絕對值相加替代.
opencv里可以使用了如下的卷積核,可以"放大像素的變化情況".
可以參考這個函數Scharr
opencv實現
import cv2 as cv
def test():
src = cv.imread("/home/sc/disk/keepgoing/opencv_test/sidetest.jpeg")
src = cv.GaussianBlur(src, (3, 3), 0)
gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
grad_x = cv.Sobel(gray, -1, 1, 0, ksize=3)
grad_y = cv.Sobel(gray, -1, 0, 1, ksize=3)
grad = cv.addWeighted(grad_x, 0.5, grad_y, 0.5, 0)
cv.imshow("origin",src)
cv.imshow("grad",grad)
cv.waitKey()
test()
首先是高斯模糊去噪.某種意義上說高斯模糊是和sobel相反的過程.高斯模糊平滑了某點像素與周邊像素的差異.那為什么還要先高斯去噪呢?
噪聲就是像素的強度相對於真值有個突變。從時域上講,通過高斯濾波能讓一個像素的強度與周圍的點相關,就減小了突變的影響;從頻域上講,突變引入了高頻分量,而高斯濾波器可以濾除高頻分量。
高斯去噪是為了防止把噪點也檢測為邊緣.
然后計算grad_x,grad_y.即對原圖做水平方向/垂直方向的sobel卷積核卷積
grad_x = cv.Sobel(gray, -1, 1, 0, ksize=3)
grad_y = cv.Sobel(gray, -1, 0, 1, ksize=3)
sobel api
注意區分c++版本和python版本api. 在上述代碼中,第二個參數-1代表我們希望輸出的圖像矩陣和原圖有同樣的depth,第3/4個參數分別代表在x/y方向做一階差分.取值0或1.
ksize必須為奇數.
tips:通常我們使用( xorder = 1, yorder = 0, ksize = 3) or ( xorder = 0, yorder = 1, ksize = 3)來計算水平/垂直方向的一階差分矩陣.ksize=3用的是標准sobel卷積核.如果ksize傳入FILTER_SCHARR,則使用的是如下卷積核:
最后將兩個矩陣疊加,綜合考慮水平和垂直方向的像素灰度值變化強度.得到邊緣.
grad = cv.addWeighted(grad_x, 0.5, grad_y, 0.5, 0)
完整代碼處理效果如下
ksize采用cv.FILTER_SCHARR效果如下:
grad_x = cv.Sobel(gray, -1, 1, 0, ksize=cv.FILTER_SCHARR)
grad_y = cv.Sobel(gray, -1, 0, 1, ksize=cv.FILTER_SCHARR)