Introduction
In this tutorial we will continue investigating algorithms for features detection. In the previous tutorials we saw corner detection techniques using Harris and Shi-Tomasi corner detection algorithms. This time we will see another feature detection algorithm – contour detection algorithm. We will see how to detect the contours in a given image and then draw them.
Contour detection basics
Contour is a set of boundary pixels, which have the same color and intensity. The contour is some kind of an edge, which is why we work with edges in order to detect the contour. To detect contours in an image, we need to have a grayscale image first, which provides information about the intensities. Then we apply some edge detection algorithm like Canny, which will provide us with all the edges. Now, using these edges we will detect the different contours and save them in some data structure. OpenCV has a dedicated function to find contour from given edges.
Contour detection implementation
As I said we have a function in OpenCV to take care of applying the contour detection algorithm. We will see how to use it and the results from it. Let us see the exemplary code first.
// Find the contours in an image void findContours() { // Path to the input image std::string l_pathToInputImage{ "../Resources/building.jpg" }; // Create an object to hold the image data of the first image Mat l_image; // Read the image date from a file with no change to color scheme l_image = imread(l_pathToInputImage, IMREAD_UNCHANGED); // Check if we have read the first image data correctly if (!l_image.data) { std::cout << "No image data \n"; return; } // Convert the image to grayscale Mat l_imageGrayscale{}; cvtColor(l_image, l_imageGrayscale, COLOR_BGR2GRAY); blur(l_imageGrayscale, l_imageGrayscale, Size{ 3,3 }); // Apply the canny edge detection constexpr int c_kernelSize = 3; constexpr double c_lowerThreshold = 30.0; constexpr double c_upperThreshold = c_lowerThreshold * 3.0; Mat l_cannyImage{}; Canny(l_imageGrayscale, l_cannyImage, c_lowerThreshold, c_lowerThreshold, c_kernelSize); // Find the contours std::vector<std::vector<Point> > l_contours; std::vector<Vec4i> l_hierarchy; findContours(l_cannyImage, l_contours, l_hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE); // Draw the contours Mat l_result = Mat::zeros(l_cannyImage.size(), CV_8UC3); for (int i = 0; i < l_contours.size(); i++) { const Scalar c_lineColor{ 0, 255, 0 }; constexpr int c_lineThickness = 1; drawContours(l_result, l_contours, i, c_lineColor, c_lineThickness, LINE_8, l_hierarchy); } // Display the input image namedWindow("Input", WINDOW_NORMAL); cv::imshow("Input", l_image); // Display the contour result image namedWindow("Result", WINDOW_NORMAL); cv::imshow("Result", l_result); }
As always, we start with loading an image from the file system. We need to convert the image to a grayscale one as a next step, because we will be working with intensities. Then we blur the image in order to remove any noise. Next, we set some constants that we will use in the function calls.
We need to detect all the edges in the image, which we will use to detect the contours. For this purpose we call the Canny edge detection algorithm function. Once we have detected the edges, it is time to find the contours.
The OpenCV function for contour detection
The function, that we need to use in order to apply the contour detection algorithm is called findContours. There are two variants of this function. The only difference between them is that the second function takes one argument less – a hierarchy vector, which contains information about the image topology. We won’t be needing this one so we can go ahead and use the second variant. In my example I have used the first variant.
The first argument we provide to the function is the source matrix, in our case the image matrix which is a result from the Canny transformation. The second argument is the output vector, where we will store the results. It is actually a vector of vectors of points, because each contour will be a set of points (pixels). Then we provide a vector, where to store the hierarchy of the contours.
The fourth argument is the contour retrieval mode. Here you can find more information about it. I have used RETR_TREE in order to retrieve all the contour points and create a full hierarchy.
The fifth argument is the contour approximation method. Here is more information about it. I have selected the CHAIN_APPROX_SIMPLE because this mode will keep only the start and end points of straight lines contours, thus reducing the size of the result vector. OpenCV knows how to draw a contour with given only a start and an end point. The last argument is an offset for the contours, so we leave it with its default values.
Displaying the results
Now, we need to draw the contours. We have a vector with contours and their points so we go through each contour in a for loop and call the drawContours function. It needs a matrix, where it will draw the contours as a first argument. The second argument is the vector of contours. The third argument is the index of the given contour in the vector of contours. Next, we provide the color and the line thickness. Lastly, we provide the hierarchy vector, which could be left with the default value if we haven’t calculated it previously. The other two arguments we leave with the default values.
You can see below the results on my side. Be aware that all the calculations might take time, depending on the image width and height and how many edges are found. We can have different results by tweaking the Canny edge detection function’s parameters.

Conclusion
In this tutorial we saw how to detect and draw contours in a given image using ready OpenCV functions. Contours are very useful in different feature detection algorithms so we will be using this technique extensively.
As usual the code example is available below as an archive and in GitHub.
Next, we will see how to draw bounding boxes around objects using their contours.

Passionate developer, loving husband and caring father. As an introvert programming is my life. I work as a senior software engineer in a big international company. My hobbies include playing computer games (mostly World of Warcraft), watching TV series and hiking.