Introduction to Watershed Algorithm
Erol
Posted on November 26, 2020
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
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");
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");
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");
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");
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");
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");
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");
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");
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");
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
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");
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");
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");
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");
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
Posted on November 26, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.