Title: Watershed OpenCV - PyImageSearch

URL Source: https://pyimagesearch.com/2015/11/02/watershed-opencv/

Published Time: 2015-11-02T15:00:34+00:00

Markdown Content:
[![Image 1: watershed_output_coins_02](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20279'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_coins_02.jpg)

The [watershed algorithm](https://en.wikipedia.org/wiki/Watershed_\(image_processing\)) is a classic algorithm used for segmentation and is especially useful when extracting _touching_ or _overlapping_ objects in images, such as the coins in the figure above.

Using traditional image processing methods such as thresholding and contour detection, we would be unable to extract each _individual_ coin from the image — but by leveraging the watershed algorithm, we are able to detect and extract each coin without a problem.

When utilizing the watershed algorithm we must start with _user-defined markers_. These markers can be either _manually_ defined via point-and-click, or we can _automatically_ or _heuristically_ define them using methods such as thresholding and/or morphological operations.

Based on these markers, the watershed algorithm treats pixels in our input image as local elevation (called a _[topography](https://en.wikipedia.org/wiki/Topography)_) — the method “floods” valleys, starting from the markers and moving outwards, until the valleys of different markers meet each other. In order to obtain an accurate watershed segmentation, the markers must be correctly placed.

In the remainder of this post, I’ll show you how to use the watershed algorithm to segment and extract objects in images that are both touching and overlapping. To accomplish this, we’ll be using a variety of Python packages including [SciPy](http://www.scipy.org/), [scikit-image](http://scikit-image.org/), and OpenCV.

![Image 2](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20128%20128'%3E%3C/svg%3E)

#### Looking for the source code to this post?

[Jump Right To The Downloads Section](#download-the-code)

[![Image 3: Figure 1: An example image containing touching objects. Our goal is to detect and extract each of these coins individually.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20348'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_coins_01.jpg)

**Figure 1:** An example image containing touching objects. Our goal is to detect and extract each of these coins individually.

In the above image you can see examples of objects that would be impossible to extract using simple thresholding and contour detection, Since these objects are touching, overlapping, or both, the contour extraction process would treat each group of touching objects as a _single object_ rather than _multiple objects_.

### The problem with basic thresholding and contour extraction

Let’s go ahead and demonstrate a limitation of simple thresholding and contour detection. Open up a new file, name it `contour_only.py` , and let’s get coding:

\# import the necessary packages
from \_\_future\_\_ import print\_function
from skimage.feature import peak\_local\_max
from skimage.segmentation import watershed
from scipy import ndimage
import argparse
import imutils
import cv2

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True,
	help="path to input image")
args = vars(ap.parse\_args())

# load the image and perform pyramid mean shift filtering
# to aid the thresholding step
image = cv2.imread(args\["image"\])
shifted = cv2.pyrMeanShiftFiltering(image, 21, 51)
cv2.imshow("Input", image)

We start off on **Lines 2-8** by importing our necessary packages. **Lines 11-14** then parse our command line arguments. We’ll only need a single switch here, `--image` , which is the path to the image that we want to process.

From there, we’ll load our image from disk on **Line 18**, apply [pyramid mean shift filtering](http://docs.opencv.org/modules/imgproc/doc/filtering.html#pyrmeanshiftfiltering) (**Line 19**) to help the accuracy of our thresholding step, and finally display our image to our screen. An example of our output thus far can be seen below:

[![Image 4: Figure 2: Output from the pyramid mean shift filtering step.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20394'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_mean_shift.jpg)

**Figure 2:** Output from the pyramid mean shift filtering step.

Now, let’s threshold the mean shifted image:

\# convert the mean shift image to grayscale, then apply
# Otsu's thresholding
gray = cv2.cvtColor(shifted, cv2.COLOR\_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
	cv2.THRESH\_BINARY | cv2.THRESH\_OTSU)\[1\]
cv2.imshow("Thresh", thresh)

Given our input `image` , we then convert it to grayscale and apply Otsu’s thresholding to segment the background from the foreground:

[![Image 5: Figure 3: Applying Otsu's automatic thresholding to segment the foreground coins from the background.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20394'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_threshold.jpg)

**Figure 3:** Applying Otsu’s automatic thresholding to segment the foreground coins from the background.

Finally, the last step is to detect contours in the thresholded image and draw each individual contour:

\# find contours in the thresholded image
cnts = cv2.findContours(thresh.copy(), cv2.RETR\_EXTERNAL,
	cv2.CHAIN\_APPROX\_SIMPLE)
cnts = imutils.grab\_contours(cnts)
print("\[INFO\] {} unique contours found".format(len(cnts)))

# loop over the contours
for (i, c) in enumerate(cnts):
	# draw the contour
	((x, y), \_) = cv2.minEnclosingCircle(c)
	cv2.putText(image, "#{}".format(i + 1), (int(x) - 10, int(y)),
		cv2.FONT\_HERSHEY\_SIMPLEX, 0.6, (0, 0, 255), 2)
	cv2.drawContours(image, \[c\], -1, (0, 255, 0), 2)

# show the output image
cv2.imshow("Image", image)
cv2.waitKey(0)

Below we can see the output of our image processing pipeline:

[![Image 6: Figure 4: The output of our simple image processing pipeline. Unfortunately, our results are pretty poor -- we are not able to detect each individual coin.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20394'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_contours_only.jpg)

**Figure 4:** The output of our simple image processing pipeline. Unfortunately, our results are pretty poor — we are not able to detect each individual coin.

As you can see, our results are pretty terrible. Using simple thresholding and contour detection our Python script reports that there are only _two_ coins in the images, even though there are clearly _nine_ of them.

The reason for this problem arises from the fact that coin borders are _touching_ each other in the image — thus, the `cv2.findContours` function only sees the coin groups as a _single_ object when in fact they are _multiple, separate_ coins.

_**Note:** A series of morphological operations (specifically, erosions) would help us for this particular image. However, for objects that are_ overlapping _these erosions would not be sufficient. For the sake of this example, let’s pretend that morphological operations are not a viable option so that we may explore the watershed algorithm._

### Using the watershed algorithm for segmentation

Now that we understand the limitations of simple thresholding and contour detection, let’s move on to the watershed algorithm. Open up a new file, name it `watershed.py` , and insert the following code:

\# import the necessary packages
from skimage.feature import peak\_local\_max
from skimage.morphology import watershed
from scipy import ndimage
import numpy as np
import argparse
import imutils
import cv2

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add\_argument("-i", "--image", required=True,
	help="path to input image")
args = vars(ap.parse\_args())

# load the image and perform pyramid mean shift filtering
# to aid the thresholding step
image = cv2.imread(args\["image"\])
shifted = cv2.pyrMeanShiftFiltering(image, 21, 51)
cv2.imshow("Input", image)

# convert the mean shift image to grayscale, then apply
# Otsu's thresholding
gray = cv2.cvtColor(shifted, cv2.COLOR\_BGR2GRAY)
thresh = cv2.threshold(gray, 0, 255,
	cv2.THRESH\_BINARY | cv2.THRESH\_OTSU)\[1\]
cv2.imshow("Thresh", thresh)

Again, we’ll start on **Lines 2-8** by importing our required packages. We’ll be using functions from [SciPy](http://www.scipy.org/), [scikit-image](http://scikit-image.org/), [imutils](https://github.com/jrosebr1/imutils), and OpenCV. If you don’t already have SciPy and scikit-image installed on your system, you can use `pip` to install them for you:

$ pip install --upgrade scipy
$ pip install --upgrade scikit-image
$ pip install --upgrade imutils

**Lines 11-14** handle parsing our command line arguments. Just like in the previous example, we only need a single switch, the path to the image `--image` we are going to apply the watershed algorithm to.

From there, **Lines 18 and 19** load our image from disk and apply pyramid mean shift filtering. **Lines 24-26** perform grayscale conversion and thresholding.

Given our thresholded image, we can now apply the watershed algorithm:

\# compute the exact Euclidean distance from every binary
# pixel to the nearest zero pixel, then find peaks in this
# distance map
D = ndimage.distance\_transform\_edt(thresh)
localMax = peak\_local\_max(D, indices=False, min\_distance=20,
	labels=thresh)

# perform a connected component analysis on the local peaks,
# using 8-connectivity, then appy the Watershed algorithm
markers = ndimage.label(localMax, structure=np.ones((3, 3)))\[0\]
labels = watershed(-D, markers, mask=thresh)
print("\[INFO\] {} unique segments found".format(len(np.unique(labels)) - 1))

The first step in applying the watershed algorithm for segmentation is to compute the Euclidean Distance Transform (EDT) via the `distance_transform_edt` function (**Line 32**). As the name suggests, this function computes the Euclidean distance to the closest zero (i.e., background pixel) for each of the foreground pixels. We can visualize the EDT in the figure below:

[![Image 7: Figure 5: Visualizing the Euclidean Distance Transform.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20500%20432'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_distance_map.jpg)

**Figure 5:** Visualizing the Euclidean Distance Transform.

On **Line 33** we take `D` , our distance map, and find peaks (i.e., local maxima) in the map. We’ll ensure that is _at least_ a 20 pixel distance between each peak.

**Line 38** takes the output of the `peak_local_max` function and applies a [connected-component analysis](https://en.wikipedia.org/wiki/Connected-component_labeling) using 8-connectivity. The output of this function gives us our `markers` which we then feed into the `watershed` function on **Line 39**. Since the watershed algorithm assumes our markers represent local minima (i.e., valleys) in our distance map, we take the negative value of `D` .

The `watershed` function returns a matrix of `labels` , a NumPy array with the same width and height as our input image. Each pixel value as a unique label value. _Pixels that have the same label value belong to the same object._

The last step is to simply loop over the unique label values and extract each of the unique objects:

\# loop over the unique labels returned by the Watershed
# algorithm
for label in np.unique(labels):
	# if the label is zero, we are examining the 'background'
	# so simply ignore it
	if label == 0:
		continue

	# otherwise, allocate memory for the label region and draw
	# it on the mask
	mask = np.zeros(gray.shape, dtype="uint8")
	mask\[labels == label\] = 255

	# detect contours in the mask and grab the largest one
	cnts = cv2.findContours(mask.copy(), cv2.RETR\_EXTERNAL,
		cv2.CHAIN\_APPROX\_SIMPLE)
	cnts = imutils.grab\_contours(cnts)
	c = max(cnts, key=cv2.contourArea)

	# draw a circle enclosing the object
	((x, y), r) = cv2.minEnclosingCircle(c)
	cv2.circle(image, (int(x), int(y)), int(r), (0, 255, 0), 2)
	cv2.putText(image, "#{}".format(label), (int(x) - 10, int(y)),
		cv2.FONT\_HERSHEY\_SIMPLEX, 0.6, (0, 0, 255), 2)

# show the output image
cv2.imshow("Output", image)
cv2.waitKey(0)

On **Line 44** we start looping over each of the unique `labels` . If the `label` is zero, then we are examining the “background component”, so we simply ignore it.

Otherwise, **Lines 52 and 53** allocate memory for our `mask` and set the pixels belonging to the current label to _255_ (white). We can see an example of such a mask below on the _right_:

[![Image 8: Figure 6: An example mask where we are detecting and extracting only a single object from the image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20179'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_single_object.jpg)

**Figure 6:** An example mask where we are detecting and extracting only a single object from the image.

On **Lines 56-59** we detect contours in the `mask` and extract the largest one — this contour will represent the outline/boundary of a given object in the image.

Finally, given the contour of the object, all we need to do is draw the enclosing circle boundary surrounding the object on **Lines 62-65**. We could also compute the bounding box of the object, apply a bitwise operation, and extract each individual object as well.

Finally, **Lines 68 and 69** display the output image to our screen:

[![Image 9: Figure 7: The final output of our watershed algorithm -- we have been able to cleanly detect and draw the boundaries of each coin in the image, even though their edges are touching.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20260'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_coins_01.jpg)

**Figure 7:** The final output of our watershed algorithm — we have been able to cleanly detect and draw the boundaries of each coin in the image, even though their edges are touching.

As you can see, we have successfully detected all nine coins in the image. Furthermore, we have been able to cleanly draw the boundaries surrounding each coin as well. This is in stark contrast to the previous example using simple thresholding and contour detection where only _two_ objects were (incorrectly) detected.

Applying the watershed algorithm to images
------------------------------------------

Now that our `watershed.py` script is finished up, let’s apply it to a few more images and investigate the results:

$ python watershed.py --image images/coins\_02.png

[![Image 10: Figure 8: Again, we are able to cleanly segment each of the coins in the image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20279'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_coins_021.jpg)

**Figure 8:** Again, we are able to cleanly segment each of the coins in the image.

Let’s try another image, this time with overlapping coins:

$ python watershed.py --image images/coins\_03.png

[![Image 11: Figure 9: The watershed algorithm is able to segment the overlapping coins from each other.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20380'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_coins_03.jpg)

**Figure 9:** The watershed algorithm is able to segment the overlapping coins from each other.

In the following image, I decided to apply the watershed algorithm to the task of pill counting:

$ python watershed.py --image images/pills\_01.png

[![Image 12: Figure 10: We are able to correctly count the number of pills in the image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20284'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_pills_01.jpg)

**Figure 10:** We are able to correctly count the number of pills in the image.

The same is true for this image as well:

$ python watershed.py --image images/pills\_02.png

[![Image 13: Figure 11: Applying the watershed algorithm with OpenCV to count the number of pills in an image.](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20600%20278'%3E%3C/svg%3E)](https://pyimagesearch.com/wp-content/uploads/2015/10/watershed_output_pills_02.jpg)

**Figure 11:** Applying the watershed algorithm with OpenCV to count the number of pills in an image.

### What's next? We recommend [PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend).

**Course information:**  
84 total classes • 114+ hours of on-demand code walkthrough videos • Last updated: February 2024  
★★★★★ 4.84 (128 Ratings) • 16,000+ Students Enrolled

**I strongly believe that if you had the right teacher you could _master_ computer vision and deep learning.**

Do you think learning computer vision and deep learning has to be time-consuming, overwhelming, and complicated? Or has to involve complex mathematics and equations? Or requires a degree in computer science?

That’s _not_ the case.

All you need to master computer vision and deep learning is for someone to explain things to you in _simple, intuitive_ terms. _And that’s exactly what I do_. My mission is to change education and how complex Artificial Intelligence topics are taught.

If you're serious about learning computer vision, your next stop should be PyImageSearch University, the most comprehensive computer vision, deep learning, and OpenCV course online today. Here you’ll learn how to _successfully_ and _confidently_ apply computer vision to your work, research, and projects. Join me in computer vision mastery.

**Inside PyImageSearch University you'll find:**

*   ✓ **84 courses** on essential computer vision, deep learning, and OpenCV topics
*   ✓ **84 Certificates** of Completion
*   ✓ **114+ hours** of on-demand video
*   ✓ **Brand new courses released _regularly_**, ensuring you can keep up with state-of-the-art techniques
*   ✓ **Pre-configured Jupyter Notebooks in Google Colab**
*   ✓ Run all code examples in your web browser — works on Windows, macOS, and Linux (no dev environment configuration required!)
*   ✓ Access to **centralized code repos for _all_ 536+ tutorials** on PyImageSearch
*   ✓ **Easy one-click downloads** for code, datasets, pre-trained models, etc.
*   ✓ **Access** on mobile, laptop, desktop, etc.

[Click here to join PyImageSearch University](https://pyimagesearch.com/pyimagesearch-university/?utm_source=blogPost&utm_medium=bottomBanner&utm_campaign=What%27s%20next%3F%20I%20recommend)

Summary
-------

In this blog post we learned how to apply the watershed algorithm, a classic segmentation algorithm used to detect and extract objects in images that are _touching_ and/or _overlapping._

To apply the watershed algorithm we need to define _markers_ which correspond to the objects in our image. These markers can be either user-defined or we can apply image processing techniques (such as thresholding) to find the markers for us. When applying the watershed algorithm, it’s absolutely critical that we obtain accurate markers.

Given our markers, we can compute the Euclidean Distance Transform and pass the distance map to the watershed function itself, which “floods” valleys in the distance map, starting from the initial markers and moving outwards. Where the “pools” of water meet can be considered boundary lines in the segmentation process.

The output of the watershed algorithm is a set of labels, where each label corresponds to a unique object in the image. From there, all we need to do is loop over each of the labels individually and extract each object.

Anyway, I hope you enjoyed this post! Be sure download the code and give it a try. Try playing with various parameters, specifically the `min_distance` argument to the `peak_local_max` function. Note how varying the value of this parameter can change the output image.

![Image 14](data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20410%20567'%3E%3C/svg%3E)

#### Download the Source Code and FREE 17-page Resource Guide

Enter your email address below to get a .zip of the code and a **FREE 17-page Resource Guide on Computer Vision, OpenCV, and Deep Learning.** Inside you'll find my hand-picked tutorials, books, courses, and libraries to help you master CV and DL!