OpenCV的fitEllipse()有时会返回完全错误的省略号

时间:2013-06-18 03:10:18

标签: c++ opencv image-processing

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

  1. 提取轮廓
  2. 使每个轮廓适合不同的形状
  3. 正确的形状应该是最接近的区域 轮廓的区域。
  4. 示例图片: enter image description here

    我使用fitEllipse()为轮廓找到最合适的椭圆,但结果有点乱: enter image description here

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

    正如您所看到的,在第一行中限定三角形的椭圆看起来非常适合最佳拟合。第三行中三角形的边界椭圆似乎不是最合适的,但仍然可以作为拒绝不正确椭圆的标准。

    但是我无法理解为什么剩下的三角形在其轮廓之外完全具有边界椭圆。 最坏的情况是最后一行中的第三个三角形:椭圆是完全错误的,但碰巧有一个区域靠近轮廓的区域,因此三角形被错误地识别为椭圆。

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

    #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;
    }
    

4 个答案:

答案 0 :(得分:12)

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

我无法告诉你为什么它在最后一行的3个三角形上失败但是在上面一行的三角形上“起作用”,但我看到的一件事是,最后一行中的所有3个三角形适用于angle 0的rotateRect。可能最小的方形配件在那里失败了。

但我不知道openCV实现中是否存在错误,或者算法无法处理这些情况。使用此算法:http://www.bmva.org/bmvc/1995/bmvc-95-050.pdf

我的建议是,如果您确定这些点确实属于椭圆,则仅使用fitEllipse。如果你有随机数据点,你不会假设从fitLine获得合理的结果。您可能希望查看的其他功能包括:minAreaRectminEnclosingCircle

如果您使用RotatedRect temp = minAreaRect(Mat(contours[i]));代替fitEllipse,您将获得如下图片:

enter image description here

也许你甚至可以使用这两种方法并拒绝在两个版本中都失败的所有省略号并接受两个版本中都接受的所有省略号,但是在那些不同的版本中进一步调查?!

答案 1 :(得分:5)

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

现在,并不完全清楚项目的限制是什么,但解决这个问题的另一种方法是根据轮廓区域分离这些形状

enter image description here

这种方法在这种特殊情况下非常简单而有效:圆形区域在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;
}

您可以调用其他函数来绘制更精确的形状轮廓(边框)。

答案 2 :(得分:3)

获得逐像素比较可能是一个更好的主意,即轮廓与“拟合”椭圆之间的重叠百分比。

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

答案 3 :(得分:3)

cv::CHAIN_APPROX_SIMPLE更改为cv::CHAIN_APPROX_NONEcv::findContours()的电话中给了我更多合理的结果。

有意义的是,我们会得到一个更好的椭圆近似,轮廓中包含更多的点,但我仍然不确定为什么结果如此偏离简单的链近似。有关差异的解释,请参阅opencv docs

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

enter image description here

至于你对最佳拟合的分类,正如其他人所指出的那样,只使用该区域会给你所观察到的结果,因为根本没有考虑到定位。