
3.9.1 形态学图像处理
本节介绍如何使用morphology模块实现二值图像处理。二值图像中每个像素的颜色只有两种:黑色和白色。在NumPy中可以用二维布尔数组表示:False表示黑色,True表示白色。也可以用无符号单字节整型(uint8)数组表示:0表示黑色,非0表示白色。
下面的两个函数用于显示形态学图像处理的结果:
import numpy as np def expand_image(img, value, out=None, size = 10): if out is None: w, h = img.shape out = np.zeros((w* size, h* size),dtype=np.uint8) tmp = np.repeat(np.repeat(img,size,0),size,1) out[:,:] = np.where(tmp, value, out) out[::size,:] = 0 out[:,::size] = 0 return out def show_image(* imgs): for idx, img in enumerate(imgs, 1): ax = pl.subplot(1, len(imgs), idx) pl.imshow(img, cmap="gray") ax.set_axis_off( ) pl.subplots_adjust(0.02, 0, 0.98, 1, 0.02, 0)
1.膨胀和腐蚀
二值图像最基本的形态学运算是膨胀和腐蚀。膨胀运算是将与某物体(白色区域)接触的所有背景像素(黑色区域)合并到该物体中的过程。简单来说,就是对于原始图像中的每个白色像素进行处理,将其周围的黑色像素都设置为白色像素。这里的“周围”是一个模糊概念,在实际运算时,需要明确给出“周围”的定义。图3-50是膨胀运算的一个例子,其中下左图是原始图像,中间的图是四连通定义的“周围”的膨胀效果,下右图是八连通定义的“周围”的膨胀效果。图中用灰色方块表示由膨胀处理添加进物体的像素。

图3-50 四连通和八连通的膨胀运算
from scipy.ndimage import morphology def dilation_demo(a, structure=None): b = morphology.binary_dilation(a, structure) img = expand_image(a, 255) return expand_image(np.logical_xor(a,b), 150, out=img) a = pl.imread("scipy_morphology_demo.png")[:,:,0].astype(np.uint8) img1 = expand_image(a, 255) img2 = dilation_demo(a) img3 = dilation_demo(a, [[1,1,1],[1,1,1],[1,1,1]]) show_image(img1, img2, img3)
四连通包括上下左右4个像素,而八连通则还包括4个斜线方向上的邻接像素。它们都可以使用下面的正方形矩阵定义,其中正中心的元素表示当前要进行运算的像素,而其周围的1和0表示对应位置的像素是否算作其“周围”像素。这种矩阵描述了周围像素和当前像素之间的关系,被称作结构元素(structuring element)。
四连通八连通 0 1 0 1 1 1 1 1 1 1 1 1 0 1 0 1 1 1
假设数组a是一个表示二值图像的数组,可以用如下语句对其进行膨胀运算:
binary_dilation(a)
binary_dilation( )默认使用四连通进行膨胀运算,通过structure参数可以指定其他的结构元素。下面是进行八连通膨胀运算的语句:
binary_dilation(a, structure=[[1,1,1],[1,1,1],[1,1,1]])
通过设置不同的结构元素,能够实现各种不同的效果,图3-51显示了三种不同结构元素的膨胀效果。图中的结构元素分别为:

图3-51 不同结构元素的膨胀效果
左中右 0 0 0 0 1 0 0 1 0 1 1 1 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0
img4 = dilation_demo(a, [[0,0,0],[1,1,1],[0,0,0]]) img5 = dilation_demo(a, [[0,1,0],[0,1,0],[0,1,0]]) img6 = dilation_demo(a, [[0,1,0],[0,1,0],[0,0,0]]) show_image(img4, img5, img6)
binary_erosion()的腐蚀运算正好和膨胀相反,它将“周围”有黑色像素的白色像素设置为黑色。图3-52是四连通和八连通腐蚀的效果,图中用灰色方块表示被腐蚀的像素。

