標籤:
我是一名初學者,如果你發現文中有錯誤,請留言告訴我,謝謝
如果需要檢測到映像裡面的邊緣,首先我們需要知道邊緣處具有什麼特徵。
對於一幅灰階映像來說,邊緣兩邊的灰階值肯定不相同,這樣我們才能分辨出哪裡是邊緣,哪裡不是。
因此,如果我們需要檢測一個灰階映像的邊緣,我們需要找出哪裡的灰階變化最大。顯然,灰階變化越大,對比越強,邊緣就越明顯。
那麼問題來了,我們怎麼知道哪裡灰階變化大,哪裡灰階變化小呢?
導數,梯度,邊緣資訊
在數學中,與變動率有關的就是導數。
如果灰階映像的像素是連續的(實際不是),那麼我們可以分別原映像G對x方向和y方向求導數
,
獲得x方向的導數映像Gx和y方向的導數映像Gy。Gx和Gy分別隱含了x和y方向的灰階變化資訊,也就隱含了邊緣資訊。
如果要在同一映像上包含兩個方向的邊緣資訊,我們可以用到梯度。(梯度是一個向量)
原映像的梯度向量Gxy為(Gx,Gy),梯度向量的大小和方向可以用下面兩個式子計算
角度值好像需要根據向量所在象限不同適當+pi或者-pi。
梯度向量大小就包含了x方向和y方向的邊緣資訊。
映像導數
實際上,映像矩陣是離散的。
連續函數求變動率用的是導數,而離散函數求變動率用的是差分。
差分的概念很容易理解,就是用相鄰兩個數的差來表示變動率。
下面公式是向後差分
x方向的差分:Gx(n,y) = G(n,y)-G(n-1,y)
y方向的差分:Gy(x,n) = G(x,n)-G(x,n-1)
實際計算映像導數時,我們是通過原映像和一個運算元進行卷積來完成的(這種方法是求映像的近似導數)。
最簡單的求映像導數的運算元是 Prewitt運算元 :
x方向的Prewitt運算元為
y方向的Prewitt運算元為
---------------------------------------------
原映像和一個運算元進行卷積的大概過程如下
如果映像矩陣中一塊地區為
那麼x5處的x方向的導數是,將x方向運算元的中心和x5重合,然後對應元素相乘再求和,即
x5處的x方嚮導數為x3+x6+x9-x1-x4-x7
對矩陣中所有元素進行上述計算,就是卷積的過程。
--------------------------------------------
因此,利用原映像和x方向Prewitt運算元進行卷積就可以得到映像的x方嚮導數矩陣Gx,
利用原映像和y方向Prewitt運算元進行卷積就可以得到映像的y方嚮導數矩陣Gy。
利用公式
就可以得到映像的梯度矩陣Gxy,這個矩陣包含映像x方向和y方向的邊緣資訊。
Python實現卷積及Prewitt運算元的邊緣檢測
首先我們把映像卷積函數封裝在一個名為imconv的函數中
import numpy as npfrom PIL import Imagedef imconv(image_array,suanzi): ‘‘‘計算卷積 參數 image_array 原灰階映像矩陣 suanzi 運算元 返回 原映像與運算元卷積後的結果矩陣 ‘‘‘ image = image_array.copy() # 原映像矩陣的深拷貝 dim1,dim2 = image.shape # 對每個元素與運算元進行乘積再求和(忽略最外圈邊框像素) for i in range(1,dim1-1): for j in range(1,dim2-1): image[i,j] = (image_array[(i-1):(i+2),(j-1):(j+2)]*suanzi).sum() # 由於卷積後灰階值不一定在0-255之間,統一化成0-255 image = image*(255.0/image.max()) # 返回結果矩陣 return image
然後我們利用Prewitt運算元計算x方嚮導數矩陣Gx,y方嚮導數矩陣Gy,和梯度矩陣Gxy。
import numpy as npimport matplotlib.pyplot as plt# x方向的Prewitt運算元suanzi_x = np.array([[-1, 0, 1], [ -1, 0, 1], [ -1, 0, 1]])# y方向的Prewitt運算元suanzi_y = np.array([[-1,-1,-1], [ 0, 0, 0], [ 1, 1, 1]])# 開啟映像並轉化成灰階映像image = Image.open("pika.jpg").convert("L")# 轉化成映像矩陣image_array = np.array(image)# 得到x方向矩陣image_x = imconv(image_array,suanzi_x)# 得到y方向矩陣image_y = imconv(image_array,suanzi_y)# 得到梯度矩陣image_xy = np.sqrt(image_x**2+image_y**2)# 梯度矩陣統一到0-255image_xy = (255.0/image_xy.max())*image_xy# 繪出映像plt.subplot(2,2,1)plt.imshow(image_array,cmap=cm.gray)plt.axis("off")plt.subplot(2,2,2)plt.imshow(image_x,cmap=cm.gray)plt.axis("off")plt.subplot(2,2,3)plt.imshow(image_y,cmap=cm.gray)plt.axis("off")plt.subplot(2,2,4)plt.imshow(image_xy,cmap=cm.gray)plt.axis("off")plt.show()
Prewitt運算元 的結果如所示
上方:左圖為原映像,右圖為x方嚮導數映像
下方:左圖為y方嚮導數映像,右圖為梯度映像
可以看出,Prewitt運算元雖然能檢測出映像邊緣,但是檢測結果較為粗糙,還帶有大量的雜訊。
近似導數的Sobel運算元
Sobel運算元與Prewitt比較類似,但是它比Prewitt運算元要好一些。
x方向的Sobel運算元為
y方向的Sobel運算元為
python代碼只需要將上面代碼中的Prewitt運算元改成Sobel運算元即可。
# x方向的Sobel運算元suanzi_x = np.array([[-1, 0, 1], [ -2, 0, 2], [ -1, 0, 1]])# y方向的Sobel運算元suanzi_y = np.array([[-1,-2,-1], [ 0, 0, 0], [ 1, 2, 1]])
Sobel運算元 的結果如所示
上方:左圖為原映像,右圖為x方嚮導數映像
下方:左圖為y方嚮導數映像,右圖為梯度映像
看出,比較Prewitt運算元和Sobel運算元,Sobel運算元稍微減少了一點雜訊,但雜訊還是比較多的。
未完,待續
參考列表
1.《python電腦視覺編程》
python電腦視覺2:映像邊緣檢測