Edges and Contours Basics with OpenCV | by Thiago Carvalho | Towards Data Science

I’ve been trying to learn computer vision with OpenCV, and in this article, I’ll explore edge detection and contours.
Last time I went through the basics of Kernels and Convolutions. Those methods are widely used and applied for different purposes; one of them caught my attention, and I think it should be an excellent next step to learn computer vision.
Edge detection and contours are used to locate points in images where the color or brightness have great changes.
That might seem quite easy and intuitive to us, but for a computer, it can get tricky.
Canny Edge Detection is an algorithm used to extract edges from images, and since it looks quite straightforward, I believe we can start with it.
The algorithm has four stages:
First — Performs noise reduction with a Gaussian Blur;
Second — Gets the gradient direction and magnitude with a Sobel kernel;
Third — Applies non-maximum suppression, which removes unwanted pixels that are not part of a contour;
Fourth — Applies the Hysteresis Thresholding that uses min and max values to filter the contours by the intensity gradient.
Even though it doesn’t look so easy, the implementation of Canny detection is quite comfortable with OpenCV.
We start by importing the libraries.
import cv2import numpy as npimport matplotlib.pyplot as plt
Now we can have a look at the image we want to extract the contours.
image = cv2.imread('Images/12.png')image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(image)
To use Canny, we need to convert our image to grayscale, then use the method .canny with the image, and the min and max thresholds.
I suggest that you experiment with those thresholds to see how the result changes.
g = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)edge = cv2.Canny(g, 60, 180)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(edge, cmap='Greys')
Nice, we can now use .findContours and .drawContours, to get and plot the external contours found with Canny detection.
contours = cv2.findContours(edge,                             cv2.RETR_EXTERNAL,                            cv2.CHAIN_APPROX_NONE)cv2.drawContours(image, contours[0], -1, (0,0,255), thickness = 2)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(image)
Cool, the contours variable is a list containing all the ones detected with Canny — That means we can access and manipulate them as such.
We can sort this list and pass the OpenCV function .countourArea as the key, that will sort all the detected contours by their sizes.
Let’s try plotting only the largest contour.
image = cv2.imread('Images/12.png')image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)contours, h = cv2.findContours(edge,                                cv2.RETR_EXTERNAL,                               cv2.CHAIN_APPROX_NONE)contours = sorted(contours, key=cv2.contourArea, reverse=True)cv2.drawContours(image, contours[0], -1, (0,0,255), thickness = 5)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(image)
*Note that I reloaded the original image since we draw over the last one.
That interesting, I think I was expecting something else, like a whole lip or even the tongue. Instead, the most prominent contour is a part of a mouth that somehow got connected to a tooth.
I guess that shows us that these methods require either previous manipulations or more straightforward images to achieve the desired result. In the way it is now, our contours are in the right places, but they are too granulated to retrieve a complete shape.
Nonetheless, that method allows us to select a specific contour we’re looking for or even filter some range of values.
There are other ways of finding contours; a simple binarization, for example, can also do the job.
image = cv2.imread('Images/12.png')image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)r, t = cv2.threshold(gray, 200, 255, cv2.THRESH_BINARY_INV)contours, h = cv2.findContours(t, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)contours = sorted(contours, key=cv2.contourArea, reverse=True)cv2.drawContours(image, contours, -1, (0,0,255), thickness = 5)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(image)
Again, you can try changing the thresholds to see what happens.
A typical application of contour is to detect geometrical shapes in images, and this can be quite useful for simplifying problems that involve classification or object detection.
The issue is, depending on the quality, angle, noise, and many other factors, contours are rarely simple polygons. That’s where Convex Hull comes in. The idea here is to simplify the polygons generated by our previous edge detection methods.
Have a look at Wikipedia definition of Convex Hull:
In geometry, the convex hull or convex envelope or convex closure of a shape is the smallest convex set that contains it.
Quite complicated, but OpenCV facilitates this step with the function .convexHull.
image = cv2.imread('Images/14.jpg')image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)g = cv2.cvtColor(image,cv2.COLOR_RGB2GRAY)edge = cv2.Canny(g, 140, 210)contours, hierarchy = cv2.findContours(edge, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)for c in contours:    hull = cv2.convexHull(c)    cv2.drawContours(image, [hull], 0, (0, 255, 0), 2)fig, ax = plt.subplots(1, figsize=(12,8))plt.imshow(image)
Nice, we can see that the contour is not following precisely the drawing but rather simplifying the shape of the drops.
I think that’s a good start with edge detection, contours, and the different manipulations and steps we can add to the process. I encourage you to play around with the parameters, thresholds and different methods of edge detection.
Thanks for reading my article. I hope you enjoyed it.
Previous articles:Kernels and Convolutions Basics;Transformations Basics;Getting Started with OpenCV;
Resources:OpenCV Canny;OpenCV Feature Detection;OpenCV ConvexHull;Finding the Convex Hull of a Simple Polygon — Ronald L. Graham;OpenCV Structural Analysis and Shape Description;