DOC: convert segmentation example to tutorial-style example.

This commit is contained in:
Tony S Yu
2012-05-15 22:24:54 -04:00
parent 5a89ef61a6
commit 84074871f2
@@ -3,57 +3,40 @@
Comparing edge-based segmentation and region-based segmentation
===============================================================
In this example, we will see how to segment objects from a background.
We use the ``coins`` image from ``skimage.data``. This image shows
several coins outlined against a darker background. The segmentation of
the coins cannot be done directly from the histogram of grey values,
because the background shares enough grey levels with the coins that a
thresholding segmentation is not sufficient. Simply thresholding the image
leads either to missing significant parts of the coins, or to merging parts
of the background with the coins.
We first try an edge-based segmentation. We use the Canny detector to
delineate the contours of the coins. These contours are filled using
mathematical morphology (``scipy.ndimage.binary_fill_holes``). Small spurious
objects are easily removed by applying a threshold on the size of
unconnected objects. However, this method is not very robust, since contours
that are not perfectly closed are not filled correctly. This happens for one
of the coins.
We therefore try a second method, that is region-based. Here we use the
watershed transform. An elevation map is provided by the Sobel gradient
of the image. Markers of the background and the coins are determined from
the extreme parts of the histogram of grey values.
This second method works even better, and the coins can be segmented and
labeled individually.
In this example, we will see how to segment objects from a background. We use
the ``coins`` image from ``skimage.data``, which shows several coins outlined
against a darker background.
"""
import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
import skimage
from skimage.filter import canny, sobel
from skimage.morphology import watershed
#------------------ Loading data --------------------------------
from skimage import data
coins = data.coins()
#------------ Histogram of grey values ---------------------------
histo = np.histogram(coins, bins=np.arange(0, 256))
coins = data.coins()
hist = np.histogram(coins, bins=np.arange(0, 256))
plt.figure(figsize=(8, 3))
plt.subplot(121)
plt.imshow(coins, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.subplot(122)
plt.plot(histo[1][:-1], histo[0], lw=2)
plt.plot(hist[1][:-1], hist[0], lw=2)
plt.title('histogram of grey values')
#------------------ Tentative thresholding --------------------------------
"""
.. image:: PLOT2RST.current_figure
Thresholding
============
A simple way to segment the coins is to choose a threshold based on the
histogram of grey values. Unfortunately, thresholding this image gives a binary
image that either misses significant parts of the coins or merges parts of the
background with the coins:
"""
plt.figure(figsize=(6, 3))
plt.subplot(121)
plt.imshow(coins > 100, cmap=plt.cm.gray, interpolation='nearest')
@@ -63,73 +46,126 @@ plt.subplot(122)
plt.imshow(coins > 150, cmap=plt.cm.gray, interpolation='nearest')
plt.title('coins > 150')
plt.axis('off')
margins = dict(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0, right=1)
plt.subplots_adjust(**margins)
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
right=1)
"""
.. image:: PLOT2RST.current_figure
#------------------ Edge-based segmentation --------------------------------
Edge-based segmentation
=======================
Next, we try to delineate the contours of the coins using edge-based
segmentation. To do this, we first get the edges of features using the Canny
edge-detector.
"""
from skimage.filter import canny
edges = canny(coins/255.)
plt.figure(figsize=(4, 3))
plt.imshow(edges, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('Canny detector')
"""
.. image:: PLOT2RST.current_figure
These contours are then filled using mathematical morphology.
"""
from scipy import ndimage
fill_coins = ndimage.binary_fill_holes(edges)
plt.figure(figsize=(4, 3))
plt.imshow(fill_coins, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('Filling the holes')
"""
.. image:: PLOT2RST.current_figure
Small spurious objects are easily removed by setting a minimum size for valid
objects.
"""
label_objects, nb_labels = ndimage.label(fill_coins)
sizes = np.bincount(label_objects.ravel())
mask_sizes = sizes > 20
mask_sizes[0] = 0
coins_cleaned = mask_sizes[label_objects]
plt.figure(figsize=(7, 3.))
plt.subplot(131)
plt.imshow(edges, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('Canny detector')
plt.subplot(132)
plt.imshow(fill_coins, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('Filling the holes')
plt.subplot(133)
plt.figure(figsize=(4, 3))
plt.imshow(coins_cleaned, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('Removing small objects')
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
right=1)
"""
.. image:: PLOT2RST.current_figure
However, this method is not very robust, since contours that are not perfectly
closed are not filled correctly, as is the case for one unfilled coin above.
#------------------ Region-based segmentation --------------------------------
Region-based segmentation
=========================
We therefore try a region-based method using the
watershed transform. First, we find an elevation map using the Sobel gradient of the
image.
"""
from skimage.filter import sobel
elevation_map = sobel(coins)
plt.figure(figsize=(4, 3))
plt.imshow(elevation_map, cmap=plt.cm.jet, interpolation='nearest')
plt.axis('off')
plt.title('elevation_map')
"""
.. image:: PLOT2RST.current_figure
Next we find markers of the background and the coins based on the extreme parts
of the histogram of grey values.
"""
markers = np.zeros_like(coins)
markers[coins < 30] = 1
markers[coins > 150] = 2
elevation_map = sobel(coins)
segmentation = watershed(elevation_map, markers)
plt.figure(figsize=(7, 3))
plt.subplot(131)
plt.figure(figsize=(4, 3))
plt.imshow(markers, cmap=plt.cm.spectral, interpolation='nearest')
plt.axis('off')
plt.title('markers')
plt.subplot(132)
plt.imshow(elevation_map, cmap=plt.cm.jet, interpolation='nearest')
plt.axis('off')
plt.title('elevation_map')
plt.subplot(133)
"""
.. image:: PLOT2RST.current_figure
Finally, we use the watershed transform to fill regions of the elevation map starting from the markers determined above:
"""
from skimage.morphology import watershed
segmentation = watershed(elevation_map, markers)
plt.figure(figsize=(4, 3))
plt.imshow(segmentation, cmap=plt.cm.gray, interpolation='nearest')
plt.axis('off')
plt.title('segmentation')
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
right=1)
"""
.. image:: PLOT2RST.current_figure
# ------------------- Removing a few small holes ---------------------
This last method works even better, and the coins can be segmented and
labeled individually.
"""
segmentation = ndimage.binary_fill_holes(segmentation - 1)
#------------------ Labeling the coins --------------------------------
labeled_coins, _ = ndimage.label(segmentation)
plt.figure(figsize=(6, 3))
@@ -141,8 +177,11 @@ plt.subplot(122)
plt.imshow(labeled_coins, cmap=plt.cm.spectral, interpolation='nearest')
plt.axis('off')
plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0,
right=1)
plt.subplots_adjust(**margins)
"""
.. image:: PLOT2RST.current_figure
"""
plt.show()