diff --git a/doc/examples/applications/README.txt b/doc/examples/applications/README.txt new file mode 100644 index 00000000..8f40133f --- /dev/null +++ b/doc/examples/applications/README.txt @@ -0,0 +1,2 @@ +Longer examples and demonstrations +---------------------------------- diff --git a/doc/examples/applications/plot_coins_segmentation.py b/doc/examples/applications/plot_coins_segmentation.py new file mode 100644 index 00000000..5b3c9de7 --- /dev/null +++ b/doc/examples/applications/plot_coins_segmentation.py @@ -0,0 +1,135 @@ +""" +=============================================================== +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 getting parts +of the background together 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. + +""" + + +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)) + +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.title('histogram of grey values') + +#------------------ Tentative thresholding -------------------------------- +plt.figure(figsize=(6, 3)) +plt.subplot(121) +plt.imshow(coins > 100, cmap=plt.cm.gray, interpolation='nearest') +plt.title('coins > 100') +plt.axis('off') +plt.subplot(122) +plt.imshow(coins > 150, cmap=plt.cm.gray, interpolation='nearest') +plt.title('coins > 150') +plt.axis('off') + +plt.subplots_adjust(hspace=0.01, wspace=0.01, top=1, bottom=0, left=0, + right=1) + + +#------------------ Edge-based segmentation -------------------------------- +edges = canny(coins/255.) + +fill_coins = ndimage.binary_fill_holes(edges) + +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.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) + + +#------------------ Region-based segmentation -------------------------------- + +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.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) +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) + + +#------------------ Labeling the coins -------------------------------- +labeled_coins, _ = ndimage.label(segmentation - 1) + +plt.figure(figsize=(6, 4)) +plt.imshow(labeled_coins, cmap=plt.cm.spectral, interpolation='nearest') +plt.axis('off') + +plt.show() diff --git a/doc/source/user_guide.txt b/doc/source/user_guide.txt index 7e0cd761..c4e7a8ff 100644 --- a/doc/source/user_guide.txt +++ b/doc/source/user_guide.txt @@ -5,4 +5,5 @@ User Guide :maxdepth: 1 user_guide/data_types + user_guide/segmentation user_guide/plugins diff --git a/doc/source/user_guide/elevation_map.jpg b/doc/source/user_guide/elevation_map.jpg new file mode 100644 index 00000000..3aca807e Binary files /dev/null and b/doc/source/user_guide/elevation_map.jpg differ diff --git a/doc/source/user_guide/segmentation.txt b/doc/source/user_guide/segmentation.txt new file mode 100644 index 00000000..fe820612 --- /dev/null +++ b/doc/source/user_guide/segmentation.txt @@ -0,0 +1,141 @@ +Image Segmentation +------------------ + +Image segmentation is the task of labeling the pixels of objects of +interest in an image. + +In this tutorial, 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. + +.. image:: ../../_images/plot_coins_segmentation_1.png + :target: ../auto_examples/applications/plot_coins_segmentation.html + :align: center + +:: + + >>> import numpy as np + >>> from skimage import data + >>> coins = data.coins() + >>> histo = np.histogram(coins, bins=np.arange(0, 256)) + +Simply thresholding the image leads either to missing significant parts +of the coins, or to getting parts of the background together with the +coins. This is due to the inhomogeneous lighting of the image. + +.. image:: ../../_images/plot_coins_segmentation_2.png + :target: ../auto_examples/applications/plot_coins_segmentation.html + :align: center + +A first idea is to take advantage of the local contrast, that is, to +use the gradients rather than the grey values. + +Edge-based segmentation +~~~~~~~~~~~~~~~~~~~~~~~ + +Let us first try to detect edges than enclose the coins. For edge +detection, we use the `Canny detector +`_ of ``skimage.filter.canny`` + +:: + + >>> from skimage.filter import canny + >>> edges = canny(coins/255.) + +As the background is very smooth, almost all edges are found at the +boundary of the coins, or inside the coins. +Now that we have contours that delineate the outer boundary of the coins, +we fill the inner part of the coins using the +``ndimage.binary_fill_holes`` function, that uses mathematical morphology +to fill the holes. + +:: + + >>> from scipy import ndimage + >>> fill_coins = ndimage.binary_fill_holes(edges) + +.. image:: ../../_images/plot_coins_segmentation_3.png + :target: ../auto_examples/applications/plot_coins_segmentation.html + :align: center + +Most coins are well segmented out of the background. Small objects from +the background can be easily removed using the ``ndimage.label`` +function, and removing objects smaller than a small threshold. + +:: + + >>> 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] + +However, the segmentation is not very satisfying, since one of the coins +has not been segmented correctly at all. The reason is that the contour +that we got from the Canny detector was not completely closed, therefore +the filling function did not fill the inner part of the coin. + +Therefore, this segmentation method is not very robust: if we miss a +single pixel of the contour of the object, we will not be able to fill +it. Of course, we could try first to dilate the contours in order to +close them. However, it is preferable to try a more robust method. + +Region-based segmentation +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Let us first determine markers of the coins and the background. These +markers are pixels that we can label unambiguously with one of the +phases. Here, the markers are found at the two extreme parts of the +histogram of grey values: + +:: + + >>> markers = np.zeros_like(coins) + >>> markers[coins < 30] = 1 + >>> markers[coins > 150] = 2 + +We will use these markers in a watershed segmentation. The name watershed +comes from an analogy with hydrology. The `watershed transform +`_ floods +an image of elevation from markers, in order to determine the catchment +basins of these markers. Watershed lines separate these catchment basins, +and correspond to the segmentation that is looked for. + +The choice of the elevation map is critical for a good segmentation. +Here, the amplitude of the gradient provides a good elevation map. We +use the Sobel operator for computing the amplitude of the gradient:: + + >>> from skimage.filter import sobel + >>> elevation_map = sobel(coins) + +From the 3-D surface plot shown below, we see that high barriers separate +well the coins from the background. + +.. image:: elevation_map.jpg + :align: center + +Let us now compute the watershed transform:: + + >>> from skimage.morphology import watershed + >>> segmentation = watershed(elevation_map, markers) + +.. image:: ../../_images/plot_coins_segmentation_4.png + :target: ../auto_examples/applications/plot_coins_segmentation.html + :align: center + +With this method, the result is satisfying for all coins. Even if the +markers for the background were not well distributed, the barriers in the +elevation map were high enough for these markers to flood the entire +background. + +We can now label all the coins one by one using ``ndimage.label``:: + + >>> labeled_coins, _ = ndimage.label(segmentation - 1) + +.. image:: ../../_images/plot_coins_segmentation_5.png + :target: ../auto_examples/applications/plot_coins_segmentation.html + :align: center +