邊緣檢測(edge detection)是最重要的圖像處理技術之一,圖像邊緣檢測大幅度地減小了數據量,而且剔除了能夠認爲不相關的信息,保留了圖像重要的結構屬性,爲後續圖像理解方法提供了基礎。html
從視覺上看,圖像中的邊緣處亮度較周圍強,好比對一垂直方向的邊緣,能夠經過水平方向像素亮度的一階微分(導數)來加強亮度變化。因此邊緣檢測能夠經過計算圖像水平和垂直兩個方向的亮度變化梯度,從而獲得亮度變化的幅度和方向。python
設Ix和Iy分別是兩個方向的亮度梯度向量,兩個向量的模就是梯度幅度:算法
兩個向量的夾角就是變化方向:chrome
簡單的亮度變化計算也可使用差分方法,好比對於水平方向的某像素,將它左右相鄰像素的差值做爲亮度變化的估算,垂直方向類推,也能夠估算出圖像的邊緣。由全部邊緣加強像素組成的新圖像,稱爲邊緣加強圖像。邊緣加強圖像能夠經過羣運算來得出。segmentfault
若是對原圖像進行像素運算得出新圖像,新圖像的每個像素點須要從原圖像周圍點基於某個算法計算出來的,這種運算叫羣運算。羣運算一般以圖像和模板的卷積形式來表示,這裏說的模板,就是決定周圍像素如何綜合計算的算法,也叫算子(operator)。上篇筆記介紹的高斯模糊介紹太高斯平均算子,算子以一個小矩陣的形式表示,每一個元素標明瞭對應像素的權值或係數。下面這張示意圖能夠幫助理解:函數
圖中的convolution mask指的就是模板或算子,它就像一個移動的遮罩層同樣,遮罩在原圖像上面,遮罩部分的每一個像素,各自與模板對應位置的權值作乘積,最後所有加起來做爲模板中心點對應的原圖像位置的新像素值。性能
上述使用差分方法得出亮度變化梯度其實就是一階微分的近似值。差分除了可使用沿座標軸方向的兩個像素來計算,也可使用對角像素來計算。Roberts交叉算子就屬於這種算子,是最先的邊緣檢測算子之一。Roberts交叉算子能夠準肯定位邊緣處,但這些只檢測亮度加強位置的基本算子,對噪聲敏感,容易將噪聲誤當成邊緣。spa
在計算亮度變化以前,先對周圍像素進行均值處理,這樣對噪聲有必定的抑制做用,可是,邊緣處會產生模糊,邊緣的定位不如Roberts算子。.net
Sobel算子
Sobel是應用比較多的算子,它也考慮均值處理來抑制噪聲,但它的技巧是隻在一條軸上進行均值處理,而在另外一條軸上加大權值來計算亮度變化。這樣得出來的效果比前二者更好。雖然使用Sobel算子得出的邊緣不如Canny算子的準確,但它在實際應用中效率比Canny高,在不少實際應用的場合成爲首選,尤爲是對效率要求較高,而對細紋理不太關心的時候。3d
Prewitt和Sobel在計算導數方法上都存在一些缺陷:濾波器的尺度須要隨着圖像分辨率的變化而變化。爲了在圖像噪聲方面更穩健,以及在任意尺度上計算導數,咱們可使用高斯導數濾波器。即咱們上個筆記用到的高斯模糊濾波器:scipy.ndimage.filters.gaussian_filter 還能夠用來計算一階、二階和三階導數,經過order參數來指定,order可取如下值的組合:
0: 表示使用高斯核作卷積
1: 使用一階高斯導數
2: 使用二階導數
3: 使用三階導數
好比order = (1,0)表示對輸入數據的每一維,先用1階導數作卷積,而後再與高斯核作卷積。
scipy.ndimage除了高斯濾波器,還提供了Prewitt和Sobel濾波器,下面咱們將使用這三種方法來生成邊緣加強圖像:
from PIL import Image import numpy as np from scipy.ndimage import filters import matplotlib.pyplot as plt im = np.array(Image.open('Valve_original.png').convert('L')) #prewitt pwimx = np.zeros(im.shape) filters.prewitt(im, 1, pwimx) pwimy = np.zeros(im.shape) filters.prewitt(im, 0, pwimy) pwmagnitude = np.sqrt(pwimx ** 2 + pwimy ** 2) #計算兩個向量的模,同:np.hypot(pwimx, pwimy) #sobel sbimx = np.zeros(im.shape) filters.sobel(im, 1, sbimx) sbimy = np.zeros(im.shape) filters.sobel(im, 0, sbimy) sbmagnitude = np.sqrt(sbimx ** 2 + sbimy ** 2) #gaussian gsimx = np.zeros(im.shape) filters.gaussian_filter(input = im, sigma = 1, order = (0,1), output = gsimx) gsimy = np.zeros(im.shape) filters.gaussian_filter(input = im, sigma = 1, order = (1,0), output = gsimy) gsmagnitude = np.sqrt(gsimx ** 2 + gsimy ** 2) plt.gray() index = 221 plt.subplot(index) plt.imshow(im) plt.title('original') plt.axis('off') plt.subplot(index + 1) plt.imshow(pwmagnitude) plt.title("prewitt") plt.axis('off') plt.subplot(index + 2) plt.imshow(sbmagnitude) plt.title("sobel") plt.axis('off') plt.subplot(index + 3) plt.imshow(gsmagnitude) plt.title("gaussian") plt.axis('off') plt.show()
效果圖以下:
光從上面效果圖很難看出Prewitt和Sobel的區別,也許他們在噪聲大的圖像上才能顯出區別。
Canny是目前定義最嚴謹的邊緣檢測算法,用Cannel計算出的邊緣很細小,鏈接性好,有必定的抗噪做用,同時具備精準的邊緣定位。它的計算過程相比以上介紹的方法複雜:
先使用高斯濾波平滑圖像,去除一些噪聲,同時,一些極細小的邊緣也會丟掉。
計算圖像的亮度變化梯度
運用非極大值抑制(non-maximum suppression)(一種邊緣細化技術)減少梯度變化幅度
使用雙閾值檢測邊緣
邊緣鏈接檢測
下面這張圖是從維基找到的,咱們能夠看到邊緣效果比上面的要細得多:
在scipy.ndimage裏面,沒有提供Canny濾波器,但有一個圖像處理庫叫scikit-image(簡稱skimage)有提供Canny函數和示例。
以上介紹的是一階微分算子,如下是二階微分算子:
Laplacian算子是基於二階微分的邊緣檢測,二階微分相比一階微分產生的邊緣更細,因二階微分處理對細節有較強的響應,因此應用Laplacian產生的邊緣加強圖像保留了原圖像較多的背景細節。
因此,Laplacian算子也可用於圖像銳化處理和斑點檢測。
這是一種把高斯濾波和Laplacian二階導數結合起來的算子。值得一提的是上面介紹的Canny算法也是參考了LoG,在邊緣檢測以前,先對原圖像應用高斯濾波來平滑圖像。
邊緣檢測算法主要是基於圖像強度的一階和二階導數,但導數的計算對噪聲很敏感,所以必須使用濾波器來改善與噪聲有關的邊緣檢測器的性能。須要指出,大多數濾波器在下降噪聲的同時也致使了細小邊緣的丟失,目前也存在一種叫各向異性擴散的平滑技術,使得平滑不在邊緣上做用,所以,使用時須要折中選擇。
另外,計算出像素的亮度導數以後,下一步要作的就是給出一個閾值來肯定哪裏是邊緣位置。閾值越低,可以檢測出的邊線越多,結果也就越容易受到圖片噪聲的影響。與此相反,一個高的閾值將會遺失細的或者短的邊緣線段。因此,閾值的選取與實際的應用結合起來考慮會讓檢測結果更好。目前,也有根據背景自動計算閾值的算法。
下一節介紹圖像處理的形態學基本運算。
你還能夠查看其它筆記。