This is one of the 100 recipes of the IPython Cookbook, the definitive guide to high-performance scientific computing and data science in Python.

11.3. Segmenting an image

  1. Let's import the packages.
In [ ]:
import numpy as np
import matplotlib.pyplot as plt
from skimage.data import coins
from skimage.filter import threshold_otsu
from skimage.segmentation import clear_border
from skimage.morphology import closing, square
from skimage.measure import regionprops, label
from skimage.color import lab2rgb
import matplotlib as mpl
%matplotlib inline
  1. We create a function to display a grayscale image.
In [ ]:
def show(img, cmap=None):
    cmap = cmap or plt.cm.gray
    plt.figure(figsize=(4,2));
    plt.imshow(img, cmap=cmap);
    plt.axis('off');
    plt.show();
  1. We retrieve a test image bundled in scikit-image, showing various coins on a plain background.
In [ ]:
img = coins()
In [ ]:
show(img)
  1. The first step to segment the image consists in finding an intensity threshold separating the (bright) coins from the (dark) background. Otsu's method defines a simple algorithm to find such a threshold automatically.
In [ ]:
threshold_otsu(img)
In [ ]:
show(img>107)
  1. There appears to be a problem in the top left corner of the image, with part of the background being too bright. Let's use the notebook widgets to find a better threshold.
In [ ]:
from IPython.html import widgets
@widgets.interact(t=(10, 240))
def threshold(t):
    show(img>t)
  1. The threshold 120 looks better. The next step consists in cleaning the binary image by smoothing the coins and removing the border. Scikit-image contains a few functions for these purposes.
In [ ]:
img_bin = clear_border(closing(img>120, square(5)))
show(img_bin)
  1. Next, we perform the segmentation task itself with the label function. This function detects the connected components in the image, and attributes a unique label to every component. Here, we color-code the labels in the binary image.
In [ ]:
labels = label(img_bin)
show(labels, cmap=plt.cm.rainbow)
  1. Small artifacts in the image result in spurious labels that do not correspond to coins. Therefore we only keep components with more than 100 pixels. The regionprops function allows us to retrieve specific properties of the components (here, the area and the bounding box).
In [ ]:
regions = regionprops(labels)
boxes = np.array([label['BoundingBox'] for label in regions 
                                       if label['Area'] > 100])
print("There are {0:d} coins.".format(len(boxes)))
  1. Finally, we show the label number on top of each component in the original image.
In [ ]:
plt.figure(figsize=(6,4));
plt.imshow(img, cmap=plt.cm.gray);
plt.axis('off');
xs = boxes[:,[1,3]].mean(axis=1)
ys = boxes[:,[0,2]].mean(axis=1)
for i, box in enumerate(boxes):
    plt.text(xs[i]-5, ys[i]+5, str(i))

You'll find all the explanations, figures, references, and much more in the book (to be released later this summer).

IPython Cookbook, by Cyrille Rossant, Packt Publishing, 2014 (500 pages).