Programming Computer Vision with Python (學習筆記七)

數學形態學(mathematical morphology)關注的是圖像中的形狀,它提供了一些方法用於檢測形狀和改變形狀。起初是基於二值圖像提出的,後來擴展到灰度圖像。二值圖像就是:每一個像素的值只能是0或1,1表明描繪圖像的點,0表明背景。python

基本的形態學運算包括:腐蝕(erosion)膨脹(dilation)開(opening)閉(closing),對於這些運算,都須要用到被稱爲結構元素(Structuring element)的模板,通常爲方形,以小矩陣的形式表示,但它的元素的值只能是0或1,它表明的是一個集合,這個集合罩在原圖像上,能夠跟原圖像的形狀進行集合運算。算法

腐蝕(erosion)

要講清楚去處過程不容易,直接上圖看效果:
圖片描述segmentfault

圖中(a)爲原圖像,(b)爲腐蝕運算後結果,能夠看出除了字母筆刷變細了以外,黑色背景的噪點也都不見了,(c)是膨脹運算結果,字母筆刷比原圖像粗。數組

ok,如今看腐蝕是怎麼實現的,仍是先看圖:
圖片描述app

如圖所示,(a)是3×3結構元素,至關於:函數

array([[ 1.,  1.,  1.],
       [ 1.,  1.,  1.],
       [ 1.,  1.,  1.]])

圖中標識出了它的中心點。學習

結構元素的設置也能夠是其它大小,也不必定全是1(黑點),好比是一個3×3十字形spa

[[0,1,0],
 [1,1,1],
 [0,1,0]]

(b)爲待處理的原圖像,咱們把其中由全部黑點組成的集合設爲X3d

(c)爲腐蝕後的結果,黑色點就是通過腐蝕以後保留下來的點,灰色的點表示被排除出去的點,咱們看到的效果是X變小了一圈,這也之因此叫腐蝕的緣由吧。code

能夠這樣來形象理解腐蝕運算過程:將結構元素平移到原圖像上某個位置,若是結構元素中全部的黑點(值爲1)都落在X裏,就把結構元素中心點對應的原圖像的像素點保留下來,不然就排除出去,如(c)所示,假設結構元素蓋在這個位置,這時結構元素下半部還有幾個點沒落在原圖X中,因此將中心點對應的像素點排除出去,從黑色標記爲灰色。將結構元素在原圖像上進行平移,直到原圖像的每個像素都被處理過。

因此這個結果也會把形狀之外的噪點排除掉。

腐蝕函數說明

scipy.ndimage.morphology.binary_erosion(input, structure=None, iterations=1,...)

input: 原圖像二值圖
structure: 即結構元素,默認爲3×3十字形
iterations: 表示要連續應用腐蝕多少次

返回腐蝕後二值圖結果,ndarray類型

示例:

>>> a = np.zeros((7,7), dtype=np.int)
>>> a[1:6, 2:5] = 1
>>> a  #原圖像二值圖,注意中間由1組成的矩形形狀
array([[0, 0, 0, 0, 0, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 0, 1, 1, 1, 0, 0],
       [0, 0, 0, 0, 0, 0, 0]])
       
>>> ndimage.binary_erosion(a).astype(a.dtype) 
#能夠看出矩形形狀被"腐蝕"了一圈
array([[0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 1, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0],
       [0, 0, 0, 0, 0, 0, 0]])

膨脹(dilation)

相似地:
圖片描述

如圖(c)就是膨脹的結果,運算過程跟腐蝕相似,只不過對像素的排除判斷不同,膨脹的判斷方式是:只要結構元素中有一個黑點(值爲1)落在X集合裏,就把結構元素中心點對應的原圖像的像素點保留下來,不然就排除出去。

膨脹函數scipy.ndimage.morphology.binary_dilation與腐蝕相似,使用示例:

>>> a = np.zeros((5, 5))
>>> a[2, 2] = 1
>>> a
array([
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.],
[ 0., 0., 0., 0., 0.]])
>>> ndimage.binary_dilation(a).astype(a. dtype) #binary_dilation第二個參數可指定結構元素,默認爲3×3十字形
array([
[ 0., 0., 0., 0., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 1., 1., 1., 0.],
[ 0., 0., 1., 0., 0.],
[ 0., 0., 0., 0., 0.]])

咱們從以上的效果圖能夠看到,腐蝕和膨脹能夠改變形狀,同時也能夠去背景噪點。
另外,把形狀的膨脹結果減去它的腐蝕結果,能夠獲得形狀的粗略邊緣以及角點。

開(opening)

先對原圖像進行腐蝕,再膨脹,就是開運算。有什麼用呢?簡單點說它能夠去除與結構元素大小至關的孔洞和碎片。若是一處圖像中有多個形狀,開運算能夠把那些只有一點點粘連的形狀分開。由於那點粘連的地方被去除了。
簡單示例:

>>> a = np.zeros((5,5), dtype=np.int)
>>> a[1:4, 1:4] = 1; a[4, 4] = 1
>>> a #原圖像,注意右下角有個1,表示零散的碎片
array([[0, 0, 0, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 0, 0, 1]])

>>> ndimage.binary_opening(a, structure=np.ones((3,3))).astype(np.int)
array([[0, 0, 0, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 0, 0, 0]]) #基於3×3全1的結構元素應用開運算,把原圖像角落的1去掉
       
