Contour detection is an important task in computer vision, not only because of the obvious element of detecting contours of subjects in an image or video frame, but also because of the derivative processes associated with identifying contours.
We can recognise the edges of objects and simply pinpoint them in a picture using contour detection. Many intriguing applications, such as image-foreground extraction, simple-image segmentation, detection, and identification, use it as a starting point.
So, using OpenCV, let's learn about contours and contour detection, and see how they may be utilised to create a variety of applications.
Contours are just a curve that connects all of the continuous points (along the border) that are the same colour or intensity. The contours are a useful tool for object detection and recognition as well as form analysis.
In this section we will learn how to find and draw contours using functions findContour()
and drawContour()
.Let's talk about the stages involved in detecting contours now that you've learned about them.
Reading and Converting Image to GrayScale:
The image should be read and converted to grayscale format. The conversion to grayscale is crucial because it prepares the image for the following stage. The image must be converted to a single channel grayscale image for thresholding, which is required for the contour detection technique to perform effectively.
input = cv::imread(argv[1]);
showImg(input,"Input Image");
cv::cvtColor(input,input_grey,cv::COLOR_BGR2GRAY);
Applying Gaussian Blur:
cv::GaussianBlur(input_grey,blur_img,kernel,0); // Applying Gaussian Blur with kernel 5x5
showImg(blur_img,"Blur Image");
Apply Binary Thresholding
Always perform binary thresholding or Canny edge detection to the grayscale image before looking for contours. We'll use binary thresholding in this case.
This turns the image to black and white, highlighting the points of interest to make the contour-detection algorithm's job easier. Thresholding makes the image's object's border totally white, with the same intensity across all pixels. From these white pixels, the programme can now discern the object's edges.
Note that black pixels with a value of 0 are considered background pixels and are disregarded.
One question may arise at this moment. What if we used single channels instead of grayscale (thresholded) images, such as R (red), G (green), or B (blue)? The contour detection algorithm will not perform well in this scenario. As previously stated, the algorithm detects contours by looking for borders and pixels of similar intensity. This information is significantly better provided by a binary image than by a single (RGB) colour channel image.
cv::threshold(blur_img,thresh_img,200,255,cv::THRESH_BINARY );
showImg(thresh_img,"Thresh Image");
A value of 255 will be assigned to any pixel with a value greater than 200 (white).In the resultant image, all remaining pixels will be set to 0. (black). You can play around with the threshold value of 200 because it is a configurable option.
Begin by calling the findContours() method. There are three arguments that must be provided, as indicated below. Please see the documentation page for more information on optional arguments.
image: The binary input picture obtained in the preceding step is called image.
contour-retrieval mode: The algorithm will retrieve all potential contours from the binary picture if this is set to RETR_TREE
. More contour retrieval modes are available. More information on these alternatives can be found here.
method: This is where the contour-approximation approach is defined. We'll use CHAIN_APPROX_NONE
in this case. We'll use this method to store ALL contour points, even though it's a little slower than CHAIN_APPROX_SIMPLE
.
It's important to note that mode relates to the type of contours that will be retrieved, whilst method refers to the points that will be saved within a contour. Both will be discussed in greater depth further down.
On the same image, the outcomes of several methods are easily visualised and understood.
As a result, in the code samples below, we make a clone of the original image before demonstrating the methods (not wanting to edit the original).
Then, to overlay the contours over the RGB image, use the drawContours()
function. There are four necessary arguments and several optional arguments for this function. The first four arguments are mandatory.
Please see the documentation page for more information on the optional arguments.
findContours()
. std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(thresh_img,contours,hierarchy,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
cv::Scalar red(0,0,255);
cv::drawContours(input,contours,-1,red,2);
showImg(input,"Detected Contour");
The parent-child relationship between contours is represented by hierarchies. You'll learn how each contour-retrieval option impacts image contour detection and generates hierarchical results.
Contour-detection algorithms may detect the following objects in an image:
We can fairly assume that the outer form is a parent of the inner shape in most circumstances where a shape contains other shapes.
The findContours()
function gives two outputs: the contours list and the hierarchy, as you can see.
Let's take a closer look at the contour hierarchy output.
The contour hierarchy is represented as an array, which is made up of four different arrays. It's written like this:
[Next, Previous, First Child, Parent]
So, what exactly do all of these numbers mean?
We've only used one retrieval approach, RETR_TREE
, to discover and draw contours thus far, but OpenCV also includes three other contour retrieval techniques: RETR_LIST
, RETR_EXTERNAL
, and RETR_CCOMP
.
There is no parent-child link between the extracted contours when using the RETR LIST contour retrieval method. As a result, the First Child and Parent index position values for all identified contour areas are always -1.
The contour retrieval method RETR EXTERNAL is a fascinating one. It only recognises parent contours and ignores child contours.
RETR_CCOMP
, unlike RETR_EXTERNAL
, recovers all of the contours in a picture. It also creates a two-level hierarchy for all of the forms or objects in the image.
This translates to:
Level 1 hierarchy will be applied to all outside outlines.
Level 2 hierarchy will be applied to all inner shapes.
RETR_TREE
, like RETR_CCOMP
, retrieves all of the contours. It also generates a whole hierarchy, with levels that aren't limited to one or two. Each contour can have its own hierarchy, which is determined by the level it is on and the parent-child relationship it has.
#include <iostream>
#include "opencv2/imgproc.hpp"
#include "opencv2/highgui.hpp"
#include <vector>
int ErrorMsg(const std::string& msg) {
std::cerr << "\n !!! Error !!!\n " << msg << "\n";
return -1;
}
void showImg(const cv::Mat& img, const std::string& name)
{
cv::namedWindow(name.c_str());
cv::imshow(name.c_str(),img);
}
void detect_contour(cv::Mat& thresh_img,cv::Mat input)
{
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
cv::findContours(thresh_img,contours,hierarchy,cv::RETR_TREE,cv::CHAIN_APPROX_SIMPLE);
cv::Scalar red(0,0,255);
cv::drawContours(input,contours,-1,red,2);
showImg(input,"Detected Contour");
}
int main ( int argc,char** argv) {
/** Variable Declaration **/
int threshold = 100;
cv::Mat input,input_grey,blur_img,thresh_img;
cv::Size kernel(3,3);
/***/
if (argc < 2 )
return ErrorMsg("Specify Image in command line");
input = cv::imread(argv[1]);
showImg(input,"Input Image");
cv::cvtColor(input,input_grey,cv::COLOR_BGR2GRAY);
if(input.empty())
return ErrorMsg("Could Not Open Image");
cv::GaussianBlur(input_grey,blur_img,kernel,0);
showImg(blur_img,"Blur Image");
cv::threshold(blur_img,thresh_img,200,255,cv::THRESH_BINARY );
showImg(thresh_img,"Thresh Image");
detect_contour(thresh_img,input);
cv::waitKey(0);
return 0;
}