Contour Extracion is a common image processing technique aims at indentifying the structural outline of objects in image. A contour can help in indentifying a shape of object and for such a reason, they are widely use for segmentation, text recognition, shape recognition, etc. A contour is a curved line representing the boundary of the same values or the same intensities. Before going deeper into the contour world, we have to understand the difference between edges and contours.

Edges are computed as points that are extrema of the image gradient in the direction of the gradient. In other words, they are points with values significantly different from the values of the neighbor points.

While, on the other hand, Contours are close curves that are obtained from edges representing the boundary of a figure. So, from the definition, we can easily understand that before finding the contours in an image, we have to extract the edges. To this end, we use one of the most common technique called: Canny edge detector.

Canny Edge Detector

This method has been developed by John F. Canny in 1986 and it is an edge detection operator that uses a multi-stage algorithm to detect a wide range of edges in images. The approach is based on 3 steps:

  1. Intensity Gradient Computation: a filter based on a Gaussian is used of computing the intensity. Moreover, by using a Gaussian approach, we reduce the noise in the image;
  2. Thinning Down: the edge candidates are thinned down to a 1-pixel curve by removing non-maximum pixels of the gradient magnitude;
  3. Hysteresis Thresholding: the useles pixels are removed by using hysteresis thresholding on the gradient magnitude.

So, we have 3 parameters to consider: (1) the size of the Gaussian filter, (2) the low and (3) hish threshold for the hysteresis thresholding.

Let’s have look at the code:

#import opencv and numpy
import cv2
import numpy as np

#read the image
image = cv2.imread("shapes.png")

#convert it to black and white
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

#gaussian kernel
kernel_size = 3
#low thresh
low = 50
#high thresh
high = 100

#Canny computation
canny = cv2.Canny(gray, low, high, kernel_size)
canny_color = np.concatenate((canny, canny, canny), axis=2)

#concatenating the bgr image and the canny one
concat_image = np.concatenate((image, canny_color), axis=1)

#show the image
cv2.imshow("result", concat_image)
cv2.waitKey(0)

The output of the code is the following:

Edge Detection

Now, that we have the edges, we can compute the contours.

Contours Extraction

Contour detection and extraction can be implemented by the opencv function cv2.findContours(). Such a function takes 2 important parameters as input:

  1. mode: indicating the way of finding contours (cv2.RETR_TREE, cv2.RETR_EXTERNAL, cv2.RETR_LIST, cv2.RETR_CCOMP);
  2. method: indicating the approximation method (cv2.CHAIN_APPROX_NONE, cv2.CHAIN_SIMPLe) mode is the way of finding contours, and method is the approximation method for the detection.

For more information about the 2 paramenters, you can have a look at the official documentation

Now, we are going to modify the code we used for finding the edges, in order to find the contours:

#import opencv and numpy
import cv2
import numpy as np

#for making random colors
import random as rng
rng.seed(12345)

#read the image
image = cv2.imread("shapes.png")

##Image preprocessing

#defining the kernel size
kernel_size = (3, 3)
#set sigma X = 0 (sigma Y is set as 0 as default)
sigmaX = 0
#apply the filter
blur_image = cv2.GaussianBlur(image, kernel_size, sigmaX)

#convert it to black and white
gray = cv2.cvtColor(blur_image, cv2.COLOR_BGR2GRAY)

#gaussian kernel
kernel_size = 3
#low thresh
low = 100
#high thresh
high = 200

#Canny computation
canny = cv2.Canny(gray, low, high, kernel_size)

cv2.imshow("Canny", canny)

#contour detection
contours, hierarchy = cv2.findContours(canny, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

#create a new image for drwaing all the contours
contour_img = np.zeros(image.shape, dtype=np.uint8)

#draw all the contours
for i in range(len(contours)):
    color = (rng.randint(0,256), rng.randint(0,256), rng.randint(0,256))
    cv2.drawContours(contour_img, contours, i, color, 2, cv2.LINE_8, hierarchy, 0)

#concatenating the bgr image and the canny one
concat_image = np.concatenate((image, contour_img), axis=1)

#show the image
cv2.imshow("result", concat_image)
cv2.waitKey(0)

As you can see, in the code, we added a preprocessing step with a Gaussian filter in order to extract only the strongest edges.

The results of the code above is the following:

Contour Detection

If we change the mode from cv2.RETR_TREE to cv2.RETR_EXTERNAL, we get only the external contours:

External Contour Detection

We covered one of the important topic of the classical computer vision methodology. We can use contours for many things: from shape detection to text recognition.