Introduction
In this tutorial we will continue with the contour topics. Previously we saw how to detect them in a given image. Now, we will see how to draw a bounding box around the contour of a given object. The bounding box is a quit useful feature. We can use it to separate different objects and differentiate them. We can also use them to check for collisions between the objects.
In this tutorial we will see several OpenCV functions, which will help us draw bounding boxes around the contours of objects. We will draw rectangles and circles around those objects.
Bounding box basics
A bounding box is a box with the smallest area or volume within which all the points of a given object lie. The bounding boxes can be of any dimension. Usually we use 2D or 3D boxes to surround an object. Using such boxes can significantly decrease the complexity of the geometric operations – we can process the object as one simple box instead of a complex object with lots of meshes. More information can be found here.
Bounding box implementation
We will see several OpenCV functions, which we will use to draw bounding boxes. There are functions to draw normal bounding boxes and rotated ones. The rotated ones take into consideration the orientation of the object.
Normal bounding boxes
The normal bounding box is always rotated at 0 degrees, no matter the orientation of the object it surrounds. We will draw one rectangle and one circle around the object. It will take the object contour into account and it will put the box around it. Here is the code.
// Add bounding boxes and circles around the contours in an image void boundContours() { // Path to the input image std::string l_pathToInputImage{ "../Resources/shapes.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); // Get the bounding boxes and circles std::vector<std::vector<Point> > l_contoursPolygones(l_contours.size()); std::vector<Rect> l_boundedRectangles(l_contours.size()); std::vector<Point2f> l_circlesCenters(l_contours.size()); std::vector<float> l_circleRadiuses(l_contours.size()); for (size_t i = 0; i < l_contours.size(); i++) { approxPolyDP(l_contours[i], l_contoursPolygones[i], 3, true); l_boundedRectangles[i] = boundingRect(l_contoursPolygones[i]); minEnclosingCircle(l_contoursPolygones[i], l_circlesCenters[i], l_circleRadiuses[i]); } // Draw the contours and the bounding boxes and circles 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 }; const Scalar c_boundingLineColor{ 0, 0, 255 }; constexpr int c_lineThickness = 1; drawContours(l_result, l_contours, i, c_lineColor, c_lineThickness, LINE_8, l_hierarchy); rectangle(l_result, l_boundedRectangles[i].tl(), l_boundedRectangles[i].br(), c_boundingLineColor, 2); circle(l_result, l_circlesCenters[i], (int)l_circleRadiuses[i], c_boundingLineColor, 2); } // Display the input image namedWindow("Input", WINDOW_NORMAL); cv::imshow("Input", l_image); // Display the result image namedWindow("Result", WINDOW_NORMAL); cv::imshow("Result", l_result); }
As usual, we start with the loading of the image. Then we do the same operations as we did in the previous post in order to find the contours of the objects – we transform the image to a grayscale one and then we blur it. Next, we apply the Canny transformation. Finally, we find all the contours around the objects in the image.
The first new thing that we do starting from line #38 is to define few vectors, which will hold the coordinates of the bounding rectangles and the center points and radiuses of the bounding box circles.
Processing all the contours
Next, we go through all the contours, which we found. For each one of them we call approxPolyDP() to approximate the polygons. This function takes as a first argument the contour points of the given contour and as a second argument a vector where it will store the result from the approximation. The third argument is the approximation accuracy and the fourth argument is whether the approximated curve shall be closed. We want to consider only the closed curves.
The result from the above function will be used in the next function call to boundingRect() function. This function will calculate the bounding rectangle based on the provided approximated contour. The only argument it takes is the approximated contour points.
Next, by calling the minEnclosingCircle() function we find the circle of the minimum area enclosing the object’s contour. It takes as a first argument the approximated contour point. The second argument is a vector, which the function will fill with the circle center point’s coordinates. The third argument is for the circles’ radiuses.
The above three function will find the rectangle and circle bounding boxes. Next, we can use that information however we want. In our case we will draw them.
Drawing the results
The last few lines of code are for the drawing of the results. For each contour we draw it in green. Then using OpenCV functions we draw each bounding box rectangle and circle in red. We use functions that we saw previously. You can see how the result looks like on my side.

Rotated bounding boxes
OpenCV also provides functions to calculate rotated bounding boxes, which consider the orientation of the object. See below an exemplary code on how we use it.
// Add rotated bounding boxes and circles around the contours in an image void rotatedBoundContours() { // Path to the input image std::string l_pathToInputImage{ "../Resources/shapes.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); // Get the bounding boxes and circles std::vector<RotatedRect> l_minRectangles(l_contours.size()); std::vector<RotatedRect> l_minEllipses(l_contours.size()); for (size_t i = 0; i < l_contours.size(); i++) { l_minRectangles[i] = minAreaRect(l_contours[i]); if (l_contours[i].size() > 5) { l_minEllipses[i] = fitEllipse(l_contours[i]); } } // Draw the contours and the bounding boxes and circles 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 }; const Scalar c_boundingLineColor{ 0, 0, 255 }; constexpr int c_lineThickness = 1; drawContours(l_result, l_contours, i, c_lineColor, c_lineThickness, LINE_8, l_hierarchy); Point2f l_rectanglePoints[4]; l_minRectangles[i].points(l_rectanglePoints); for (int j = 0; j < 4; j++) { line(l_result, l_rectanglePoints[j], l_rectanglePoints[(j + 1) % 4], c_boundingLineColor); } ellipse(l_result, l_minEllipses[i], c_boundingLineColor, 2); } // Display the input image namedWindow("Input", WINDOW_NORMAL); cv::imshow("Input", l_image); // Display the result image namedWindow("Result", WINDOW_NORMAL); cv::imshow("Result", l_result); }
Similarly, to the first example, we load an image, then we find the contours of the objects. The new thing here is that we create two vectors which will hold objects of type RotatedRect – one for the rectangle and one for the ellipse (this time we use ellipse instead of circle).
For each contour that we have found, we find the minimum rotated rectangle, which will enclose the object. We do this with the minAreaRect() function. It takes as a first and only argument the points of the contour and returns a rotated rectangle.
Then we do the same for finding the ellipse, enclosing the contour. For this we use the fitEllipse() OpenCV function. Again, it takes only one argument which is the points of the contour.
Drawing the results
The last few lines of code are for the drawing of the results. We draw each contour in green. Then using OpenCV functions we draw each rotated bounding box rectangle and ellipse in red. We use functions that we saw previously. You can see how the result looks like on my side and how it is different than the one from the previous example. This results look more optimal, because it covers smaller area.

Conclusion
In this tutorial we saw how to detect and draw bounding box around given contours in a given image using ready OpenCV functions. The bounding boxes are very useful when working with objects and contours.
As usual the code example is available below as an archive and in GitHub.
In the next blog post we will see how to draw convex hulls around the 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.