OpenCV fitEllipse() иногда возвращает полностью неправильные эллипсы

Моя цель - распознать все фигуры, присутствующие в изображении. Идея такова:

  • Контуры извлечения
  • Установите каждый контур с различными формами
  • Правильная форма должна быть той, у которой есть область, наиболее близкая к область контура.

Пример изображения: 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;
}

Ответы

Ответ 1

Имейте в виду, что fitEllipse - это не вычисление ограничивающей эллипса, а наименьшая квадратная оптимизация, предполагающая, что точки лежат на эллипсе.

Я не могу сказать вам, почему это так неудачно на трех треугольниках в последней строке так плохо, но "работает" над треугольником на одной строке выше, но одна вещь, которую я видел, - это то, что все три треугольника в последней строке были установлены на вращающемся реке с angle 0. Вероятно, наименьшая квадратная подгонка просто не удалась.

Но я не знаю, есть ли ошибка в реализации openCV, или если алгоритм не может обрабатывать эти случаи. Этот алгоритм используется: http://www.bmva.org/bmvc/1995/bmvc-95-050.pdf

Мой совет: использовать fitEllipse только в том случае, если вы уверены, что точки действительно принадлежат эллипсу. Вы даже не предполагаете получить разумные результаты от fitLine, если у вас есть случайные точки данных. Другие функции, которые вы можете посмотреть, следующие: minAreaRect и minEnclosingCircle

если вы используете RotatedRect temp = minAreaRect(Mat(contours[i])); вместо fitEllipse, вы получите следующее изображение:

enter image description here

возможно, вы даже можете использовать оба метода и отказаться от всех эллипсов, которые не работают в обеих версиях, и принять все, что принято в обеих версиях, но в дальнейшем исследовать те, которые отличаются?!?

Ответ 2

Если у вас возникли проблемы с cv::fitEllipse(), этот пост, обсудите несколько методов, чтобы свести к минимуму те ошибки, которые происходят, когда 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 a triangle;
        //  - Area >= 1300 and < 1700 means it 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;
}

Вы можете вызвать другие функции, чтобы нарисовать более точные контуры (границы) фигур.

Ответ 3

Может быть, лучше получить пиксельно-пиксельное сравнение, т.е. какой процент является перекрытием между контуром и "установленным" эллипсом.

Другая, более простая идея - также сравнить центроиды контура и его эллипса.

Ответ 4

Изменение cv::CHAIN_APPROX_SIMPLE на cv::CHAIN_APPROX_NONE в вызове cv::findContours() дает мне гораздо более разумные результаты.

Имеет смысл, что мы получим лучшее эллипсовое приближение с большим количеством точек, включенных в контур, но я все еще не уверен, почему результаты настолько просты с простым цепным приближением. См. opencv docs для объяснения разницы

Похоже, что при использовании cv::CHAIN_APPROX_SIMPLE относительно горизонтальные края треугольников почти полностью удаляются из контура.

enter image description here

Что касается вашей классификации наилучшего соответствия, как указывали другие, использование только области даст вам результаты, которые вы наблюдаете, поскольку позиционирование не учитывается вообще.