我們在上一個教程中前面的例子學習了使用Sobel邊緣檢測。原理是利用邊緣區域像素值的跳變。通過求一階導數,可以使邊緣值最大化。如下圖所示:
那么,如果求二階導數會得到什么呢?
可以觀察到二階導數為0的地方。因此,可以利用該方法獲取圖像中的邊緣。然而,需要注意的是二級導數為0的不只出現在邊緣地方,還可能是一些無意義的位置,根據需要通過濾波處理該情況。
二階微分
現在我們來討論二階微分,它是拉普拉斯算子的基礎,與微積分中定義的微分略有不同,數字圖像中處理的是離散的值,因此對於一維函數的一階微分的基本定義是差值:
類似的,二階微分定義為:
將一維函數擴展到二維:
二階微分的定義保證了以下幾點:
1、在恆定灰度區域的微分值為0
2、在灰度台階或斜坡的起點處微分值非零
可以看出,二階微分可以檢測出圖像的邊緣、增強細節
拉普拉斯算子
從上面的解釋,可以看出二階導數可以擁有邊緣檢測。由於圖像是二維的,因此需要分別獲取兩個方向的導數。這里使用的是拉普拉斯算子來進行近似。
拉普拉斯算子用下面公式定義:
其中:
可以用多種方式將其表示為數字形式。對於一個3*3的區域,一般情況下被推薦最多的形式是:
實現上式的濾波器模板為:
我們可以發現,拉普拉斯算子不需要向Sobel算子那樣分別對x,y方向進行處理,它可以直接處理,現在我們來看看OpenCV中的拉普拉斯算子的函數原型:
dst = cv2.Laplacian(src, ddepth[, dst[, ksize[, scale[, delta[, borderType]]]]])
如果看了上一個教程中對於Sobel算子的介紹,這里的參數應該不難理解。
前兩個是必須的參數:
第一個參數是需要處理的圖像;
第二個參數是圖像的深度,-1表示采用的是與原圖像相同的深度。目標圖像的深度必須大於等於原圖像的深度;
其后是可選的參數:
dst不用解釋了;
ksize是算子的大小,必須為1、3、5、7。默認為1。
scale是縮放導數的比例常數,默認情況下沒有伸縮系數;
delta是一個可選的增量,將會加到最終的dst中,同樣,默認情況下沒有額外的值加到dst中;
borderType是判斷圖像邊界的模式。這個參數默認值為cv2.BORDER_DEFAULT。
我們來看代碼:
import cv2 import numpy as np img = cv2.imread("pie.png") dst = cv2.Laplacian(img,cv2.CV_16S,ksize=3) dst = cv2.convertScaleAbs(dst) cv2.imshow("img",img) cv2.imshow("res",dst) cv2.waitKey(0) cv2.destroyAllWindows()
現在可以拿這個結果對比上一個教程的結果了,我們發現,這個結果要比上一個教程的結果好的多,對於邊緣檢測沒有大的偏差。
然而事實上,這只是對於簡單的圖像而言,而對於一幅復雜的圖像,那么邊緣提取就有點愛莫能助了,我們來看代碼:
import cv2 import numpy as np img = cv2.imread("cat.jpg") dst = cv2.Laplacian(img,cv2.CV_16S,ksize=3) dst = cv2.convertScaleAbs(dst) cv2.imshow("img",img) cv2.imshow("res",dst) cv2.waitKey(0) cv2.destroyAllWindows()
可以看到,對於較為復雜的圖像,拉普拉斯算子的效果也並不是很好,由於二階微分一定的局限性,目前的邊緣檢測還不夠完美,我們需要一種綜合的算法,而這將在下一個教程中介紹到。