高通濾波法、微分算子法、神經網絡方法實現邊緣檢測
邊緣檢測(Edge detection)是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。本文使用多種不同的方法,實現對 Lena 肖像的邊緣檢測,研究分析各算法的效果和優缺點。所涉及的方法如下:
-
高通濾波法
- 理想高通濾波器
- Butterworth 高通濾波器
- 指數高通濾波器
-
微分算子法
- Roberts 算子
- Sobel 算子
- Laplacian 算子
- Canny 算子
-
神經網絡方法
- HED 算法
高通濾波法
圖像中的邊緣或線條等細節部分與圖像頻譜的高頻分量相對應,因此採用高通濾波讓高頻分量順利通過,使圖像的邊緣或線條細節變得清楚,實現邊緣提取和圖像銳化。
常見的高通濾波器包括:理想高通濾波器、Butterworth 高通濾波器、指數高通濾波器等。
理想高通濾波器
理想高通濾波器的傳遞函數 \(H(u, v)\) 滿足下式:
\]
理想高通濾波器只是一種理想狀況下的濾波器,不能用實際的電子器件實現。
Butterworth 高通濾波器
Butterworth 高通濾波器的傳遞函數 \(H(u,v)\) 如下:
\]
式中,\(n\) 為階數,\(D_0\) 為截止頻率。
Butterworth 高通濾波器在高低頻率間的過渡比較平滑,所以由其得到的輸出圖像的振鈴現象不明顯。
指數高通濾波器
指數高通濾波器的傳遞函數 \(H(u,v)\) 如下:
\]
式中,變量 \(n\) 控制從原點算起的傳遞函數 \(H(u,v)\) 的增長率。
指數高通濾波器的另一種常用的傳遞函數如下式所示:
\]
代碼實現
為了在頻率域中實現高通濾波,先通過傅里葉變換得到圖像的頻譜,根據不同濾波器的不同傳遞函數,對頻率進行相應的過濾,最後再對其進行傅里葉反變換,得到濾波後的圖像。
傅里葉變換
img = plt.imread('images/lena.bmp')
fft_shift = np.fft.fftshift(np.fft.fft2(img)) # 變換後將零頻分量移到頻譜中心
fft_img = np.log(np.abs(fft_shift)) # 可視化
實現三種濾波器
def distance(shape): # 計算每個像素到中心原點的距離
n, m = shape
u = np.arange(n)
v = np.arange(m)
u, v = np.meshgrid(u, v)
return np.sqrt((u - n//2)**2 + (v - m//2)**2)
def ideal_filter(shape, d0):
d = distance(shape)
mask = d > d0
return mask.astype(int)
def butterworth_filter(shape, d0, order=1):
d = distance(shape)
mask = 1 / (1 + (d0 / d)**(2 * order))
return mask
def exponential_filter(shape, d0, order=1):
d = distance(shape)
mask = np.exp(-(d0 / d)**order)
return mask
濾波後進行傅里葉反變換
ifft_shift = np.fft.ifftshift(fft_shift * mask)
ifft_img = np.abs(np.fft.ifft2(ifft_shift))
運行結果及分析
下圖中,從上到下依次展示了使用理想高通濾波器、Butterworth 高通濾波器和指數高通濾波器對 Lena 進行邊緣檢測,在不同的截止頻率 \(D_0\) 下(10、20、40、80)所得到的頻譜圖以及濾波後的圖像輸出。
通過對比可以發現,使用理想高通濾波器得到的結果有明顯的振鈴現象,而 Butterworth 高通濾波器和指數高通濾波器的結果相近,均具有較好的效果。從頻譜圖中也可以看出,理想高通濾波器對頻率的截斷非常陡峭,在臨界點發生了突變,而後兩者的濾波比較平滑,是一種在高低頻率間逐漸過渡的過程。
微分算子法
針對由於平均或積分運算而引起的圖像模糊,可用微分運算來實現圖像的銳化。微分運算是求信號的變化率,有加強高頻分量的作用,從而使圖像輪廓清晰。
常見的邊緣檢測算子包括:Roberts 算子、Sobel 算子、Laplacian 算子、Canny 算子等。各種算子的存在就是對這種導數分割原理進行的實例化計算,是為了在計算過程中直接使用的一種計算單位。實際使用時,通常用各種算子對應的模板對原圖進行卷積運算,從而提取出圖像的邊緣信息。
上述各算子的具體定義就不展開贅述了,具體可以參考相關書籍或文章。這裡通過一張表對這些算子進行簡要的介紹和比較。
算子 | 介紹及優缺點比較 |
---|---|
Roberts | 一種最簡單的算子,採用對角線方向相鄰兩像素之差近似梯度幅值檢測邊緣。檢測垂直邊緣的效果好於斜向邊緣,定位精度高,但是對噪聲敏感,對具有陡峭邊緣且含噪聲少的圖像效果較好。 |
Sobel | 根據像素點上下左右四鄰域灰度加權差檢測邊緣,類似局部平均運算,因此對噪聲具有平滑作用,對灰度漸變和噪聲較多的圖像處理效果比較好,對邊緣定位比較準確。 |
Laplacian | 屬於二階微分算子,在只考慮邊緣點的位置而不考慮周圍的灰度差時適合用該算子進行檢測。對噪聲非常敏感,只適用於無噪聲圖像。存在噪聲的情況下,使用該算子檢測邊緣之前需要先進行低通濾波,因此通常把 Laplacian 算子和平滑算子結合起來生成一個新的模板。 |
Canny | 該算子功能比前面幾種都要好,不容易受噪聲的干擾,能夠檢測到真正的弱邊緣,但是實現起來較為麻煩,是一個具有濾波、增強、檢測的多階段的優化算子。在進行處理前,Canny 算子先利用高斯平滑濾波器來平滑圖像以除去噪聲。Canny 分割算法採用一階偏導的有限差分來計算梯度幅值和方向,在處理過程中,該算子還將經過一個非極大值抑制的過程,最後採用兩個閾值來連接邊緣。 |
代碼實現
Roberts、Sobel 以及 Laplacian 算子方法均為手工實現:先根據不同算子對應的模板,分別定義對單個像素塊的卷積運算函數,然後在圖像上滑動模板,調用該函數計算每一個卷積塊,最終得到經過各算子微分後的輸出圖像。對於 Canny 算子,由於其實現過程比較複雜,這裡選擇直接調用 OpenCV 中的 Canny()
函數。
實現前三種算子
def roberts_operator(block):
kernel1 = np.array([[1,0], [0,-1]])
kernel2 = np.array([[0,-1], [1,0]])
return np.abs(np.sum(block[1:,1:] * kernel1)) + np.abs(np.sum(block[1:,1:] * kernel2))
def sobel_operator(block, orientation): # 水平和垂直兩個方向
if orientation == 'horizontal':
kernel = np.array([[-1,-2,-1], [0,0,0], [1,2,1]])
elif orientation == 'vertical':
kernel = np.array([[-1,0,1], [-2,0,2], [-1,0,1]])
else:
raise('Orientation Error')
return np.abs(np.sum(block * kernel))
def laplacian_operator(block):
kernel = np.array([[0,-1,0], [-1,4,-1], [0,-1,0]])
return np.abs(np.sum(block * kernel))
滑動計算卷積塊
def operator_process(img, operator_type, orientation=None):
n, m = img.shape
res = np.zeros((n, m))
for i in range(1, n-1):
for j in range(1, m-1):
if operator_type == 'roberts':
res[i][j] = roberts_operator(img[i-1:i+2, j-1:j+2])
elif operator_type == 'sobel':
res[i][j] = sobel_operator(img[i-1:i+2, j-1:j+2], orientation)
elif operator_type == 'laplacian':
res[i][j] = laplacian_operator(img[i-1:i+2, j-1:j+2])
else:
raise('Operator Type Error')
return res
調用 OpenCV 中的 Canny 算子
canny_res = cv2.Canny(img, threshold1, threshold2) # 設置高低閾值參數
運行結果及分析
使用 Roberts 算子、Laplacian 算子以及水平和垂直兩個方向的 Sobel 算子進行邊緣檢測所得到的結果如下圖所示:
可以看到,Roberts 算子簡單但有效,已經能實現比較好的邊緣檢測效果;而 Laplacian 算子的效果相對較差,邊緣不是很清晰,還出現了很多噪點;Sobel 算子整體表現也較好,可以明顯地看出水平和垂直兩個方向上結果的差別,前者對水平邊緣響應最大(如眼眶、嘴唇、下巴處),後者對垂直邊緣響應最大(如鼻樑、兩側臉頰處)。
下圖顯示了使用 OpenCV 自帶的 Canny 算子在不同閾值下的輸出結果,其中低閾值 threshold1
依次取 30、50、80、120、150,而高閾值 threshold2
根據 Canny 算法的推薦,均取為 threshold1
的 3 倍。
低於 threshold1
的像素被認為不是邊緣,高於 threshold2
的像素被認為是邊緣,介於二者之間的則會根據相鄰的像素點進一步確定。從圖中也可以看出,閾值設置得越高,對邊緣的過濾就越嚴格,輸出結果中的邊緣線條也越發稀疏。
噪聲測試
為了進一步研究在有噪聲的情況下各算子的邊緣檢測效果,首先使用 skimage
庫中的函數,對 Lena 加入高斯噪聲和椒鹽噪聲。
img_noise1 = skimage.util.random_noise(img, mode='gaussian')
img_noise2 = skimage.util.random_noise(img, mode='s&p')
然後分別在這兩張有噪聲的圖像上應用 Roberts 算子、Sobel 算子(水平方向)、Laplacian 算子和 Canny 算子,得到的結果如下:
其中,第一行是加入高斯噪聲後各算子的輸出,第一行是加入椒鹽噪聲後各算子的輸出。
可以發現,Roberts 算子和 Sobel 算子都有一定的抗噪聲能力,從圖中依然可以看出部分邊緣信息,而在 Laplacian 算子則對噪聲非常敏感,其輸出結果完全看不出任何邊緣。對於 Canny 算子,需要將閾值設置得很高,才能得到比較好的效果,否則大量噪點也會被認為是邊緣。經過多次嘗試,最終將 threshold1
設為 180,將 threshold2
設為 3 * threshold1
,得到了上圖中最後一列的結果。
實驗過程中還發現,在相同的閾值條件下,Canny 算子對高斯噪聲的抵抗能力比對椒鹽噪聲的抵抗能力強。下圖展示了當 threshold1
取值為 100、120、140、160、180 時,Canny 算子對加入了高斯噪聲和椒鹽噪聲的圖像的邊緣檢測效果:
HED 算法
2015年,Saining Xie 等人提出了一種基於卷積神經網絡的邊緣檢測算法——Holistically-Nested Edge Detection(HED)算法。模型使用 VGG-16 作為骨幹網絡進行多尺度多層級的特徵學習。其中 Holistically 的意思是」整體地「,表示該算法試圖訓練一個 image-to-image 的網絡;Nested 則強調在生成的輸出過程中,通過不斷的集成和學習,得到更精確的邊緣預測圖。
HED 算法具有很多優點。單就預測過程來說,對一張圖片進行邊緣檢測的速度是很快的。使用時,根據圖片的實際內容,可以通過調整合適的超參數從而得到最優尺度的邊緣信息。另外,由於 HED 本身就是基於神經網絡的,因而可以很方便地嵌入其他網絡模型中,直接參与各種學習任務的訓練過程。
在效果上,HED 算法也具有優越性。在論文中,作者把 HED 和傳統 Canny 算法進行對比。如下圖所示,可以看到 HED 的效果明顯優於 Canny 算法。關於 HED 算法的更多內容請參考論文原文。
代碼實現
這裡採用的是 HED 算法的另一個 PyTorch 實現版本,直接使用了作者提供的預訓練模型進行預測。
運行結果及分析
模型在彩色 Lena 圖像上的運行結果如下圖所示:
在添加了高斯噪聲和椒鹽噪聲後的圖像上,運行結果分別如下所示:
![]() |
![]() |
可見,雖然 HED 算法的邊緣線條比較粗,但整體表現還是相當優秀的,尤其是在存在噪聲的情況下,該算法的效果比前述幾種基於微分算子的方法都要好。
總結
本文通過使用高通濾波法、微分算子法、神經網絡方法三大類,共計 8 種不同的方法對 Lena 圖像進行了邊緣檢測,將各種方法得到的結果進行橫向比較,並對它們的優缺點和適用場景進行了一定的討論。對於具有參數的算法,進一步根據參數取值的不同進行了縱向比較,觀察參數對於輸出結果的影響。此外,還通過對原圖像添加高斯噪聲和椒鹽噪聲進行噪聲測試,研究各算法對兩種噪聲的敏感性。
實驗結果表明,不同算法由於原理或核心函數的不同,均具有各自的優缺點和適用場景。使用時應根據圖像內容和實際需求進行選擇取捨,並通過調整相關參數從而到達最佳的效果。
完整源碼請見 GitHub 倉庫
參考資料
- 王一丁,李琛,王蘊紅.《數字圖像處理》.西安電子科技大學出版社
- Xie, Saining, and Zhuowen Tu. “Holistically-nested edge detection.” Proceedings of the IEEE international conference on computer vision. 2015.
- PyTorch-HED://github.com/sniklaus/pytorch-hed
- 互聯網上的一些博客、文章、資料