samples/cpp/geometry.cpp#
An example program illustrates the use of cv::convexHull, cv::fitEllipse, cv::minEnclosingTriangle, cv::minEnclosingCircle and cv::minAreaRect.
1/*******************************************************************************
2 *
3 * This program demonstrates various shape fitting techniques using OpenCV.
4 * It reads an image, applies binary thresholding, and then detects contours.
5 *
6 * For each contour, it fits and draws several geometric shapes including
7 * convex hulls, minimum enclosing circles, rectangles, triangles, and ellipses
8 * using different fitting methods:
9 * 1: OpenCV's original method fitEllipse which implements Fitzgibbon 1995 method.
10 * 2: The Approximate Mean Square (AMS) method fitEllipseAMS proposed by Taubin 1991
11 * 3: The Direct least square (Direct) method fitEllipseDirect proposed by Fitzgibbon1999
12 *
13 * The results are displayed with unique colors
14 * for each shape and fitting method for clear differentiation.
15 *
16 *
17 *********************************************************************************/
18
19#include "opencv2/geometry/2d.hpp"
20#include "opencv2/imgproc.hpp"
21#include "opencv2/highgui.hpp"
22#include <iostream>
23#include <vector>
24
25using namespace cv;
26using namespace std;
27
28const string hot_keys =
29 "\n\nHot keys: \n"
30 "\tESC - quit the program\n"
31 "\tq - quit the program\n"
32 "\tc - make the circle\n"
33 "\tr - make the rectangle\n"
34 "\th - make the convexhull\n"
35 "\tt - make the triangle\n"
36 "\te - make the ellipse\n"
37 "\ta - make all shapes\n"
38 "\t0 - use OpenCV's method for ellipse fitting\n"
39 "\t1 - use Approximate Mean Square (AMS) method for ellipse fitting \n"
40 "\t2 - use Direct least square (Direct) method for ellipse fitting\n";
41
42static void help(char** argv)
43{
44 cout << "\nThis program demonstrates various shape fitting techniques on a set of points using functions: \n"
45 << "minAreaRect(), minEnclosingTriangle(), minEnclosingCircle(), convexHull(), and ellipse().\n\n"
46 << "Usage: " << argv[0] << " [--image_name=<image_path> Default: ellipses.jpg]\n\n";
47 cout << hot_keys << endl;
48}
49
50void processImage(int, void*);
51void drawShapes(Mat &img, const vector<Point> &points, int ellipseMethod, string shape);
52void drawConvexHull(Mat &img, const vector<Point> &points);
53void drawMinAreaRect(Mat &img, const vector<Point> &points);
54void drawFittedEllipses(Mat &img, const vector<Point> &points, int ellipseMethod);
55void drawMinEnclosingCircle(Mat &img, const vector<Point> &points);
56void drawMinEnclosingTriangle(Mat &img, const vector<Point> &points);
57
58// Shape fitting options
59Mat image;
60enum EllipseFittingMethod {
61 OpenCV_Method,
62 AMS_Method,
63 Direct_Method
64};
65
66struct UserData {
67 int sliderPos = 70;
68 string shape = "--all";
69 int ellipseMethod = OpenCV_Method;
70};
71
72const char* keys =
73 "{help h | | Show help message }"
74 "{@image |ellipses.jpg| Path to input image file }";
75
76int main(int argc, char** argv) {
77
78 cv::CommandLineParser parser(argc, argv, keys);
79 help(argv);
80
81 if (parser.has("help"))
82 {
83 return 0;
84 }
85
86 UserData userData; // variable to pass all the necessary values to trackbar callback
87
88 string filename = parser.get<string>("@image");
89 image = imread(samples::findFile(filename), IMREAD_COLOR); // Read the image from the specified path
90
91 if (image.empty()) {
92 cout << "Could not open or find the image" << endl;
93 return -1;
94 }
95
96 namedWindow("Shapes", WINDOW_AUTOSIZE); // Create a window to display the results
97 createTrackbar("Threshold", "Shapes", NULL, 255, processImage, &userData); // Create a threshold trackbar
98 setTrackbarPos("Threshold", "Shapes", userData.sliderPos);
99
100 for(;;) {
101 char key = (char)waitKey(0); // Listen for a key press
102 if (key == 'q' || key == 27) break; // Exit the loop if 'q' or ESC is pressed
103
104 switch (key) {
105 case 'h': userData.shape = "--convexhull"; break;
106 case 'a': userData.shape = "--all"; break;
107 case 't': userData.shape = "--triangle"; break;
108 case 'c': userData.shape = "--circle"; break;
109 case 'e': userData.shape = "--ellipse"; break;
110 case 'r': userData.shape = "--rectangle"; break;
111 case '0': userData.ellipseMethod = OpenCV_Method; break;
112 case '1': userData.ellipseMethod = AMS_Method; break;
113 case '2': userData.ellipseMethod = Direct_Method; break;
114 default: break; // Do nothing for other keys
115 }
116
117 processImage(userData.sliderPos, &userData); // Process the image with the current settings
118 }
119
120 return 0;
121}
122
123// Function to draw the minimum enclosing circle around given points
124void drawMinEnclosingCircle(Mat &img, const vector<Point> &points) {
125 Point2f center;
126 float radius = 0;
127 minEnclosingCircle(points, center, radius); // Find the enclosing circle
128 // Draw the circle
129 circle(img, center, cvRound(radius), Scalar(0, 0, 255), 2, LINE_AA);
130}
131
132// Function to draw the minimum enclosing triangle around given points
133void drawMinEnclosingTriangle(Mat &img, const vector<Point> &points) {
134 vector<Point> triangle;
135 minEnclosingTriangle(points, triangle); // Find the enclosing triangle
136
137 if (triangle.size() != 3) {
138 return;
139 }
140 // Use polylines to draw the triangle. The 'true' argument closes the triangle.
141 polylines(img, triangle, true, Scalar(255, 0, 0), 2, LINE_AA);
142
143}
144
145// Function to draw the minimum area rectangle around given points
146void drawMinAreaRect(Mat &img, const vector<Point> &points) {
147 RotatedRect box = minAreaRect(points); // Find the minimum area rectangle
148 Point2f vtx[4];
149 box.points(vtx);
150 // Convert Point2f to Point because polylines expects a vector of Point
151 vector<Point> rectPoints;
152 for (int i = 0; i < 4; i++) {
153 rectPoints.push_back(Point(cvRound(vtx[i].x), cvRound(vtx[i].y)));
154 }
155
156 // Use polylines to draw the rectangle. The 'true' argument closes the loop, drawing a rectangle.
157 polylines(img, rectPoints, true, Scalar(0, 255, 0), 2, LINE_AA);
158}
159
160// Function to draw the convex hull of given points
161void drawConvexHull(Mat &img, const vector<Point> &points) {
162 vector<Point> hull;
163 convexHull(points, hull, false); // Find the convex hull
164 // Draw the convex hull
165 polylines(img, hull, true, Scalar(255, 255, 0), 2, LINE_AA);
166}
167
168inline static bool isGoodBox(const RotatedRect& box) {
169 //size.height >= size.width awalys,only if the pts are on a line or at the same point,size.width=0
170 return (box.size.height <= box.size.width * 30) && (box.size.width > 0);
171}
172
173// Function to draw fitted ellipses using different methods
174void drawFittedEllipses(Mat &img, const vector<Point> &points, int ellipseMethod) {
175 switch (ellipseMethod) {
176 case OpenCV_Method: // Standard ellipse fitting
177 {
178 RotatedRect fittedEllipse = fitEllipse(points);
179 if (isGoodBox(fittedEllipse)) {
180 ellipse(img, fittedEllipse, Scalar(255, 0, 255), 2, LINE_AA);
181 }
182 putText(img, "OpenCV", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
183 }
184 break;
185 case AMS_Method: // AMS ellipse fitting
186 {
187 RotatedRect fittedEllipseAMS = fitEllipseAMS(points);
188 if (isGoodBox(fittedEllipseAMS)) {
189 ellipse(img, fittedEllipseAMS, Scalar(255, 0, 255), 2, LINE_AA);
190 }
191 putText(img, "AMS", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
192 }
193 break;
194 case Direct_Method: // Direct ellipse fitting
195 {
196 RotatedRect fittedEllipseDirect = fitEllipseDirect(points);
197 if (isGoodBox(fittedEllipseDirect)) {
198 ellipse(img, fittedEllipseDirect, Scalar(255, 0, 255), 2, LINE_AA);
199 }
200 putText(img, "Direct", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
201 }
202 break;
203 default: // Default case falls back to OpenCV method
204 {
205 RotatedRect fittedEllipse = fitEllipse(points);
206 if (isGoodBox(fittedEllipse)) {
207 ellipse(img, fittedEllipse, Scalar(255, 0, 255), 2, LINE_AA);
208 }
209 putText(img, "OpenCV (default)", Point(img.cols - 80, 20), FONT_HERSHEY_SIMPLEX, 0.5, Scalar(255, 0, 255), 2, LINE_AA);
210 cout << "Warning: Invalid ellipseMethod value. Falling back to default OpenCV method." << endl;
211 }
212 break;
213 }
214}
215
216// Function to draw shapes
217void drawShapes(Mat &img, const vector<Point> &points, int ellipseMethod, string shape) {
218 if (shape == "--circle") {
219 drawMinEnclosingCircle(img, points);
220 } else if (shape == "--triangle") {
221 drawMinEnclosingTriangle(img, points);
222 } else if (shape == "--rectangle") {
223 drawMinAreaRect(img, points);
224 } else if (shape == "--convexhull") {
225 drawConvexHull(img, points);
226 } else if (shape == "--ellipse"){
227 drawFittedEllipses(img, points, ellipseMethod);
228 }
229 else if (shape == "--all") {
230 drawMinEnclosingCircle(img, points);
231 drawMinEnclosingTriangle(img, points);
232 drawMinAreaRect(img, points);
233 drawConvexHull(img, points);
234 drawFittedEllipses(img, points, ellipseMethod);
235 }
236}
237
238// Main function to process the image based on the current trackbar position
239void processImage(int position, void* userData){
240
241 UserData* data = static_cast<UserData*>(userData);
242
243 data->sliderPos = position;
244
245 Mat processedImg = image.clone(); // Clone the original image for processing
246 Mat gray;
247 cvtColor(processedImg, gray, COLOR_BGR2GRAY); // Convert to grayscale
248 threshold(gray, gray, data->sliderPos, 255, THRESH_BINARY); // Apply binary threshold
249
250 Mat filteredImg;
251 medianBlur(gray, filteredImg, 3);
252
253 vector<vector<Point>> contours;
254 findContours(filteredImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE); // Find contours
255
256 if (contours.empty()) {
257 return;
258 }
259
260 imshow("Mask", filteredImg); // Show the mask
261 for (size_t i = 0; i < contours.size(); ++i) {
262 if (contours[i].size() < 5) { // Check if the contour has enough points
263 continue;
264 }
265 drawShapes(processedImg, contours[i], data->ellipseMethod, data->shape);
266 }
267
268 imshow("Shapes", processedImg); // Display the processed image with fitted shapes
269}