Image Segmentation
图像分割
图像分割是标记图像中感兴趣对象的像素的任务。
在本教程中,我们将看到如何从背景中分割对象。我们使用coins
来自的图像skimage.data
。此图显示了几个硬币在较暗的背景下绘制。硬币的分割不能直接从灰度值的直方图中完成,因为背景与阈值分割不足以与硬币共享足够的灰度级。
>>> import numpy as np
>>> from skimage import data
>>> coins = data.coins()
>>> histo = np.histogram(coins, bins=np.arange(0, 256))
简单地对图像进行阈值处理会导致丢失重要部分的硬币,或者将背景的部分与硬币合并。这是由于图像的不均匀照明造成的。
第一个想法是利用局部对比度,即使用梯度而不是灰度值。
基于边缘的分割
让我们首先尝试检测包围硬币的边缘。对于边缘检测,我们使用Canny算子检测器的skimage.feature.canny
>>> from skimage.feature import canny
>>> edges = canny(coins/255.)
由于背景非常光滑,几乎所有边缘都位于硬币的边界处或硬币内部。
>>> from scipy import ndimage as ndi
>>> fill_coins = ndi.binary_fill_holes(edges)
现在我们有轮廓描绘了硬币的外部边界,我们使用ndi.binary_fill_holes
函数来填充硬币的内部部分,该函数使用数学形态学来填充孔。
大多数硬币都很好地被分割出背景。使用该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]
然而,分割并不是很令人满意,因为其中一枚硬币根本没有被正确分割。原因是我们从Canny探测器获得的轮廓没有完全封闭,因此填充功能没有填充硬币的内部。
因此,这种分割方法不是非常稳健:如果我们错过了对象轮廓的单个像素,我们将无法填充它。当然,我们可以尝试扩大轮廓以关闭它们。但是,最好尝试一种更稳健的方法。
基于区域的分割
让我们先确定硬币的标记和背景。这些标记是我们可以明确标记为对象或背景的像素。这里,标记位于灰度值直方图的两个极端部分:
>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2
我们将在分水岭分割中使用这些标记。名称分水岭来自与水文学的类比。该分水岭变换洪水高程的图像从标记开始,以确定这些标记的集水盆地。分水岭线将这些集水盆分开,并对应于所需的分段。
高程图的选择对于良好的分割至关重要。这里,梯度的幅度提供了良好的高程图。我们使用Sobel算子来计算梯度的幅度:
>>> from skimage.filters import sobel
>>> elevation_map = sobel(coins)
从下面显示的三维表面图中,我们看到高屏障有效地将硬币从背景中分离出来。
这里是相应的二维图:
下一步是根据灰度值直方图的极端部分找出背景和硬币的标记:
>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2
让我们现在计算分水岭变换:
>>> from skimage.morphology import watershed
>>> segmentation = watershed(elevation_map, markers)
用这种方法,结果是令人满意的所有硬币。即使背景的标记分布不均匀,高程图中的障碍也足够高,以便这些标记充满整个背景。
我们用数学形态去除一些小孔:
>>> segmentation = ndi.binary_fill_holes(segmentation - 1)
现在我们可以使用ndi.label
以下标签逐个标记所有硬币:
>>> labeled_coins, _ = ndi.label(segmentation)