CofeehousePy/deps/scikit-image/doc/examples/segmentation/plot_floodfill.py

187 lines
5.7 KiB
Python

"""
==========
Flood Fill
==========
Flood fill is an algorithm to identify and/or change adjacent values in an
image based on their similarity to an initial seed point [1]_. The conceptual
analogy is the 'paint bucket' tool in many graphic editors.
.. [1] https://en.wikipedia.org/wiki/Flood_fill
Basic example
-------------
First, a basic example where we will change a checkerboard square from white
to mid-gray.
"""
import numpy as np
import matplotlib.pyplot as plt
from skimage import data, filters, color, morphology
from skimage.segmentation import flood, flood_fill
checkers = data.checkerboard()
# Fill a square near the middle with value 127, starting at index (76, 76)
filled_checkers = flood_fill(checkers, (76, 76), 127)
fig, ax = plt.subplots(ncols=2, figsize=(10, 5))
ax[0].imshow(checkers, cmap=plt.cm.gray)
ax[0].set_title('Original')
ax[1].imshow(filled_checkers, cmap=plt.cm.gray)
ax[1].plot(76, 76, 'wo') # seed point
ax[1].set_title('After flood fill')
plt.show()
##############################################################################
# Advanced example
# ----------------
#
# Because standard flood filling requires the neighbors to be strictly equal,
# its use is limited on real-world images with color gradients and noise.
# The `tolerance` keyword argument widens the permitted range about the initial
# value, allowing use on real-world images.
#
# Here we will experiment a bit on the cameraman. First, turning his coat from
# dark to light.
cameraman = data.camera()
# Change the cameraman's coat from dark to light (255). The seed point is
# chosen as (155, 150)
light_coat = flood_fill(cameraman, (155, 150), 255, tolerance=10)
fig, ax = plt.subplots(ncols=2, figsize=(10, 5))
ax[0].imshow(cameraman, cmap=plt.cm.gray)
ax[0].set_title('Original')
ax[0].axis('off')
ax[1].imshow(light_coat, cmap=plt.cm.gray)
ax[1].plot(150, 155, 'ro') # seed point
ax[1].set_title('After flood fill')
ax[1].axis('off')
plt.show()
##############################################################################
# The cameraman's coat is in varying shades of gray. Only the parts of the coat
# matching the shade near the seed value is changed.
#
# Experimentation with tolerance
# ------------------------------
#
# To get a better intuitive understanding of how the tolerance parameter
# works, here is a set of images progressively increasing the parameter with
# seed point in the upper left corner.
output = []
for i in range(8):
tol = 5 + 20 * i
output.append(flood_fill(cameraman, (0, 0), 255, tolerance=tol))
# Initialize plot and place original image
fig, ax = plt.subplots(nrows=3, ncols=3, figsize=(12, 12))
ax[0, 0].imshow(cameraman, cmap=plt.cm.gray)
ax[0, 0].set_title('Original')
ax[0, 0].axis('off')
# Plot all eight different tolerances for comparison.
for i in range(8):
m, n = np.unravel_index(i + 1, (3, 3))
ax[m, n].imshow(output[i], cmap=plt.cm.gray)
ax[m, n].set_title('Tolerance {0}'.format(str(5 + 20 * i)))
ax[m, n].axis('off')
ax[m, n].plot(0, 0, 'bo') # seed point
fig.tight_layout()
plt.show()
##############################################################################
# Flood as mask
# -------------
#
# A sister function, `flood`, is available which returns a mask identifying
# the flood rather than modifying the image itself. This is useful for
# segmentation purposes and more advanced analysis pipelines.
#
# Here we segment the nose of a cat. However, multi-channel images are not
# supported by flood[_fill]. Instead we Sobel filter the red channel to
# enhance edges, then flood the nose with a tolerance.
cat = data.chelsea()
cat_sobel = filters.sobel(cat[..., 0])
cat_nose = flood(cat_sobel, (240, 265), tolerance=0.03)
fig, ax = plt.subplots(nrows=3, figsize=(10, 20))
ax[0].imshow(cat)
ax[0].set_title('Original')
ax[0].axis('off')
ax[1].imshow(cat_sobel)
ax[1].set_title('Sobel filtered')
ax[1].axis('off')
ax[2].imshow(cat)
ax[2].imshow(cat_nose, cmap=plt.cm.gray, alpha=0.3)
ax[2].plot(265, 240, 'wo') # seed point
ax[2].set_title('Nose segmented with `flood`')
ax[2].axis('off')
fig.tight_layout()
plt.show()
##############################################################################
# Flood-fill in HSV space and mask post-processing
# ------------------------------------------------
#
# Since flood fill operates on single-channel images, we transform here the
# image to the HSV (Hue Saturation Value) space in order to flood pixels of
# similar hue.
#
# In this example we also show that it is possible to post-process the binary
# mask returned by :func:`skimage.segmentation.flood` thanks to the functions
# of :mod:`skimage.morphology`.
img = data.astronaut()
img_hsv = color.rgb2hsv(img)
img_hsv_copy = np.copy(img_hsv)
# flood function returns a mask of flooded pixels
mask = flood(img_hsv[..., 0], (313, 160), tolerance=0.016)
# Set pixels of mask to new value for hue channel
img_hsv[mask, 0] = 0.5
# Post-processing in order to improve the result
# Remove white pixels from flag, using saturation channel
mask_postprocessed = np.logical_and(mask,
img_hsv_copy[..., 1] > 0.4)
# Remove thin structures with binary opening
mask_postprocessed = morphology.binary_opening(mask_postprocessed,
np.ones((3, 3)))
# Fill small holes with binary closing
mask_postprocessed = morphology.binary_closing(
mask_postprocessed, morphology.disk(20))
img_hsv_copy[mask_postprocessed, 0] = 0.5
fig, ax = plt.subplots(1, 2, figsize=(8, 4))
ax[0].imshow(color.hsv2rgb(img_hsv))
ax[0].axis('off')
ax[0].set_title('After flood fill')
ax[1].imshow(color.hsv2rgb(img_hsv_copy))
ax[1].axis('off')
ax[1].set_title('After flood fill and post-processing')
fig.tight_layout()
plt.show()