OpenCV's fitEllipse() sometimes returns completely wrong ellipses

后端 未结 4 997
轮回少年
轮回少年 2020-12-25 14:54

My goal is to recognize all the shapes present in an image. The idea is:

  1. Extract contours
  2. Fit each contour with different shapes
  3. The correct
4条回答
  •  执笔经年
    2020-12-25 15:54

    If you are having problems with cv::fitEllipse(), this post discuss a few methods to minimize those errors that happen when the cv::RotatedRect is draw directly without any further tests. Turns out cv::fitEllipse() is not perfect and can have issues as noted in the question.

    Now, it's not entirely clear what the constraints of the project are, but another way to solve this problem is to separate these shapes based on the area of the contours:

    enter image description here

    This approach is extremely simple yet efficient on this specific case: the area of a circle varies between 1300-1699 and the area of a triangle between 1-1299.

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include 
    
    int main()
    {
        cv::Mat img = cv::imread("input.png");
        if (img.empty())
        {
            std::cout << "!!! Failed to open image" << std::endl;
            return -1;
        }
    
        /* Convert to grayscale */
    
        cv::Mat gray;
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
    
        /* Convert to binary */
    
        cv::Mat thres;
        cv::threshold(gray, thres, 127, 255, cv::THRESH_BINARY);
    
        /* Find contours */
    
        std::vector > contours;
        cv::findContours(thres, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);
    
        int circles = 0;
        int triangles = 0;
        for (size_t i = 0; i < contours.size(); i++)
        {
            // Draw a contour based on the size of its area:
            //  - Area > 0 and < 1300 means it's a triangle;
            //  - Area >= 1300 and < 1700 means it's a circle;
    
            double area = cv::contourArea(contours[i]);
            if (area > 0 && area < 1300)
            {
                std::cout << "* Triangle #" << ++triangles << " area: " << area << std::endl;
                cv::drawContours(img, contours, i, cv::Scalar(0, 255, 0), -1, 8); // filled (green)
                cv::drawContours(img, contours, i, cv::Scalar(0, 0, 255), 2, 8); // outline (red)
            }
            else if (area >= 1300 && area < 1700)
            {
                std::cout << "* Circle #" << ++circles << " area: " << area << std::endl;
                cv::drawContours(img, contours, i, cv::Scalar(255, 0, 0), -1, 8); // filled (blue)
                cv::drawContours(img, contours, i, cv::Scalar(0, 0, 255), 2, 8); // outline (red)
            }
            else
            {
                std::cout << "* Ignoring area: " << area << std::endl;
                continue;
            }
    
            cv::imshow("OBJ", img);
            cv::waitKey(0);
        }   
    
        cv::imwrite("output.png", img);
        return 0;
    }
    

    You can invoke other functions to draw more precise outline (borders) of the shapes.

提交回复
热议问题