16

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

  1. 提取轮廓
  2. 用不同的形状拟合每个轮廓
  3. 正确的形状应该是最接近轮廓区域的形状。

示例图片: 在此处输入图像描述

我用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;
}
4

4 回答 4

15

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

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

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

我的建议是,仅fitEllipse在您非常确定这些点确实属于椭圆时才使用。fitLine如果您有随机数据点,您也不会假设得到合理的结果。您可能想要查看的其他功能是:minAreaRectminEnclosingCircle

如果你使用RotatedRect temp = minAreaRect(Mat(contours[i]));而不是fitEllipse你会得到这样的图像:

在此处输入图像描述

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

于 2014-11-12T12:30:59.037 回答
5

如果您在使用cv::fitEllipse(). 结果并不完美,可能会出现问题中提到的问题。cv::RotatedRectcv::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;
}

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

于 2014-11-11T23:11:48.607 回答
5

在调用中更改cv::CHAIN_APPROX_SIMPLE为给我更合理的结果。cv::CHAIN_APPROX_NONEcv::findContours()

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

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

在此处输入图像描述

至于您对最佳拟合的分类,正如其他人所指出的那样,仅使用该区域将为您提供您观察到的结果,因为根本不考虑定位。

于 2014-11-12T01:55:30.780 回答
4

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

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

于 2013-06-18T17:48:49.903 回答