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]()
Что касается вашей классификации наилучшего соответствия, как указывали другие, использование только области даст вам результаты, которые вы наблюдаете, поскольку позиционирование не учитывается вообще.