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)

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

Forem Open with the Forem app