高通濾波法、微分運算元法、神經網路方法實現邊緣檢測
阿新 • • 發佈:2020-05-16
邊緣檢測(Edge detection)是影象處理和計算機視覺中的基本問題,邊緣檢測的目的是標識數字影象中亮度變化明顯的點。本文使用多種不同的方法,實現對 Lena 肖像的邊緣檢測,研究分析各演算法的效果和優缺點。所涉及的方法如下:
* 高通濾波法
* 理想高通濾波器
* Butterworth 高通濾波器
* 指數高通濾波器
* 微分運算元法
* Roberts 運算元
* Sobel 運算元
* Laplacian 運算元
* Canny 運算元
* 神經網路方法
* HED 演算法
## 高通濾波法
影象中的邊緣或線條等細節部分與影象頻譜的高頻分量相對應,因此採用高通濾波讓高頻分量順利通過,使影象的邊緣或線條細節變得清楚,實現邊緣提取和影象銳化。
常見的高通濾波器包括:理想高通濾波器、Butterworth 高通濾波器、指數高通濾波器等。
#### 理想高通濾波器
理想高通濾波器的傳遞函式 $H(u, v)$ 滿足下式:
$$
H(u,v) = \begin{cases} 1, &D(u,v)>D_0 \cr 0, &D(u,v) \leq D_0 \end{cases}
$$
理想高通濾波器只是一種理想狀況下的濾波器,不能用實際的電子器件實現。
#### Butterworth 高通濾波器
Butterworth 高通濾波器的傳遞函式 $H(u,v)$ 如下:
$$
H(u,v)=\frac{1}{1+[D_0/D(u,v)]^{2n}}
$$
式中,$n$ 為階數,$D_0$ 為截止頻率。
Butterworth 高通濾波器在高低頻率間的過渡比較平滑,所以由其得到的輸出影象的振鈴現象不明顯。
#### 指數高通濾波器
指數高通濾波器的傳遞函式 $H(u,v)$ 如下:
$$
H(u,v)=\exp\{-[\frac{D_0}{D(u,v)}]^n\}
$$
式中,變數 $n$ 控制從原點算起的傳遞函式 $H(u,v)$ 的增長率。
指數高通濾波器的另一種常用的傳遞函式如下式所示:
$$
H(u,v)=\exp\{[\ln(\frac{1}{\sqrt 2})][\frac{D_0}{D(u,v)}]^n\}
$$
### 程式碼實現
為了在頻率域中實現高通濾波,先通過傅立葉變換得到影象的頻譜,根據不同濾波器的不同傳遞函式,對頻率進行相應的過濾,最後再對其進行傅立葉反變換,得到濾波後的影象。
#### 傅立葉變換
```python
img = plt.imread('images/lena.bmp')
fft_shift = np.fft.fftshift(np.fft.fft2(img)) # 變換後將零頻分量移到頻譜中心
fft_img = np.log(np.abs(fft_shift)) # 視覺化
```
#### 實現三種濾波器
```python
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
```
#### 濾波後進行傅立葉反變換
```python
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)所得到的頻譜圖以及濾波後的影象輸出。
![ideal_spectrum](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg8dkkndj31rs0g7e81.jpg)
![ideal_output](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg8h0u6zj31rs0fub29.jpg)
![butterworth_spectrum](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg8mwxc7j31rs0g7hdt.jpg)
![butterworth_output](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg8rsf46j31rs0fub29.jpg)
![exponential_spectrum](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg94hrdij31rs0g7e81.jpg)
![exponential_output](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg98ec3rj31rs0fub29.jpg)
通過對比可以發現,使用理想高通濾波器得到的結果有明顯的振鈴現象,而 Butterworth 高通濾波器和指數高通濾波器的結果相近,均具有較好的效果。從頻譜圖中也可以看出,理想高通濾波器對頻率的截斷非常陡峭,在臨界點發生了突變,而後兩者的濾波比較平滑,是一種在高低頻率間逐漸過渡的過程。
## 微分運算元法
針對由於平均或積分運算而引起的影象模糊,可用微分運算來實現影象的銳化。微分運算是求訊號的變化率,有加強高頻分量的作用,從而使影象輪廓清晰。
常見的邊緣檢測運算元包括:Roberts 運算元、Sobel 運算元、Laplacian 運算元、Canny 運算元等。各種運算元的存在就是對這種導數分割原理進行的例項化計算,是為了在計算過程中直接使用的一種計算單位。實際使用時,通常用各種運算元對應的模板對原圖進行卷積運算,從而提取出影象的邊緣資訊。
上述各運算元的具體定義就不展開贅述了,具體可以參考相關書籍或文章。這裡通過一張表對這些運算元進行簡要的介紹和比較。
| 運算元 | 介紹及優缺點比較 |
| --------- | ------------------------------------------------------------ |
| Roberts | 一種最簡單的運算元,採用對角線方向相鄰兩畫素之差近似梯度幅值檢測邊緣。檢測垂直邊緣的效果好於斜向邊緣,定位精度高,但是對噪聲敏感,對具有陡峭邊緣且含噪聲少的影象效果較好。 |
| Sobel | 根據畫素點上下左右四鄰域灰度加權差檢測邊緣,類似區域性平均運算,因此對噪聲具有平滑作用,對灰度漸變和噪聲較多的影象處理效果比較好,對邊緣定位比較準確。 |
| Laplacian | 屬於二階微分運算元,在只考慮邊緣點的位置而不考慮周圍的灰度差時適合用該運算元進行檢測。對噪聲非常敏感,只適用於無噪聲影象。存在噪聲的情況下,使用該運算元檢測邊緣之前需要先進行低通濾波,因此通常把 Laplacian 運算元和平滑運算元結合起來生成一個新的模板。 |
| Canny | 該運算元功能比前面幾種都要好,不容易受噪聲的干擾,能夠檢測到真正的弱邊緣,但是實現起來較為麻煩,是一個具有濾波、增強、檢測的多階段的優化運算元。在進行處理前,Canny 運算元先利用高斯平滑濾波器來平滑影象以除去噪聲。Canny 分割演算法採用一階偏導的有限差分來計算梯度幅值和方向,在處理過程中,該運算元還將經過一個非極大值抑制的過程,最後採用兩個閾值來連線邊緣。 |
### 程式碼實現
Roberts、Sobel 以及 Laplacian 運算元方法均為手工實現:先根據不同運算元對應的模板,分別定義對單個畫素塊的卷積運算函式,然後在影象上滑動模板,呼叫該函式計算每一個卷積塊,最終得到經過各運算元微分後的輸出影象。對於 Canny 運算元,由於其實現過程比較複雜,這裡選擇直接呼叫 OpenCV 中的 `Canny()` 函式。
#### 實現前三種運算元
```python
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))
```
#### 滑動計算卷積塊
```python
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 運算元
```python
canny_res = cv2.Canny(img, threshold1, threshold2) # 設定高低閾值引數
```
### 執行結果及分析
使用 Roberts 運算元、Laplacian 運算元以及水平和垂直兩個方向的 Sobel 運算元進行邊緣檢測所得到的結果如下圖所示:
![roberts_sobel_laplacian](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg9hi5rtj30u00u97wh.jpg)
可以看到,Roberts 運算元簡單但有效,已經能實現比較好的邊緣檢測效果;而 Laplacian 運算元的效果相對較差,邊緣不是很清晰,還出現了很多噪點;Sobel 運算元整體表現也較好,可以明顯地看出水平和垂直兩個方向上結果的差別,前者對水平邊緣響應最大(如眼眶、嘴脣、下巴處),後者對垂直邊緣響應最大(如鼻樑、兩側臉頰處)。
下圖顯示了使用 OpenCV 自帶的 Canny 運算元在不同閾值下的輸出結果,其中低閾值 `threshold1` 依次取 30、50、80、120、150,而高閾值 `threshold2` 根據 Canny 演算法的推薦,均取為 `threshold1` 的 3 倍。
![canny](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg9n26n2j31rq0cygn5.jpg)
低於 `threshold1` 的畫素被認為不是邊緣,高於 `threshold2` 的畫素被認為是邊緣,介於二者之間的則會根據相鄰的畫素點進一步確定。從圖中也可以看出,閾值設定得越高,對邊緣的過濾就越嚴格,輸出結果中的邊緣線條也越發稀疏。
### 噪聲測試
為了進一步研究在有噪聲的情況下各運算元的邊緣檢測效果,首先使用 `skimage` 庫中的函式,對 Lena 加入高斯噪聲和椒鹽噪聲。
```python
img_noise1 = skimage.util.random_noise(img, mode='gaussian')
img_noise2 = skimage.util.random_noise(img, mode='s&p')
```
![noise_lena](https://tva1.sinaimg.cn/large/007S8ZIlgy1getg9scbc5j30vs0g51kx.jpg)
然後分別在這兩張有噪聲的影象上應用 Roberts 運算元、Sobel 運算元(水平方向)、Laplacian 運算元和 Canny 運算元,得到的結果如下:
![gaussian](https://tva1.sinaimg.cn/large/007S8ZIlgy1getga33rgbj31rs0g7npd.jpg)
![s_p](https://tva1.sinaimg.cn/large/007S8ZIlgy1getga5uytkj31rs0g7b29.jpg)
其中,第一行是加入高斯噪聲後各運算元的輸出,第二行是加入椒鹽噪聲後各運算元的輸出。
可以發現,Roberts 運算元和 Sobel 運算元都有一定的抗噪聲能力,從圖中依然可以看出部分邊緣資訊,而在 Laplacian 運算元則對噪聲非常敏感,其輸出結果完全看不出任何邊緣。對於 Canny 運算元,需要將閾值設定得很高,才能得到比較好的效果,否則大量噪點也會被認為是邊緣。經過多次嘗試,最終將 `threshold1` 設為 180,將 `threshold2` 設為 `3 * threshold1`,得到了上圖中最後一列的結果。
實驗過程中還發現,在相同的閾值條件下,Canny 運算元對高斯噪聲的抵抗能力比對椒鹽噪聲的抵抗能力強。下圖展示了當 `threshold1` 取值為 100、120、140、160、180 時,Canny 運算元對加入了高斯噪聲和椒鹽噪聲的影象的邊緣檢測效果:
![canny_gaussian](https://tva1.sinaimg.cn/large/007S8ZIlgy1getga8lsydj31rq0cygnx.jpg)
![canny_s_p](https://tva1.sinaimg.cn/large/007S8ZIlgy1getgadixdlj31rq0cyach.jpg)
## 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 倉庫**
## 參考資料
1. 王一丁,李琛,王蘊紅.《數字影象處理》.西安電子科技大學出版社
2. Xie, Saining, and Zhuowen Tu. "Holistically-nested edge detection." *Proceedings of the IEEE international conference on computer vision*. 2015.
3. PyTorch-HED:https://github.com/sniklaus/pytorch-hed
4. 網際網路上的一些部落格、文章、資料