import numpy as np
from matplotlib import pyplot as plt, cm
from skimage import io
from scipy import ndimage as nd
from skimage.filter import threshold_otsu
from skimage.morphology import disk
from skimage.color import rgb2gray
from skimage import img_as_float
from skimage.morphology import opening
from skimage.morphology import remove_small_objects
# needs skimage 0.9dev; comment out the following line if your version is earlier
from skimage.color import label2rgb
Next, we'll define some parameters of the tutorial. These are the numbers that will need modification until you find something that'll work for all/most images.
disk_size
defines the size of the opening operation. Roughly speaking, any object parts that are thinner than this radius will get removed from the image.
otsu_scale_factor
: Otsu's threshold by default is too low, and misses the "shiny" part of the pothole. This factor defines how much to multiply the threshold by in order to catch the brighter part as well as the dark part. If you have darker potholes, you might want to reduce this.
%cd ~/Downloads/
filename = 'P1.jpg'
disk_size = 18
otsu_scale_factor = 1.45
/Users/nuneziglesiasj/Downloads
First, lets convert the image to grayscale to deal only with light intensity. We want the darker regions of the image.
%pylab inline
pothole = io.imread(filename)
gpot = img_as_float(rgb2gray(pothole))
plt.imshow(gpot, cmap=cm.gray)
plt.show()
Populating the interactive namespace from numpy and matplotlib
Now, we want to threshold the image to find the "candidate" pothole regions.
t = threshold_otsu(gpot) * otsu_scale_factor
s = disk(disk_size)
tpot = gpot < t
plt.imshow(tpot, cmap=cm.gray)
plt.show()
This threshold is too high for our object but notice that it's the only continuous region of above-threshold pixels. We can use morphological opening to obliterate any foreground pixels close to background pixels.
tpot_open = opening(tpot, s)
plt.imshow(tpot_open, cmap=cm.gray)
plt.show()
So, most of the noise is gone, but we're still left with quite a bit of non-pothole foreground. Fortunately, the pothole is the biggest feature in this image (after the background). np.bincount
gives us the number of pixels of each label. We can use it to extract the second largest object (ie the largest foreground object), which should be our pothole.
objects = nd.label(tpot_open)[0] # label each connected region
b = np.bincount(objects.ravel()) # find object sizes
largest_object_size = sorted(b)[-2] # get second largest object
pothole_object = remove_small_objects(tpot_open.astype(bool), largest_object_size - 1)
plt.imshow(pothole_object)
plt.show()
Nice! Now we have our pothole. However, there's some noise inside. Not to worry: nd.binary_fill_holes
is exactly what we need:
pothole_object = nd.binary_fill_holes(pothole_object)
plt.imshow(pothole_object)
plt.show()
Now, putting it together, with scikit-image version 0.9dev, we can display this region superimposed on the original image:
found_pothole = label2rgb(pothole_object, pothole)
plt.imshow(found_pothole)
plt.show()
/Users/nuneziglesiasj/anaconda/envs/euroscipy/lib/python2.7/site-packages/scikit_image-0.9dev-py2.7-macosx-10.5-x86_64.egg/skimage/color/colorconv.py:104: skimage_deprecation: Call to deprecated function ``is_gray``. def is_gray(image): /Users/nuneziglesiasj/anaconda/envs/euroscipy/lib/python2.7/site-packages/scikit_image-0.9dev-py2.7-macosx-10.5-x86_64.egg/skimage/color/colorconv.py:91: skimage_deprecation: Call to deprecated function ``is_rgb``. def is_rgb(image):