C++ OpenCV';s fitEllipse()有时返回完全错误的椭圆

C++ OpenCV';s fitEllipse()有时返回完全错误的椭圆,c++,opencv,image-processing,C++,Opencv,Image Processing,我的目标是识别图像中的所有形状。 这个想法是: 提取等高线 用不同的形状拟合每个轮廓 正确的形状应该是面积最接近的形状 轮廓面积 示例图像: 我使用fitEllipse() 可能正确的椭圆用蓝色填充,边界椭圆用黄色填充。 可能不正确的轮廓用绿色填充,(错误的)边界椭圆为青色 如您所见,第一行中包围三角形的椭圆看起来非常适合最佳拟合。第三行三角形的边界椭圆似乎不是最合适的,但仍然可以作为拒绝错误椭圆的标准 但我不明白为什么剩下的三角形的边界椭圆完全在它们的轮廓之外。 最坏的情况是最后一行的第三

我的目标是识别图像中的所有形状。 这个想法是:

  • 提取等高线
  • 用不同的形状拟合每个轮廓
  • 正确的形状应该是面积最接近的形状 轮廓面积
  • 示例图像:

    我使用
    fitEllipse()
    

    可能正确的椭圆用蓝色填充,边界椭圆用黄色填充。 可能不正确的轮廓用绿色填充,(错误的)边界椭圆为青色

    如您所见,第一行中包围三角形的椭圆看起来非常适合最佳拟合。第三行三角形的边界椭圆似乎不是最合适的,但仍然可以作为拒绝错误椭圆的标准

    但我不明白为什么剩下的三角形的边界椭圆完全在它们的轮廓之外。 最坏的情况是最后一行的第三个三角形:椭圆完全错误,但它的区域恰好靠近轮廓的区域,因此三角形被错误地识别为椭圆

    我错过什么了吗?我的代码:

    #include <iostream>
    #include <opencv/cv.h>
    #include <opencv/highgui.h>
    
    using namespace std;
    using namespace cv;
    
    void getEllipses(vector<vector<Point> >& contours, vector<RotatedRect>& ellipses) {
        ellipses.clear();
        Mat img(Size(800,500), CV_8UC3);
        for (unsigned i = 0; i<contours.size(); i++) {
            if (contours[i].size() >= 5) {
                RotatedRect temp = fitEllipse(Mat(contours[i]));
                if (area(temp) <= 1.1 * contourArea(contours[i])) {
                    //cout << area(temp) << " < 1.1* " << contourArea(contours[i]) << endl;
                    ellipses.push_back(temp);
                    drawContours(img, contours, i, Scalar(255,0,0), -1, 8);
                    ellipse(img, temp, Scalar(0,255,255), 2, 8);
                    imshow("Ellipses", img);
                    waitKey();
                } else {
                    //cout << "Reject ellipse " << i << endl;
                    drawContours(img, contours, i, Scalar(0,255,0), -1, 8);
                    ellipse(img, temp, Scalar(255,255,0), 2, 8);
                    imshow("Ellipses", img);
                    waitKey();
                }
            }
        }
    }
    
    int main() {
        Mat img = imread("image.png", CV_8UC1);
        threshold(img, img, 127,255,CV_THRESH_BINARY);
        vector<vector<Point> > contours;
        vector<Vec4i> hierarchy;
        findContours(img, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
        vector<RotatedRect> ellipses;
        getEllipses(contours, ellipses);
        return 0;
    }
    
    #包括
    #包括
    #包括
    使用名称空间std;
    使用名称空间cv;
    void getellipes(向量和等高线、向量和椭圆){
    省略号。清除();
    材料img(尺寸(800500),CV_8UC3);
    for(无符号i=0;i=5){
    RotatedRect temp=fitEllipse(图[i]);
    
    如果(面积(温度),最好逐像素比较,即轮廓和“拟合”椭圆之间的重叠百分比是多少


    另一个更简单的想法是还比较轮廓的质心及其椭圆拟合。

    如果您在
    cv::fitEllipse()
    方面遇到问题,请讨论一些方法,以最大限度地减少在不进行任何进一步测试而直接绘制
    cv::RotatedRect
    时发生的错误。结果是
    cv::fitEllipse()
    不是完美的,可能会出现问题,如问题中所述

    现在,还不完全清楚项目的约束是什么,但是解决这个问题的另一种方法是根据轮廓的面积来分离这些形状

    这种方法非常简单,但在这种特殊情况下非常有效:圆的面积在1300-1699之间变化,三角形的面积在1-1299之间变化

    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    
    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<std::vector<cv::Point> > 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;
    }
    
    #包括“opencv2/highgui/highgui.hpp”
    #包括“opencv2/imgproc/imgproc.hpp”
    #包括
    int main()
    {
    cv::Mat img=cv::imread(“input.png”);
    if(img.empty())
    {
    标准::cout 0和面积<1300)
    {
    
    std::cout将
    cv::CHAIN\u approw\u SIMPLE
    更改为
    cv::CHAIN\u approw\u NONE
    在调用
    cv::findContours()
    时,我得到了更合理的结果

    如果轮廓中包含更多的点,我们可以得到更好的椭圆近似值,这是有道理的,但我仍然不确定为什么结果与简单的链近似值相差如此之远。有关差异的解释,请参阅

    使用
    cv::CHAIN_APPROX_SIMPLE
    时,三角形的相对水平边几乎完全从轮廓上移除


    至于您的最佳拟合分类,正如其他人所指出的,仅使用面积将得到您观察到的结果,因为根本不考虑定位。

    请记住,
    fitEllipse
    不是边界椭圆的计算,而是假设点位于椭圆上的最小二乘优化

    我无法告诉你为什么它在最后一行的3个三角形上失败得如此糟糕,但在上面一行的三角形上“起作用”,但我看到的一件事是,最后一行的所有3个三角形都被拟合到一个角度为0的旋转体上。可能最小二乘拟合在那里失败了

    但我不知道openCV实现中是否存在缺陷,或者该算法是否无法处理这些情况。该算法用于:

    我的建议是,如果你非常确定这些点真的属于一个椭圆,那么只使用
    fitEllipse
    。如果你有随机数据点,你不会假设从
    fitLine
    得到合理的结果。你可能想看的其他函数有:
    minarealect
    mineConclosingCircle

    如果您使用
    rotatedlect temp=minarealect(Mat(轮廓[i]);
    而不是
    fitEllipse
    您将获得如下图像:


    也许你甚至可以使用这两种方法,拒绝所有在两个版本中都失败的椭圆,接受所有在两个版本中都被接受的椭圆,但对那些不同的椭圆进行进一步的调查?

    你是否尝试过绘制/显示正在计算的轮廓?也许问题在于这一点,而不是fitEllipse。而且,因为你看起来为了有一个二进制输入,你可以考虑使用形态学运算来寻找等值线,即原始腐蚀(原始)。然后每个连接的分量应该是一个轮廓。另一个建议可能是使用一个最大树,这就消除了主(2)线上的阈值的需要。。您需要定义正确的属性过滤器。参考:或重叠区域和质心的组合。所有建议都很好,但它们没有解决主要问题:椭圆拟合怎么会完全错误?为什么OpenCV返回的椭圆显然不是此轮廓的最小椭圆?如果您运行此应用程序,你必须按键盘上的一个键来执行循环的下一次迭代。这允许用户看到被检测和绘制的每个形状。