Python計算機視覺:影象邊緣檢測

如果需要檢測到影象裡面的邊緣,首先我們需要知道邊緣處具有什麼特徵。

對於一幅灰度影象來說,邊緣兩邊的灰度值肯定不相同,這樣我們才能分辨出哪裡是邊緣,哪裡不是。

因此,如果我們需要檢測一個灰度影象的邊緣,我們需要找出哪裡的灰度變化最大。顯然,灰度變化越大,對比度越強,邊緣就越明顯。

那麼問題來了,我們怎麼知道哪裡灰度變化大,哪裡灰度變化小呢?

導數,梯度,邊緣資訊

在數學中,與變化率有關的就是導數。

如果灰度影象的畫素是連續的(實際不是),那麼我們可以分別原影象G對x方向和y方向求導數

Python計算機視覺:影象邊緣檢測

獲得x方向的導數影象Gx和y方向的導數影象Gy。Gx和Gy分別隱含了x和y方向的灰度變化資訊,也就隱含了邊緣資訊。

如果要在同一影象上包含兩個方向的邊緣資訊,我們可以用到梯度。(梯度是一個向量)

原影象的梯度向量Gxy為(Gx,Gy),梯度向量的大小和方向可以用下面兩個式子計算

Python計算機視覺:影象邊緣檢測

Python計算機視覺:影象邊緣檢測

角度值好像需要根據向量所在象限不同適當+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運算元為

Python計算機視覺:影象邊緣檢測

y方向的Prewitt運算元為

Python計算機視覺:影象邊緣檢測

原影象和一個運算元進行卷積的大概過程如下

如果影象矩陣中一塊區域為

Python計算機視覺:影象邊緣檢測

那麼x5處的x方向的導數是,將x方向運算元的中心和x5重合,然後對應元素相乘再求和,即

x5處的x方向導數為x3+x6+x9-x1-x4-x7

對矩陣中所有元素進行上述計算,就是卷積的過程。

因此,利用原影象和x方向Prewitt運算元進行卷積就可以得到影象的x方向導數矩陣Gx,

利用原影象和y方向Prewitt運算元進行卷積就可以得到影象的y方向導數矩陣Gy。

利用公式

Python計算機視覺:影象邊緣檢測

就可以得到影象的梯度矩陣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方向導數影象,右圖為梯度影象

Python計算機視覺:影象邊緣檢測

從圖中可以看出,Prewitt運算元雖然能檢測出影象邊緣,但是檢測結果較為粗糙,還帶有大量的噪聲。

近似導數的Sobel運算元

Sobel運算元與Prewitt比較類似,但是它比Prewitt運算元要好一些。

x方向的Sobel運算元為

Python計算機視覺:影象邊緣檢測

y方向的Sobel運算元為

Python計算機視覺:影象邊緣檢測

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方向導數影象,右圖為梯度影象

Python計算機視覺:影象邊緣檢測

從圖中看出,比較Prewitt運算元和Sobel運算元,Sobel運算元稍微減少了一點噪聲,但噪聲還是比較多的。

近似二階導數的Laplace運算元

Laplace運算元是一個二階導數的運算元,它實際上是一個x方向二階導數和y方向二階導數的和的近似求導運算元。

實際上,Laplace運算元是透過Sobel運算元推匯出來的。

Laplace運算元為

Python計算機視覺:影象邊緣檢測

Laplace還有一種擴充套件運算元為

Python計算機視覺:影象邊緣檢測

為了不再重複造輪子,這次我們運用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擴充套件運算元結果

Python計算機視覺:影象邊緣檢測

從結果可以看出,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()

結果如下圖

Python計算機視覺:影象邊緣檢測

從圖中可以看出,經過降噪處理後,邊緣效果較為明顯。