>>> ndimage.binary_opening(a).astype(np.int)
array([[0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 1, 0, 0],
       [0, 0, 0, 0, 0]]) #還能夠用於平滑邊角,也就是四角處縮小變平滑了,若是形狀與形狀有邊角的粘連,就能夠分開

閉(closing)

與開運算相反,先對原圖進行膨脹,再腐蝕,就是閉運算。閉運算能夠填充圖像中的孔洞,鏈接一些缺口和碎片,變成塊狀。舉個應用場景——車牌定位,以下圖:
005RM8OMgw1ep49c9d5j2j30ep06pgqc.jpg

右圖是使用經過簡單的算法獲得車的粗略邊角,車牌位置像是一堆散點,若是對這個邊角圖運用閉運算能夠獲得這樣的效果:
005RM8OMgw1ep49c9pdcjj306s066glk.jpg

車牌的位置變成一個接近車牌形狀的矩形,爲下一步檢測提供了便利。

閉運算函數ndimage.binary_closing的用法:

>>> a = np.zeros((5,5), dtype=np.int)
>>> a[1:-1, 1:-1] = 1; a[2,2] = 0
>>> a #原圖像,注意中間有個0,表示形狀裏面有個空洞
array([[0, 0, 0, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 0, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 0, 0, 0]])

>>> ndimage.binary_closing(a).astype(np.int)
array([[0, 0, 0, 0, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 1, 1, 1, 0],
       [0, 0, 0, 0, 0]])  #應用閉運算以後,空洞被填充了

開閉運算原理看似簡單,但很強大,只要結構元素選取得當,能夠作不少事情。

對象計數(Counting Objects)

這裏說的對象是指圖像中與周圍沒有連通的單獨的形狀,咱們的目標是要計算這些對象的個數,計算對象個數可使用函數:

label, num_features = scipy.ndimage.measurements.label(input, structure=None, output=None)
參數
input: 數組類型,其中元素非0值表示對象組成的點,0表示圖像背景
structure: 結構元素,用於檢測對象的連通特徵,默認是3×3十字形

返回值
label: 返回與input同樣的大小,可是把對象標記出來
num_features:對象的個數

用法簡單示例:

>>> a = np.array([[0,0,1,1,0,0],
...               [0,0,0,1,0,0],
...               [1,1,0,0,1,0],
...               [0,0,0,1,0,0]])
>>> labeled_array, num_features = measurements.label(a) #使用默認3×3十字形結構元素
>>> print(num_features)
4
>>> print(labeled_array) #打印被識別出來的對象的位置,分別用1,2,3...遞增的下標標記出來,因此labeled_array能夠當成灰度圖打印出來,被標識的對象的灰度從黑到白變化
array([[0, 0, 1, 1, 0, 0],
       [0, 0, 0, 1, 0, 0],
       [2, 2, 0, 0, 3, 0],
       [0, 0, 0, 4, 0, 0]])

從上面例子看出,使用默認3×3十字形結構元素,檢測時,只有水平和垂直連通才認爲像素屬於同一個對象,對角連通不算,若是要把對角連通看成是同一個對象來計算,能夠指定結構元素爲:

[[1,1,1],
 [1,1,1],
 [1,1,1]]

有時候,因受噪聲影響,對象之間有一點邊角的粘連,人眼能夠很容易分辨出是兩個對象,但要讓label函數理解這一點,可使用前面提到的開運算先對把對象稍微分開,再把結果傳給label函數進行計數,下面給出一個具體的圖像進行示例:

from PIL import Image
import numpy as np
from scipy.ndimage import measurements,morphology
import matplotlib.pyplot as plt

im = np.array(Image.open('house.png').convert('L'))
im = 1 * (im < 128) #把灰度圖像轉爲二值圖,即灰度少於128的當成圖像黑點,不然看成背景

label_from_origin, num_from_origin = measurements.label(im)

im_open = morphology.binary_opening(im, np.ones((9, 5)), iterations=2) #運用了一個9×5全1的結構元素,並連續應用兩次開運算
label_from_open, num_from_open = measurements.label(im_open)

#如下是畫圖
index = 221
plt.subplot(index)
plt.imshow(im)
plt.title('original')
plt.axis('off')

plt.subplot(index + 1)
plt.imshow(label_from_origin)
plt.title('%d objects' % num_from_origin)
plt.axis('off')

plt.subplot(index + 2)
plt.imshow(im_open)
plt.title('apply opening')
plt.axis('off')

plt.subplot(index + 3)
plt.imshow(label_from_open)
plt.title('%d objects' % num_from_open)
plt.axis('off')

#plt.gray()  #爲了更好的看出對象的分離,故意不用灰度顯示
plt.show()

效果圖以下,第二組(即第二行)是應用開運算以後的圖像及計算結果,跟第一組相比,對象計數增長了,我在第二組圖中圈出了應用開運算以後的主要變化之處:
圖片描述

小結

上面介紹的用於二值圖的一些函數,也有其對應的用於灰度圖像的函數,包括:

  • grey_erosion()

  • grey_dilation()

  • grey_opening()

  • grey_closing()

下一節學習圖像去噪。
你還能夠查看其它筆記

參考資料

圖像的膨脹與腐蝕
數學形態學基本操做及其應用《計算機視覺特徵提取與圖像處理(第三版)》

相關文章
相關標籤/搜索