图3-52 四连通和八连通的腐蚀运算
def erosion_demo(a, structure=None): b = morphology.binary_erosion(a, structure) img = expand_image(a, 255) return expand_image(np.logical_xor(a,b), 100, out=img) img1 = expand_image(a, 255) img2 = erosion_demo(a) img3 = erosion_demo(a, [[1,1,1],[1,1,1],[1,1,1]]) show_image(img1, img2, img3)
2.Hit和Miss
Hit和Miss是二值形态学图像处理中最基本的运算,因为几乎所有其他的运算都可以用Hit和Miss的组合推演出来。它对图像中的每个像素周围的像素进行模式判断,如果周围像素的黑白模式符合指定的模式,将此像素设为白色,否则设置为黑色。因为它需要同时对白色和黑色像素进行判断,因此需要指定两个结构元素。进行Hit和Miss运算的binary_hit_or_miss()的调用形式如下:
binary_hit_or_miss(input, structure1=None, structure2=None, ...)
其中structure1参数指定白色像素的结构元素,而structure2参数则指定黑色像素的结构元素。图3-53是binary_hit_or_miss()的运算结果。其中下左图为原始图像,中图为使用下面两个结构元素进行运算的结果:

图3-53 Hit和Miss运算
白色结构元素 黑色结构元素 0 0 0 1 0 0 0 1 0 0 0 0 1 1 1 0 0 0
在这两个结构元素中,0表示不关心其对应位置的像素的颜色,1表示其对应位置的像素必须为结构元素所表示的颜色。因此通过这两个结构元素可以找到“下方三个像素为白色,并且左上像素为黑色的白色像素”。
与下右图对应的结构元素如下,通过它可以找到“下方三个像素为白色、左上像素为黑色的黑色像素”。
白色结构元素 黑色结构元素 0 0 0 1 0 0 0 0 0 0 1 0 1 1 1 0 0 0
def hitmiss_demo(a, structure1, structure2): b = morphology.binary_hit_or_miss(a, structure1, structure2) img = expand_image(a, 100) return expand_image(b, 255, out=img) img1 = expand_image(a, 255) img2 = hitmiss_demo(a, [[0,0,0],[0,1,0],[1,1,1]], [[1,0,0],[0,0,0],[0,0,0]]) img3 = hitmiss_demo(a, [[0,0,0],[0,0,0],[1,1,1]], [[1,0,0],[0,1,0],[0,0,0]]) show_image(img1, img2, img3)
使用Hit和Miss运算的组合,可以实现复杂的图像处理。例如文字识别中常用的细线化运算就可以用一系列的Hit和Miss运算实现。图3-54显示了细线化处理的效果,实现程序如下:

图3-54 使用Hit和Miss进行细线化运算
def skeletonize(img): h1 = np.array([[0, 0, 0],[0, 1, 0],[1, 1, 1]]) ❶ m1 = np.array([[1, 1, 1],[0, 0, 0],[0, 0, 0]]) h2 = np.array([[0, 0, 0],[1, 1, 0],[0, 1, 0]]) m2 = np.array([[0, 1, 1],[0, 0, 1],[0, 0, 0]]) hit_list = [] miss_list = [] for k in range(4): ❷ hit_list.append(np.rot90(h1, k)) hit_list.append(np.rot90(h2, k)) miss_list.append(np.rot90(m1, k)) miss_list.append(np.rot90(m2, k)) img = img.copy() while True: last = img for hit, miss in zip(hit_list, miss_list): hm = morphology.binary_hit_or_miss(img, hit, miss) ❸ # 从图像中删除hit_or_miss所得到的白色点 img = np.logical_and(img, np.logical_not(hm)) ❹ # 如果处理之后的图像和处理前的图像相同,则结束处理 if np.all(img == last): ❺ break return img a = pl.imread("scipy_morphology_demo2.png")[:,:,0].astype(np.uint8) b = skeletonize(a)
❶以图3-55所示的两个结构元素为基础,构造4个形状为(3, 3)的二维数组:h1、m1、h2、m2。其中h1和m1对应图中左边的结构元素,而h2和m2对应图中右边的结构元素,h1和h2对应白色结构元素,m1和m2对应黑色结构元素。❷将这些结构元素进行90°、180°、270°旋转之后一共得到8个结构元素。

图3-55 细线化算法的4个结构元素
❸依次使用这些结构元素进行Hit和Miss运算,❹并从图像中删除运算所得到的白色像素,其效果就是依次从8个方向删除图像的边缘上的像素。❺重复运算直到没有像素可删除为止。