In this post we continue exploring and experimenting with topics from the PluralSight course Building Image Processing Applications Using scikit-image by Janani Ravi. The topic for this post is Watershed.
In image processing, a watershed (image processing) is a transformation defined on a grayscale image. The name refers metaphorically to a geological watershed, or drainage divide, which separates adjacent drainage basins. The watershed transformation treats the image it operates upon like a topographic map, with the brightness of each point representing its height, and finds the lines that run along the tops of ridges. These lines are then used to segment the image into regions.
There are three different watershed algorithms. In this post we will only visit the watershed by flooding, which is the one used by scikit-image.
# **** folder of interest **** cd C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos # **** open file of interest using VSCode **** (base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>code Watershed.py # **** execute python script of interest **** (base) C:\Documents\_Image Processing\scikit-image-building-image-processing-applications\02\demos>python Watershed.py
We start by opening an Anaconda prompt and moving to a folder of interest. The folder might be different for you. Once there we invoke the VSCode IDE with the name of interest for the Python script we will work on in this post. The name may be different if you prefer.
I should disclose that at this time I am a Microsoft employee and have been using Visual STudio and VSCode for several years. I used to have several IDEs based on the programming language I would use. In general Visual Studio and VSCode seem to provide most (never generalize) of the typical features you may need when developing code for different languages.
Next we save the Python script of interest. The script should be saved before running it.
To run the script of interest we just type Python followed by the name of the script.
I would like to note that the author of the course uses a Jupyter notebook to write code. Since I am interested in using GitHub Copilot I am using VSCode.
# **** imports **** import numpy as np import matplotlib.pyplot as plt from skimage import io from skimage import data, util, filters, color #from skimage.morphology import watershed from skimage.segmentation import watershed
We now import a set of libraries which we will use in this Python script. Note the last two lines. The watershed function has been moved in the Watershed segmentation in the scikit-image package. When not using the exact version of libraries that an author uses, you may run into this type of issue. I happen to be using the latest versions as loaded by Anaconda.
# **** read bubbles image as grayscale **** bubbles = color.rgb2gray(io.imread('./images/pexels-bubbles.jpg')) # **** display the grayscale bubbles image **** plt.figure(figsize=(10, 10)) plt.imshow(bubbles, cmap='gray') plt.title('Bubbles') plt.show()
We now read a color JPEG image that contains some bubbles. Note that the RGB image is being transformed to grayscale. The resulting image is then displayed.
# **** Apply the sobel filter to the bubbles image. # A watershed algorithm will perform better if we use # an edge detected image. **** bubbles_edges = filters.sobel(bubbles) # **** display the edges of the bubbles image **** plt.figure(figsize=(10, 10)) plt.imshow(bubbles_edges, cmap='gray') plt.title('Bubbles Edges') plt.show()
A watershed algorithm will perform better if we use an edge detected image. To generate such an image we use the Sobel algorithm. We have used such an algorithm in a previous post in this blog. The resulting image is then displayed.
# **** Find 300 points regularly spaced along the image **** grid = util.regular_grid( bubbles.shape, n_points=300) # **** The points are returned in the form of slices- # one slice for each dimension **** grid [slice(15, None, 30), slice(15, None, 30)] # **** The seeds matrix is the same shape as the # image and contains integers ranging # from 1 to size of image **** seeds = np.zeros(bubbles.shape, dtype=int) seeds[grid] = np.arange(seeds[grid].size).reshape(seeds[grid].shape) + 1 # **** Seeds are the image markers from where # the flooding should begin. # The classic watershed algorithm may produce uneven # fragments - maybe hard to perform further analysis. **** w0 = watershed( bubbles_edges, seeds) water_classic = color.label2rgb(w0, bubbles, alpha=0.4, kind='overlay') # **** display the classic watershed image **** plt.figure(figsize=(8, 8)) plt.imshow(water_classic) plt.title('Classic Watershed') plt.show()
To get a good effect on the watershed algorithm we need to use a set of equally spaced points.
In this code we generate a grid with seeds. The seeds are then applied to the classing watershed algorithm The resulting image is then displayed.
# **** Run compact watershed algorithm. # Any compactness value > 0 produces a # compact watershed - produces even fragments. **** w1 = watershed( bubbles_edges, seeds, compactness=0.91) water_compact = color.label2rgb(w1, bubbles, alpha=0.4, kind='overlay') # **** Display the compact watershed image **** plt.figure(figsize=(8, 8)) plt.imshow(water_compact) plt.title('Compact Watershed') plt.show()
We repeat the watershed algorithm but the compactness argument is set to 0.91. The image is then displayed.
Note that the image is blocked in squares that allows us to ease the performance of future operations. In our case we will stop here.
If interested in getting the code for this post you can find it in the Watershed GitHub repository.
As usual, if you have comments or questions please leave them below.