11.1. 画像セグメンテーション#

画像セグメンテーションは、画像内の関心のあるオブジェクトのピクセルにラベルを付けるタスクです。

このチュートリアルでは、オブジェクトを背景からセグメント化する方法を見ていきます。 skimage.data.coins()からの画像を使用します。この画像は、暗い背景に対して輪郭が描かれたいくつかのコインを示しています。コインのセグメンテーションは、背景がコインと十分なグレーレベルを共有しているため、グレー値のヒストグラムから直接行うことはできません。したがって、閾値セグメンテーションでは不十分です。

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

単純に画像の閾値を設定すると、コインの重要な部分が欠落するか、背景の一部がコインとマージされます。これは、画像の不均一な照明が原因です。

../_images/sphx_glr_plot_coins_segmentation_002.png

最初のアイデアは、グレースケール値ではなく、局所的なコントラスト、つまりグラデーションを利用することです。

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)
../_images/sphx_glr_plot_coins_segmentation_003.png

コインの外側の境界を区切る輪郭ができたので、scipy.ndimage.binary_fill_holes()関数を使用してコインの内側を塗りつぶします。この関数は、数学的形態を使用して穴を埋めます。

../_images/sphx_glr_plot_coins_segmentation_004.png

ほとんどのコインは、背景からうまくセグメント化されています。背景からの小さなオブジェクトは、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]

ただし、コインの1つがまったく正しくセグメント化されていないため、セグメンテーションはあまり満足のいくものではありません。理由は、Canny検出器から得られた輪郭が完全に閉じていなかったため、塗りつぶし関数がコインの内側を塗りつぶさなかったためです。

../_images/sphx_glr_plot_coins_segmentation_005.png

したがって、このセグメンテーション方法はあまりロバストではありません。オブジェクトの輪郭の1ピクセルを見逃すと、それを塗りつぶすことができなくなります。もちろん、輪郭を閉じるために輪郭を拡張しようとすることができます。ただし、より堅牢な方法を試す方が望ましいです。

11.1.2. 領域ベースのセグメンテーション#

最初に、コインと背景のマーカーを決定しましょう。これらのマーカーは、オブジェクトまたは背景のいずれかとして明確にラベル付けできるピクセルです。ここでは、マーカーはグレー値のヒストグラムの2つの極端な部分で見つかります

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

これらのマーカーをウォーターシェッドセグメンテーションで使用します。ウォーターシェッドという名前は、水文学との類似性に由来しています。 ウォーターシェッド変換は、マーカーから開始する標高の画像をフラッディングし、これらのマーカーの集水域を決定します。ウォーターシェッド線はこれらの集水域を分離し、目的のセグメンテーションに対応します。

適切なセグメンテーションのためには、標高マップの選択が重要です。ここでは、勾配の振幅が適切な標高マップを提供します。勾配の振幅を計算するために、Sobel演算子を使用します。

>>> elevation_map = ski.filters.sobel(coins)

以下に示す3次元表面プロットから、高い障壁がコインを背景から効果的に分離していることがわかります。

../_images/elevation_map.jpg

そして、これが対応する2次元プロットです

../_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

次に、ウォーターシェッド変換を計算しましょう。

>>> segmentation = ski.segmentation.watershed(elevation_map, markers)
../_images/sphx_glr_plot_coins_segmentation_008.png

この方法では、すべてのコインで結果が満足のいくものです。背景のマーカーが十分に分散していなくても、標高マップの障壁は十分に高いため、これらのマーカーは背景全体をフラッディングしました。

数学的形態を使用して、いくつかの小さな穴を削除します。

>>> segmentation = sp.ndimage.binary_fill_holes(segmentation - 1)

これで、ndi.labelを使用して、すべてのコインを1つずつラベル付けできます。

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