
3.9.2 图像分割
下面以矩形区域识别为例,介绍如何使用measurements和morphology进行图像区域分割。我们要抽取矩形信息的图像如图3-56所示。这个图像是二值图像,其中矩形区域为白色,背景为黑色。但是由于它采用JPEG格式储存,因此用pyplot.imread()读取的是一个形状为(高,宽,3)的三通道图像。下面的程序使用其中的第0通道将其转换成二值数组squares,将矩形区域设置为1,将背景设置为0。结果如图3-56(左上)所示。

图3-56 矩形区域分割算法各个步骤的输出图像
squares = pl.imread("suqares.jpg") squares = (squares[:,:,0]<200).astype(np.uint8)
由于许多矩形都有一些小凸起与邻近的矩形连在一起,我们需要先将每块矩形与其周围的矩形分离出来。可以使用上节介绍的二值腐蚀函数morphology.binary_erosion()实现这一功能。不过这里我们采用另外的方法。
morphology.distance_transform_cdt(image)计算二值图像中每个像素到最近的黑色像素的距离,返回一个保存所有距离的数组。图像上两点之间的距离有很多定义方式,此函数默认采用切比雪夫距离。两点之间的切比雪夫距离定义为其各坐标数值差的最大值,即DChess =max(|x2 -x1|,|y2 -y1|)。
下面调用distance_transform_cdt(squares)得到距离数组squares_dt,并绘制成图。图中颜色越红的像素表示该点距离黑色背景越远,而原图中值为0的像素对应的距离为0,离黑色背景最远的距离为27个像素。如果将距离数组当作图像输出,显示效果如图3-56(中上)所示。
from scipy.ndimage import morphology squares_dt = morphology.distance_transform_cdt(squares) print "各种距离值", np.unique(squares_dt) 各种距离值 [ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27]
只需要选择合适的阈值将距离数组squares_dt转换成二值数组,就可以实现矩形区域的分离,其效果和腐蚀算法类似。squares_core中每个矩形区域都缩得足够小,以至于没有任何两块区域之间有连通的路径。其效果如图3-56(右上)所示,可以看到所有的区域都和其相邻的区域分割开了。
squares_core = (squares_dt>8).astype(np.uint8)
下面调用measurements中的label()和center_of_mess()对各个独立的白色区域进行染色,并计算各个区域的重心坐标。
labels()对二值图像中每个独立的白色区域使用唯一的整数进行填充,这相当于将每块区域染上不同的“颜色”。这里所谓的“颜色”是指为每个区域指定的一个整数,而不是指图像中像素的真正颜色。
labels()返回的结果数组可以用于计算各个区域的一些统计信息。例如可以调用center_of_mass()计算每个区域的重心。其第一个参数为各个像素的权值(可以认为是每个像素的质量密度),第二个参数为labels()输出的染色数组,第三个参数为需要计算重心的区域的标签列表。在下面的程序中,权值数组和染色数组相同,因此可以计算区域中所有白色像素的重心。
如果直接使用imshow()显示labels()输出的染色数组,将会得到一个区域颜色逐渐变化的图像,这样的图像不利于肉眼识别各个区域。因此下面的程序用random_palette()为每个整数分配一个随机颜色。其结果如图3-56(左下)所示,每个区域的重心使用白色小圆点表示。
from scipy.ndimage.measurements import label, center_of_mass def random_palette(labels, count, seed=1): np.random.seed(seed) palette = np.random.rand(count+1, 3) palette[0,:] = 0 return palette[labels] labels, count = label(squares_core) h, w = labels.shape centers = np.array(center_of_mass(labels, labels, index=range(1, count+1)), np.int) cores = random_palette(labels, count)
我们几乎完成了区域识别的任务,但是每个矩形区域都比原始图像中的要小一圈。下面将染色之后的矩形区域恢复到原始图像中的大小。即对于原始图像中为白色、腐蚀之后的图像中为黑色的每个像素,将其颜色设置为染色数组中与之最近的区域的颜色。具体的计算步骤如下:
❶将腐蚀之后的图像square_core反转,并调用distance_transform_cdt()。这样可以找到square_core中距离每个黑色像素最近的白色像素的坐标。为了让其返回坐标信息,需要设置参数return_indices为True。由于这里不需要距离信息,因此可以设置参数return_distances为False。其返回值index是一个形状为(2,高,宽)的三维数组。index[0]为最近像素的第0轴(纵轴)坐标,index[1]为最近像素的第1轴(横轴)坐标。
❷使用index[0]和index[1]获取labels中对应坐标的颜色,得到near_labels。
❸创建一个布尔数组mask,其中的每个True值对应squares中为白色、squares_core中为黑色的像素。将labels复制一份到labels2,最后将mask中True对应的near_labels中的颜色值复制到labels2中。
图3-56(中下)是使用random_palette()随机着色之后的结果。
index = morphology.distance_transform_cdt(1-squares_core, return_distances=False, return_indices=True) ❶ near_labels = labels[index[0], index[1]] ❷ mask = (squares - squares_core).astype(bool) labels2 = labels.copy() labels2[mask] = near_labels[mask] ❸ separated = random_palette(labels2, count)