Python計算機視覺:影象邊緣檢測
如果需要檢測到影象裡面的邊緣,首先我們需要知道邊緣處具有什麼特徵。
對於一幅灰度影象來說,邊緣兩邊的灰度值肯定不相同,這樣我們才能分辨出哪裡是邊緣,哪裡不是。
因此,如果我們需要檢測一個灰度影象的邊緣,我們需要找出哪裡的灰度變化最大。顯然,灰度變化越大,對比度越強,邊緣就越明顯。
那麼問題來了,我們怎麼知道哪裡灰度變化大,哪裡灰度變化小呢?
導數,梯度,邊緣資訊
在數學中,與變化率有關的就是導數。
如果灰度影象的畫素是連續的(實際不是),那麼我們可以分別原影象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的函式中 ( 實際上,scipy庫中的signal模組含有一個二維卷積的方法convolve2d() )
複製程式碼
import numpy as np
def 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 np
# 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-255
image_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運算元稍微減少了一點噪聲,但噪聲還是比較多的。
近似二階導數的Laplace運算元
Laplace運算元是一個二階導數的運算元,它實際上是一個x方向二階導數和y方向二階導數的和的近似求導運算元。
實際上,Laplace運算元是透過Sobel運算元推匯出來的。
Laplace運算元為
Laplace還有一種擴充套件運算元為
為了不再重複造輪子,這次我們運用scipy庫中signal模組的convolve()方法來計算影象卷積。
convolve()的第一個引數是原影象矩陣,第二個引數為卷積運算元,然後指定關鍵字引數mode=“same”(輸出矩陣大小和原影象矩陣相同)。
import numpy as np
from PIL import Image
import matplotlib。pyplot as plt
import matplotlib。cm as cm
import scipy。signal as signal # 匯入sicpy的signal模組
# Laplace運算元
suanzi1 = np。array([[0, 1, 0],
[1,-4, 1],
[0, 1, 0]])
# Laplace擴充套件運算元
suanzi2 = np。array([[1, 1, 1],
[1,-8, 1],
[1, 1, 1]])
# 開啟影象並轉化成灰度影象
image = Image。open(“pika。jpg”)。convert(“L”)
image_array = np。array(image)
# 利用signal的convolve計算卷積
image_suanzi1 = signal。convolve2d(image_array,suanzi1,mode=“same”)
image_suanzi2 = signal。convolve2d(image_array,suanzi2,mode=“same”)
# 將卷積結果轉化成0~255
image_suanzi1 = (image_suanzi1/float (image_suanzi1。max()))*255
image_suanzi2 = (image_suanzi2/float (image_suanzi2。max()))*255
# 為了使看清邊緣檢測結果,將大於灰度平均值的灰度變成255(白色)
image_suanzi1[image_suanzi1> image_suanzi1。mean()] = 255
image_suanzi2[image_suanzi2> image_suanzi2。mean()] = 255
# 顯示影象
plt。subplot(2,1,1)
plt。imshow(image_array,cmap=cm。gray)
plt。axis(“off”)
plt。subplot(2,2,3)
plt。imshow(image_suanzi1,cmap=cm。gray)
plt。axis(“off”)
plt。subplot(2,2,4)
plt。imshow(image_suanzi2,cmap=cm。gray)
plt。axis(“off”)
plt。show()
結果如下圖
其中上方為原影象
下方:左邊為Laplace運算元結果,右邊為Laplace擴充套件運算元結果
從結果可以看出,laplace運算元似乎比前面兩個運算元(prewitt運算元和Sobel運算元)要好一些,噪聲減少了,但還是比較多。
而Laplace擴充套件運算元的結果看上去比Laplace的結果少一些噪聲。
降噪後進行邊緣檢測
為了獲得更好的邊緣檢測效果,可以先對影象進行模糊平滑處理,目的是去除影象中的高頻噪聲。
python程式如下
首先用標準差為5的5*5高斯運算元對影象進行平滑處理,然後利用Laplace的擴充套件運算元對影象進行邊緣檢測。
import numpy as np
from PIL import Image
import matplotlib。pyplot as plt
import matplotlib。cm as cm
import scipy。signal as signal
# 生成高斯運算元的函式
def func(x,y,sigma=1):
return 100*(1/(2*np。pi*sigma)) *np。exp(-((x-2)**2+(y-2)**2)/(2。0*sigma**2))
# 生成標準差為5的5*5高斯運算元
suanzi1 = np。fromfunction(func,(5,5),sigma=5)
# Laplace擴充套件運算元
suanzi2 = np。array([[1, 1, 1],
[1,-8, 1],
[1, 1, 1]])
# 開啟影象並轉化成灰度影象
image = Image。open(“pika。jpg”)。convert(“L”)
image_array = np。array(image)
# 利用生成的高斯運算元與原影象進行卷積對影象進行平滑處理
image_blur = signal。convolve2d(image_array, suanzi1, mode= “same”)
# 對平滑後的影象進行邊緣檢測
image2 = signal。convolve2d(image_blur, suanzi2, mode=“same”)
# 結果轉化到0-255
image2 = (image2/float(image2。max()))*255
# 將大於灰度平均值的灰度值變成255(白色),便於觀察邊緣
image2[image2>image2。mean()] = 255
# 顯示影象
plt。subplot(2,1,1)
plt。imshow(image_array,cmap=cm。gray)
plt。axis(“off”)
plt。subplot(2,1,2)
plt。imshow(image2,cmap=cm。gray)
plt。axis(“off”)
plt。show()
結果如下圖
從圖中可以看出,經過降噪處理後,邊緣效果較為明顯。