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}