图像分割的概念
图像分割是将图像分割成不同的区域或类别,并使这些区域或类别对应于不同的目标或局部目标。每个区域包含具有相似属性的像素,并且图像中的每个像素都分配给这些类别之一。一个好的图像分割通常指同一类别的像素具有相似的强度值并形成一个连通区域,而相邻的不同类别的像素具有不同的值。这样做的目的是简化成改变图像的表示形式,使其更有意义、更易于分析。
如果分割做得好,那么图像分析的所有其他阶段都将变得更简单。因此,分割的质量和可靠性决定了图像分析是否成功。但是如何将图像分割成正确的片段通常是一个非常具有挑战性的问题。
分割技术可以是非上下文的(不考虑图像中特征和组像素之间的空间关系,只考虑一些全局属性,例如颜色或灰度),也可以是上下文的(另外利用空间关系,例如对具有相似灰度的空间封闭像素分组)。在本章中,我们将讨论不同的分割技术,并使用scikit-image、python-opencv(cv2)和SimpleITK库函数演示基于Python的图像分割实现。为此,从导入本章所需的库开始,如下面的代码所示:
import numpy as np
from skimage.transform import (hough_line, hough_line_peaks, hough_circle,
hough_circle_peaks)
from skimage.draw import circle_perimeter
from skimage.feature import canny
from skimage.data import astronaut
from skimage.io import imread, imsave
from skimage.color import rgb2gray, gray2rgb, label2rgb
from skimage import img_as_float
from skimage.morphology import skeletonize
from skimage import data, img_as_float
import matplotlib.pyplot as pylab
from matplotlib import cm
from skimage.filters import sobel, threshold_otsu
from skimage.feature import canny
from skimage.segmentation import felzenszwalb, slic, quickshift, watershed
from skimage.segmentation import mark_boundaries, find_boundaries
本节的这个例子源自scikit-image文档,它演示了如何从背景中分割目标。先使用基于边缘的分割算法,然后使用基于区域的分割算法。将源自skimage.data的硬币图像作为输入图像,在较幽暗的背景下勾勒出了硬币的轮廓。
如下代码实现了显示其灰度图像及其强度直方图:
coins = data.coins()
hist = np.histogram(coins, bins=np.arange(0, 256), normed=True)
fig, axes = pylab.subplots(1, 2, figsize=(20, 10))
axes[0].imshow(coins, cmap=pylab.cm.gray, interpolation='nearest')
axes[0].axis('off'), axes[1].plot(hist[1][:-1], hist[0], lw=2)
axes[1].set_title('histogram of gray values')
pylab.show()
运行上述代码,输出结果如图8-4所示。
图8-4 硬币图像及其灰度直方图
8.4.1 基于边缘的图像分割
在本例中,我们将尝试使用基于边缘的分割来描绘硬币的轮廓。为此,先使用Canny边缘检测器获取特征的边缘,如下面的代码所示:
edges = canny(coins, sigma=2)
fig, axes = pylab.subplots(figsize=(10, 6))
axes.imshow(edges, cmap=pylab.cm.gray, interpolation='nearest')
axes.set_title('Canny detector'), axes.axis('off'), pylab.show()
运行上述代码,使用Canny边缘检测器得到的硬币轮廓如图8-5所示。
图8-5 使用Canny边缘检测器得到的硬币轮廓
然后使用scipy ndimage模块中的形态学函数binary_fill_holes()填充这些轮廓,如下面的代码所示:
from scipy import ndimage as ndi
fill_coins = ndi.binary_fill_holes(edges)
fig, axes = pylab.subplots(figsize=(10, 6))
axes.imshow(fill_coins, cmap=pylab.cm.gray, interpolation='nearest')
axes.set_title('filling the holes'), axes.axis('off'), pylab.show()
运行上述代码,输出硬币的填充轮廓,如图8-6所示。
图8-6 硬币的填充轮廓
可以看到,有一枚硬币的轮廓没有被填满。在接下来的步骤中,我们将通过为有效目标设置最小尺寸,并再次使用形态学函数来删除诸如此类小伪目标。这次使用的是scikit-image形态学模块的remove_small_objects()函数,如下面的代码所示:
from skimage import morphology
coins_cleaned = morphology.remove_small_objects(fill_coins, 21)
fig, axes = pylab.subplots(figsize=(10, 6))
axes.imshow(coins_cleaned, cmap=pylab.cm.gray, interpolation='nearest')
axes.set_title('removing small objects'), axes.axis('off'), pylab.show()
运行上述代码,输出结果如图8-7所示。
图8-7 删除未填充的硬币轮廓
但这种方法并不是很健壮,因为没有完全闭合的轮廓没有被正确填充,如图8-6中还有一枚没有填充的硬币一样。
8.4.2 基于区域的图像分割
在本节中,我们将使用形态学分水岭算法对同一幅图像应用基于区域的分割方法。先直观地阐述分水岭算法的基本步骤。
形态学分水岭算法
任何灰度图像都可以看作一个地形地貌面。如果这个面从它的最小值被淹没,并且阻止了不同来源的水的汇合,那么图像就被分割成两个不同的集合,即集水盆和分水岭线。如果将这种变换应用于图像梯度,在理论上集水盆应与图像的同质的灰度区域(片段)相对应。
然而,在实际应用中,由于梯度图像中存在噪声或局部不规则性,使用变换会对图像进行过度分割。为了防止过度分割,使用一组预定义标记,并从这些标记开始对地表面进行注水淹没。因此,通过分水岭变换分割图像的步骤如下:
(1)找到标记和分割准则(用于分割区域的函数,通常是图像对比度/梯度);
(2)利用这两个元素运行标记控制的分水岭算法。
现在,使用scikit-image中的形态学分水岭算法实现从图像的背景中分离出前景硬币。首先,使用图像的sobel梯度找到图像的高程图,如下面的代码所示:
elevation_map = sobel(coins)
fig, axes = pylab.subplots(figsize=(10, 6))
axes.imshow(elevation_map, cmap=pylab.cm.gray, interpolation='nearest')
axes.set_title('elevation map'), axes.axis('off'), pylab.show()
运行上述代码,输出高程图,如图8-8所示。
图8-8 利用sobel梯度得到硬币图像的高程图
紧接着,基于灰度值直方图的极值部分,计算背景标记和硬币标记,如下面的代码所示:
markers = np.zeros_like(coins)
markers[coins < 30] = 1
markers[coins > 150] = 2
print(np.max(markers), np.min(markers))
fig, axes = pylab.subplots(figsize=(10, 6))
a = axes.imshow(markers, cmap=plt.cm.hot, interpolation='nearest')
plt.colorbar(a)
axes.set_title('markers'), axes.axis('off'), pylab.show()
运行上述代码,输出结果如图8-9所示,图中还示出标记数组的热度图。
图8-9 背景标记和硬币标记——热度图
最后,利用分水岭变换,从确定的标记点开始注入高程图的区域,如下面的代码所示:
segmentation = morphology.watershed(elevation_map, markers)
fig, axes = pylab.subplots(figsize=(10, 6))
axes.imshow(segmentation, cmap=pylab.cm.gray, interpolation='nearest')
axes.set_title('segmentation'), axes.axis('off'), pylab.show()
运行上述代码,输出使用形态学分水岭算法进行分割后所得到的二值图像,如图8-10所示。
图8-10 使用形态学分水岭算法进行分割后的二值图像
最后一种方法的效果更好,可以将硬币分割并单独标记出来,如下面的代码所示:
segmentation = ndi.binary_fill_holes(segmentation - 1)
labeled_coins, _ = ndi.label(segmentation)
image_label_overlay = label2rgb(labeled_coins, image=coins)
fig, axes = pylab.subplots(1, 2, figsize=(20, 6), sharey=True)
axes[0].imshow(coins, cmap=pylab.cm.gray, interpolation='nearest')
axes[0].contour(segmentation, [0.5], linewidths=1.2, colors='y')
axes[1].imshow(image_label_overlay, interpolation='nearest')
for a in axes:
a.axis('off')
pylab.tight_layout(), pylab.show()
运行上述代码,输出被分水岭线(等值线)分割和标记后的硬币,如图8-11所示。
图8-11 分水岭线分割后的硬币和标记后的硬币
本文摘自《Python图像处理实战》
本书介绍如何使用流行的Python图像处理库(如PIL、scikit-image、python-opencv、SciPy ndimage和SimpleITK)、机器学习库(scikit-learn)和深度学习库(TensorFlow、Keras)解决图像处理问题。通过学习本书,读者能够通过编写程序代码来实现复杂的图像处理(如图像增强、滤波、复原、分割、分类和目标检测),还能够掌握用机器学习和深度学习模型来解决复杂的图像处理问题的方法。
本书从基础开始,通过书中所提供的Python可重现实现来引导读者逐步进阶。本书从经典的图像处理技术开始,探索图像处理算法的进化历程,直至图像处理或计算机视觉与深度学习方面的最新进展。读者将学习如何用Python的PIL、scikit-image和SciPy ndimage等图像处理库编写Python 3代码片段,以及如何快速实现复杂的图像处理算法,如图像增强、滤波、分割、目标检测和分类。读者还将学习如何使用scikit-learn库和机器学习模型,并随后探索深度卷积神经网络(CNN),如TensorFlow/Keras VGG-19,用端到端深度学习YOLO模型进行目标检测,将DeepLab V3+用于语义分割和神经中枢式的转移模型等。读者还会学到一些高级图像处理技术问题,如图像内画、梯度混合、变分去噪、接缝雕刻、绗缝和变形。在本书最后,读者还将学习有效的图像处理的各种实现算法。
本书循着“高度实用”的宗旨来引导读者学习一系列图像处理的概念/算法,以帮助读者详细了解如何用高级的Python库函数实现这些算法。