The findContours algorithm
The findContours algorithm is one of the most used OpenCV algorithms in regards to segment objects. This is because this algorithm was included in OpenCV from version 1.0 and gives developers more information and descriptors, including shapes, topological organizations, and so on:
void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
Let's explain each parameter:
- Image: Input binary image.
- Contours: A contour's output where each detected contour is a vector of points.
- Hierarchy: This is the optional output vector where the hierarchy of contours is saved. This is the topology of the image where we can get the relations between each contour. The hierarchy is represented as a vector of four indices, which are (next contour, previous contour, first child, parent contour). Negative indices are given where the given contour has no relationship with other contours. A more detailed explanation can be found at https://docs.opencv.org/3.4/d9/d8b/tutorial_py_contours_hierarchy.html.
- Mode: This method is used to retrieve the contours:
- RETR_EXTERNAL retrieves only the external contours.
- RETR_LIST retrieves all contours without establishing the hierarchy.
- RETR_CCOMP retrieves all contours with two levels of hierarchy, external and holes. If another object is inside one hole, this is put at the top of the hierarchy.
- RETR_TREE retrieves all contours, creating a full hierarchy between contours.
- Method: This allows us to use the approximation method for retrieving the contour's shapes:
- If CV_CHAIN_APPROX_NONE is set, then this does not apply any approximation to the contours and stores the contour's points.
- CV_CHAIN_APPROX_SIMPLE compresses all horizontal, vertical, and diagonal segments, storing only the start and end points.
- CV_CHAIN_APPROX_TC89_L1 and CV_CHAIN_APPROX_TC89_KCOS apply the Telchin chain approximation algorithm.
- Offset: This is an optional point value to shift all contours. This is very useful when we are working in an ROI and need to retrieve global positions.
Now that we know the parameters of the findContours function, let's apply this to our example:
void FindContoursBasic(Mat img) { vector<vector<Point> > contours; findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); Mat output= Mat::zeros(img.rows,img.cols, CV_8UC3); // Check the number of objects detected if(contours.size() == 0 ){ cout << "No objects detected" << endl; return; }else{ cout << "Number of objects detected: " << contours.size() << endl; } RNG rng(0xFFFFFFFF); for(auto i=0; i<contours.size(); i++){ drawContours(output, contours, i, randomColor(rng)); imshow("Result", output); }
}
Let's explain our implementation, line by line.
In our case, we don't need any hierarchy, so we are only going to retrieve the external contours of all possible objects. To do this, we can use the RETR_EXTERNAL mode and basic contour encoding by using the CHAIN_APPROX_SIMPLE method:
vector<vector<Point> > contours; vector<Vec4i> hierarchy; findContours(img, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
Like the connected component examples we looked at before, first we check how many contours we have retrieved. If there are none, then we exit our function:
// Check the number of objects detected if(contours.size() == 0){ cout << "No objects detected" << endl; return; }else{ cout << "Number of objects detected: " << contours.size() << endl; }
Finally, we draw the contour for each detected object. We draw this in our output image with different colors. To do this, OpenCV gives us a function to draw the result of the find contours image:
for(auto i=0; i<contours.size(); i++) drawContours(output, contours, i, randomColor(rng)); imshow("Result", output); }
The drawContours function allows the following parameters:
- Image: The output image to draw the contours.
- Contours: The vector of contours.
- Contour index: A number indicating the contour to draw. If this is negative, all contours are drawn.
- Color: The color to draw the contour.
- Thickness: If it is negative, the contour is filled with the chosen color.
- Line type: This specifies whether we want to draw with anti-aliasing or another drawing method.
- Hierarchy: This is an optional parameter that is only needed if you want to draw some of the contours.
- Max Level: This is an optional parameter that is only taken into account when the hierarchy parameter is available. If it is set to 0, only the specified contour is drawn. If it is 1, the function draws the current contour and the nested contours too. If it is set to 2, then the algorithm draws all of the specified contour hierarchy.
- Offset: This is an optional parameter for shifting the contours.
The result of our example can be seen in the following image: