數學形態學(mathematical morphology)關注的是圖像中的形狀,它提供了一些方法用於檢測形狀和改變形狀。起初是基於二值圖像提出的,後來擴展到灰度圖像。二值圖像就是:每一個像素的值只能是0或1,1表明描繪圖像的點,0表明背景。python
基本的形態學運算包括:腐蝕(erosion)、膨脹(dilation)、開(opening)、閉(closing),對於這些運算,都須要用到被稱爲結構元素(Structuring element)的模板,通常爲方形,以小矩陣的形式表示,但它的元素的值只能是0或1,它表明的是一個集合,這個集合罩在原圖像上,能夠跟原圖像的形狀進行集合運算。算法
要講清楚去處過程不容易,直接上圖看效果:
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]])
相似地:
如圖(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.]])
咱們從以上的效果圖能夠看到,腐蝕和膨脹能夠改變形狀,同時也能夠去背景噪點。
另外,把形狀的膨脹結果減去它的腐蝕結果,能夠獲得形狀的粗略邊緣以及角點。
先對原圖像進行腐蝕,再膨脹,就是開運算。有什麼用呢?簡單點說它能夠去除與結構元素大小至關的孔洞和碎片。若是一處圖像中有多個形狀,開運算能夠把那些只有一點點粘連的形狀分開。由於那點粘連的地方被去除了。
簡單示例:
>>> 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]]) #還能夠用於平滑邊角,也就是四角處縮小變平滑了,若是形狀與形狀有邊角的粘連,就能夠分開
與開運算相反,先對原圖進行膨脹,再腐蝕,就是閉運算。閉運算能夠填充圖像中的孔洞,鏈接一些缺口和碎片,變成塊狀。舉個應用場景——車牌定位,以下圖:
右圖是使用經過簡單的算法獲得車的粗略邊角,車牌位置像是一堆散點,若是對這個邊角圖運用閉運算能夠獲得這樣的效果:
車牌的位置變成一個接近車牌形狀的矩形,爲下一步檢測提供了便利。
閉運算函數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]]) #應用閉運算以後,空洞被填充了
開閉運算原理看似簡單,但很強大,只要結構元素選取得當,能夠作不少事情。
這裏說的對象是指圖像中與周圍沒有連通的單獨的形狀,咱們的目標是要計算這些對象的個數,計算對象個數可使用函數:
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()
下一節學習圖像去噪。
你還能夠查看其它筆記。
圖像的膨脹與腐蝕
數學形態學基本操做及其應用《計算機視覺特徵提取與圖像處理(第三版)》