DEV Community

loading...

Introduction to Watershed Algorithm

erol profile image Erol ・5 min read

Previously, I worked on Object Detection algorithm and I make detected coordinaters, rotations and dimension of objects. This project is about Watershed. Basically Watershed algoritm is used for seperating the other objects in image. After apply this algorithm, I need to apply some Low-Pass Filtering and Morphological Operations deu to determine area and edge of objects.

In Watershed Algoritm, I will apply this techniques:

1- Median Blurring
2- Gray Scale
3- Binary Threshold
4- Opening
5- Distance Transform
6- Threshold for foreground
7- Dilation for enlarging image to use for background
8- Connected Components
9- Watershed
10- Find and Draw Contours around of objects

I'll explain these subjects again when I explain code cells.


First of all I must import my libraries I'll use:

import cv2
import numpy as np
import matplotlib.pyplot as plt
Enter fullscreen mode Exit fullscreen mode

After that, I need to read my image I want to apply Watershed:

coin = cv2.imread("data/coins.jpg")

plt.figure(), plt.title("Original"), plt.imshow(coin), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

original

By the way, the semicolon (;) which is in end of plt.axis("off") is a trick for ingoring some inputs of matplotlib.

To remove the noises which are on image I will apply one of Low-Pass Filtering methods called MedianBlurring

coin_blur = cv2.medianBlur(src=coin, ksize=13)

plt.figure(), plt.title("Low Pass Filtering (Blurring)"), plt.imshow(coin_blur), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Low Pass Filtering (Blurring)

After that, I need to convert color of image to Gray

coin_gray = cv2.cvtColor(coin_blur, cv2.COLOR_BGR2GRAY)

plt.figure(), plt.title("Gray Scale"), plt.imshow(coin_gray, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Gray Scale

Using Binary Threshold I will make image specific between coins and background

ret, coin_thres = cv2.threshold(src=coin_gray, thresh=75, maxval=255, type=cv2.THRESH_BINARY)

plt.figure(), plt.title("Binary Threshold"), plt.imshow(coin_thres, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Binary Threshold

As you can see after Thresholding it is almostly clear between coins and background.

Now, I will try to draw these Contours

contour, hierarchy = cv2.findContours(image=coin_thres.copy(), mode=cv2.RETR_CCOMP, method=cv2.CHAIN_APPROX_SIMPLE)

for i in range(len(contour)):

    if hierarchy[0][i][3] == -1: # external contour
        cv2.drawContours(image=coin,contours=contour,contourIdx=i, color=(0,255,0), thickness=10)

plt.figure(figsize=(7,7)), plt.title("After Contour"), plt.imshow(coin, cmap="gray"), 
plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

After Contour1

But as you all can see it doesn't work, I couldn't seperate their edge.

I will use other method called Watershed for seperating and draw their edge. First method is not Watershed


Before Watershed, I need to apply all of this techniques. For that:

# read data
coin = cv2.imread("data/coins.jpg")
plt.figure(), plt.title("Original"), plt.imshow(coin), plt.axis("off");

# Blurring
coin_blur = cv2.medianBlur(src=coin, ksize=15)
plt.figure(), plt.title("Low Pass Filtering (Blurring)"), plt.imshow(coin_blur), plt.axis("off");

# Gray Scale
coin_gray = cv2.cvtColor(coin_blur, cv2.COLOR_BGR2GRAY)
plt.figure(), plt.title("Gray Scale"), plt.imshow(coin_gray, cmap="gray"), plt.axis("off");

# Binary Threshold
ret, coin_thres = cv2.threshold(src=coin_gray, thresh=65, maxval=255, type=cv2.THRESH_BINARY)
plt.figure(), plt.title("Binary Threshold"), plt.imshow(coin_thres, cmap="gray"), 
plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

original
Low Pass Filtering (Blurring)
Gray Scale
Binary Threshold

First I need to remove connetion between coins. Almostly every coins connetc other self, therefor using Opening from Morphological Operations, I will Open them

kernel = np.ones((3,3), np.uint8)

opening = cv2.morphologyEx(coin_thres, cv2.MORPH_OPEN, kernel=kernel, iterations=2)

plt.figure(), plt.title("Opening"), plt.imshow(opening, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Opening

To romevo the connection of between coins I will use Distance Transform.
After that I can see Distance between objects (coins)

dist_transform = cv2.distanceTransform(src=opening, distanceType=cv2.DIST_L2, maskSize=5)

plt.figure(), plt.title("Distance Transform"), plt.imshow(dist_transform, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Distance Transform

After finding distance, to find image which is in foreground what it is, I'll minimizing that using Threshold.

ret, sure_foreground = cv2.threshold(src=dist_transform, thresh=0.4*np.max(dist_transform), maxval=255, type=0)

plt.figure(), plt.title("Fore Ground"), plt.imshow(sure_foreground, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Fore Ground

To find background what it is, I will enlarging image using Dilate.

sure_background = cv2.dilate(src=opening, kernel=kernel, iterations=1) #int

sure_foreground = np.uint8(sure_foreground) # change its format to int
Enter fullscreen mode Exit fullscreen mode

And now, I can Subtrack them eachothers (BackGround - ForeGround) so that, the image can be more understandable. Here is the result of Opened and Dilated image

unknown = cv2.subtract(sure_background, sure_foreground)

plt.figure(), plt.title("BackGround - ForeGround = "), plt.imshow(unknown, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

BackGround - ForeGround =

After these steps, I need to find Markers for giving inputs for Watershed algorithm. And now I'll provide Connection between Components.

ret, marker = cv2.connectedComponents(sure_foreground)

marker = marker + 1

marker[unknown == 255] = 0 # White area is turned into Black to find island for watershed

plt.figure(), plt.title("Connection"), plt.imshow(marker, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Connection

After that, now I can apply Watershed Algorithm and I can make segmentation

marker = cv2.watershed(image=coin, markers=marker)

plt.figure(), plt.title("Watershed"), plt.imshow(marker, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

Watershed

As a last step, I'll find and Draw Contours around of Coins.

contour, hierarchy = cv2.findContours(image=marker.copy(), mode=cv2.RETR_CCOMP, method=cv2.CHAIN_APPROX_SIMPLE)

for i in range(len(contour)):

    if hierarchy[0][i][3] == -1:
        cv2.drawContours(image=coin,contours=contour,contourIdx=i, color=(255,0,0), thickness=3)

plt.figure(figsize=(7,7)), plt.title("After Contour"), plt.imshow(coin, cmap="gray"), plt.axis("off");
Enter fullscreen mode Exit fullscreen mode

After Contour2


You can find the notebook here: https://github.com/ierolsen/Object-Detection-with-OpenCV/blob/main/7-watershed.ipynb

Also other things about Object Detection: https://github.com/ierolsen/Object-Detection-with-OpenCV

Discussion (1)

pic
Editor guide
Collapse
tvinko profile image
Tomaž Vinko

very interesting. Can i put this algorithm to Algonia Market place? It will be visualised and free to download. I’ll add all credits to you in algo card description