图像分割

图像分割是对图像中感兴趣对象的像素进行标记的任务。

在本教程中,我们将了解如何将对象从背景中分割出来。我们使用 coins 图片来自 skimage.data 。这张图片显示了几枚硬币,在较暗的背景下勾勒出来。硬币的分割不能直接从灰度值的直方图中完成,因为背景与硬币具有足够的灰度级,因此阈值分割是不够的。

../_images/sphx_glr_plot_coins_segmentation_001.png
>>> from skimage import data
>>> from skimage.exposure import histogram
>>> coins = data.coins()
>>> hist, hist_centers = histogram(coins)

简单地对图像进行阈值处理,要么会丢失硬币的重要部分,要么会将部分背景与硬币合并。这是由于图像的照明不均匀造成的。

../_images/sphx_glr_plot_coins_segmentation_002.png

第一个想法是利用局部对比度,即使用渐变而不是灰度值。

基于边缘的分割

让我们首先尝试检测包围硬币的边缘。对于边缘检测,我们使用 Canny detectorskimage.feature.canny

>>> from skimage.feature import canny
>>> edges = canny(coins/255.)

由于背景非常光滑,几乎所有的边缘都在硬币的边界上,或者在硬币内部。

>>> from scipy import ndimage as ndi
>>> fill_coins = ndi.binary_fill_holes(edges)
../_images/sphx_glr_plot_coins_segmentation_003.png

现在我们有了描绘硬币外部边界的轮廓,我们使用 ndi.binary_fill_holes 函数,该函数使用数学形态来填充空洞。

../_images/sphx_glr_plot_coins_segmentation_004.png

大多数硬币都能很好地从背景中分割出来。从背景中移除小对象可以使用 ndi.label 函数来移除小于较小阈值的对象。

>>> label_objects, nb_labels = ndi.label(fill_coins)
>>> sizes = np.bincount(label_objects.ravel())
>>> mask_sizes = sizes > 20
>>> mask_sizes[0] = 0
>>> coins_cleaned = mask_sizes[label_objects]

然而,分割并不是很令人满意,因为其中一枚硬币根本没有被正确分割。原因是我们从坎尼探测器获得的轮廓并不是完全闭合的,因此填充函数没有填充硬币的内部。

../_images/sphx_glr_plot_coins_segmentation_005.png

因此,这种分割方法不是很健壮:如果我们遗漏了对象轮廓的一个像素,我们将无法填充它。当然,我们可以尝试扩大轮廓以使其闭合。但是,最好尝试一种更健壮的方法。

基于区域的分割

让我们首先确定硬币和背景的标记。这些标记是我们可以毫不含糊地标记为对象或背景的像素。在这里,可以在灰度值直方图的两个极端部分找到标记:

>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2

我们将在分水岭分割中使用这些标记。分水岭这个名字来源于对水文学的类比。这个 watershed transform 整体应用从标记开始的高程图像,以确定这些标记的汇流流域。分水岭线条分隔这些集水流域,并与所需的分段相对应。

高程图的选择对于良好的分割是至关重要的。在这里,渐变的幅度提供了一个很好的高程贴图。我们使用Sobel算子来计算梯度的幅度::

>>> from skimage.filters import sobel
>>> elevation_map = sobel(coins)

从下面显示的3-D表面图中,我们可以看到高高的栅栏有效地将硬币与背景隔开。

../_images/elevation_map.jpg

下面是相应的2-D图:

../_images/sphx_glr_plot_coins_segmentation_006.png

下一步是根据灰度值直方图的极端部分查找背景和硬币的标记:

>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2
../_images/sphx_glr_plot_coins_segmentation_007.png

现在让我们计算分水岭变换::

>>> from skimage.segmentation import watershed
>>> segmentation = watershed(elevation_map, markers)
../_images/sphx_glr_plot_coins_segmentation_008.png

用这种方法,对所有的硬币都能得到满意的结果。即使背景的标记不是均匀分布的,高程地图中的障碍物也足够高,这些标记可以淹没整个背景。

我们用数学形态移除几个小孔:

>>> segmentation = ndi.binary_fill_holes(segmentation - 1)

现在,我们可以使用以下命令逐个标记所有硬币 ndi.label ::

>>> labeled_coins, _ = ndi.label(segmentation)
../_images/sphx_glr_plot_coins_segmentation_009.png