邊緣檢測(Edge detection)是圖像處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字圖像中亮度變化明顯的點。本文使用多種不一樣的方法,實現對 Lena 肖像的邊緣檢測,研究分析各算法的效果和優缺點。所涉及的方法以下:python
高通濾波法git
微分算子法github
神經網絡方法算法
圖像中的邊緣或線條等細節部分與圖像頻譜的高頻份量相對應,所以採用高通濾波讓高頻份量順利經過,使圖像的邊緣或線條細節變得清楚,實現邊緣提取和圖像銳化。網絡
常見的高通濾波器包括:理想高通濾波器、Butterworth 高通濾波器、指數高通濾波器等。dom
理想高通濾波器的傳遞函數 \(H(u, v)\) 知足下式:ide
理想高通濾波器只是一種理想情況下的濾波器,不能用實際的電子器件實現。函數
Butterworth 高通濾波器的傳遞函數 \(H(u,v)\) 以下:post
式中,\(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\) 下(十、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
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 算子對加入了高斯噪聲和椒鹽噪聲的圖像的邊緣檢測效果:
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 倉庫