1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
|
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 image from :func:`skimage.data.coins`. This image shows
several coins outlined against a darker background. The segmentation of
the coins cannot be done directly from the histogram of gray values,
because the background shares enough gray levels with the coins that a
thresholding segmentation is not sufficient.
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_001.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
::
>>> from skimage.exposure import histogram
>>> coins = ski.data.coins()
>>> hist, hist_centers = ski.exposure.histogram(coins)
Simply thresholding the image leads either to missing significant parts
of the coins, or to merging parts of the background with the
coins. This is due to the inhomogeneous lighting of the image.
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_002.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 gray values.
Edge-based segmentation
~~~~~~~~~~~~~~~~~~~~~~~
Let us first try to detect edges that enclose the coins. For edge
detection, we use the `Canny detector
<https://en.wikipedia.org/wiki/Canny_edge_detector>`_ of :func:`skimage.feature.canny`
::
>>> edges = ski.feature.canny(coins / 255.)
As the background is very smooth, almost all edges are found at the
boundary of the coins, or inside the coins.
::
>>> import scipy as sp
>>> fill_coins = sp.ndimage.binary_fill_holes(edges)
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_003.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
Now that we have contours that delineate the outer boundary of the coins,
we fill the inner part of the coins using the
:func:`scipy.ndimage.binary_fill_holes` function, which uses mathematical morphology
to fill the holes.
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_004.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 ``ndi.label``
function to remove objects smaller than a small threshold.
::
>>> 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]
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.
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_005.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
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 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 as either object or
background. Here, the markers are found at the two extreme parts of the
histogram of gray 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
<https://en.wikipedia.org/wiki/Watershed_%28image_processing%29>`_ floods
an image of elevation starting from markers, in order to determine the catchment
basins of these markers. Watershed lines separate these catchment basins,
and correspond to the desired segmentation.
The choice of the elevation map is critical for 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::
>>> elevation_map = ski.filters.sobel(coins)
From the 3-D surface plot shown below, we see that high barriers effectively
separate the coins from the background.
.. image:: data/elevation_map.jpg
:align: center
and here is the corresponding 2-D plot:
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_006.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
The next step is to find markers of the background and the coins based on the
extreme parts of the histogram of gray values::
>>> markers = np.zeros_like(coins)
>>> markers[coins < 30] = 1
>>> markers[coins > 150] = 2
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_007.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
Let us now compute the watershed transform::
>>> segmentation = ski.segmentation.watershed(elevation_map, markers)
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_008.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 remove a few small holes with mathematical morphology::
>>> segmentation = sp.ndimage.binary_fill_holes(segmentation - 1)
We can now label all the coins one by one using ``ndi.label``::
>>> labeled_coins, _ = sp.ndimage.label(segmentation)
.. image:: ../auto_examples/applications/images/sphx_glr_plot_coins_segmentation_009.png
:target: ../auto_examples/applications/plot_coins_segmentation.html
:align: center
|