sobel算子一文說了,索貝爾算子是模擬一階求導,導數越大的地方說明變換越劇烈,越有可能是邊緣.
那如果繼續對f'(t)求導呢?
可以發現"邊緣處"的二階導數=0. 我們可以利用這一特性去尋找圖像的邊緣. 注意有一個問題,二階求導為0的位置也可能是無意義的位置
拉普拉斯算子推導過程
以x方向求解為例:
一階差分:f'(x) = f(x) - f(x - 1)
二階差分:f''(x) = f'(x+1) - f'(x) = (f(x + 1) - f(x)) - (f(x) - f(x - 1))
化簡后:f''(x) = f(x - 1) - 2 f(x)) + f(x + 1)
提取前面的系數:[1, -2, 1]
同理得到y方向的系數[1,-2,1]
這樣的話,疊加起來就得到了拉普拉斯矩陣
opencv實現
默認的ksize=1,和ksize=3效果是一樣的,都是用的上述拉普拉斯矩陣去卷積原圖像
關於filter具體是什么,可以通過函數getDerivKernels得到
dx,dy代表求導的階數.
def cal_filter(dx,dy,ksize):
kx, ky=cv.getDerivKernels(dx, dy, ksize)
print(kx)
print(ky)
cal_filter(2,2,1)
cal_filter(2,2,3)
cal_filter(2,2,5)
輸出為
可以看到ksize=1和ksize=3其實是一樣的.
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)
dst1 = cv.Laplacian(gray, -1,3)
dst2 = cv.Laplacian(gray, -1,1)
cv.imshow("origin",src)
cv.imshow("dst1",dst1)
cv.imshow("dst2",dst2)
if 27 == cv.waitKey():
cv.destroyAllWindows()
test()
效果如下:
sobel和laplace都是比較簡單的邊緣檢測算法,目前比較常用的是canny,后面的博文會寫到.
在搜索各種邊緣檢測算法的適用場景時,發現大部分文章都只講了opencv里如何實現,並且都是互相抄來抄去.下面給出個人認為講的不錯的兩個link
https://blog.csdn.net/xiaojiegege123456/article/details/7714863
https://dsp.stackexchange.com/questions/74/what-factors-should-i-consider-in-choosing-an-edge-detection-algorithm
二階導數還可以說明灰度突變的類型。在有些情況下,如灰度變化均勻的圖像,只利用一階導數可能找不到邊界,此時二階導數就能提供很有用的信息。二階導數對噪聲也比較敏感,解決的方法是先對圖像進行平滑濾波,消除部分噪聲,再進行邊緣檢測。不過,利用二階導數信息的算法是基於過零檢測的,因此得到的邊緣點數比較少,有利於后繼的處理和識別工作
總結一下就是:拉普拉斯對噪聲更敏感,但是對邊緣灰度變化不大的圖像,檢測效果比索貝爾算子要好一些.比如下圖中牛和樹的灰度變換並不是特別強.
實際使用中最常用的還是canny算法.后面的博文會再做介紹.