11.1. 影像分割#
影像分割是在影像中標記感興趣的物件像素的任務。
在本教學中,我們將學習如何從背景中分割物件。我們使用來自 skimage.data.coins()
的影像。此影像顯示了在較暗背景下勾勒出的數個硬幣。由於背景與硬幣共享足夠的灰階,因此無法直接從灰階值的直方圖進行硬幣分割,單純的閾值分割並不足夠。

>>> from skimage.exposure import histogram
>>> coins = ski.data.coins()
>>> hist, hist_centers = ski.exposure.histogram(coins)
簡單地對影像進行閾值處理,會導致遺失硬幣的重要部分,或將背景的部分與硬幣合併。這是由於影像的光照不均勻所致。

第一個想法是利用局部對比,也就是使用梯度而不是灰階值。
11.1.1. 基於邊緣的分割#
首先,讓我們嘗試偵測包圍硬幣的邊緣。對於邊緣偵測,我們使用 skimage.feature.canny()
的 Canny 偵測器
>>> edges = ski.feature.canny(coins / 255.)
由於背景非常平滑,幾乎所有的邊緣都位於硬幣的邊界或硬幣內部。
>>> import scipy as sp
>>> fill_coins = sp.ndimage.binary_fill_holes(edges)

現在我們有了描繪硬幣外邊界的輪廓,我們使用 scipy.ndimage.binary_fill_holes()
函式,該函式使用數學形態學來填充孔洞,以填充硬幣的內部。

大多數硬幣都很好地從背景中分割出來。可以使用 ndi.label
函式輕鬆移除背景中的小物件,以移除小於小閾值的物件。
>>> label_objects, nb_labels = sp.ndimage.label(fill_coins)
>>> sizes = np.bincount(label_objects.ravel())
>>> mask_sizes = sizes > 20
>>> mask_sizes[0] = 0
>>> coins_cleaned = mask_sizes[label_objects]
然而,分割效果並不是非常令人滿意,因為其中一個硬幣根本沒有正確分割。原因是我們從 Canny 偵測器取得的輪廓沒有完全閉合,因此填充函式沒有填充硬幣的內部。

因此,這種分割方法不是很穩健:如果我們遺漏物件輪廓的一個像素,我們就無法填充它。當然,我們可以嘗試擴張輪廓以使其閉合。但是,最好嘗試更穩健的方法。
11.1.2. 基於區域的分割#
首先,讓我們確定硬幣和背景的標記。這些標記是我們可以明確標記為物件或背景的像素。在這裡,標記位於灰階值直方圖的兩個極端部分。
>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2
我們將在分水嶺分割中使用這些標記。分水嶺的名稱來自與水文的類比。分水嶺轉換從標記開始,淹沒高程影像,以確定這些標記的集水區。分水嶺線分隔這些集水區,並對應於所需的分割。
高程圖的選擇對於良好的分割至關重要。在這裡,梯度的幅度提供了一個良好的高程圖。我們使用 Sobel 運算子計算梯度的幅度。
>>> elevation_map = ski.filters.sobel(coins)
從下面顯示的 3D 曲面圖中,我們可以看到高障礙有效地將硬幣與背景分開。

這是對應的 2D 圖

下一步是根據灰階值直方圖的極端部分找到背景和硬幣的標記
>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2

現在讓我們計算分水嶺轉換
>>> segmentation = ski.segmentation.watershed(elevation_map, markers)

使用這種方法,所有硬幣的結果都令人滿意。即使背景的標記分佈不均勻,高程圖中的障礙也足夠高,這些標記可以淹沒整個背景。
我們使用數學形態學移除一些小孔
>>> segmentation = sp.ndimage.binary_fill_holes(segmentation - 1)
我們現在可以使用 ndi.label
一個一個地標記所有硬幣
>>> labeled_coins, _ = sp.ndimage.label(segmentation)
