python計算機視覺2:圖像邊緣檢測




我是一名初學者,如果你發現文中有錯誤,請留言告訴我,謝謝


 

如果需要檢測到圖像里面的邊緣,首先我們需要知道邊緣處具有什么特征。

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

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

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


 導數,梯度,邊緣信息 

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

如果灰度圖像的像素是連續的(實際不是),那么我們可以分別原圖像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
from PIL import Image

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
import 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-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()

 

結果如下圖

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


 

參考列表

1. 《python計算機視覺編程》 

2. 網絡(感謝百度,感覺網絡上分享知識的網友)


實際上,一些現成的Python庫已經對邊緣檢測過程進行了封裝,效果和效率更為出色。

文中以自己的python代碼進行邊緣檢測,實際上是想對實際過程有更好的認識和了解

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM