# Problem when calculating the fitted ellipse

I need to extract contour of shapes in images, and get the fitted ellipse. However, when I use the following code, the result is not very satisfying.

  #include "stdafx.h"
#include <iostream>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <fstream>

using namespace std;
using namespace cv;

extern void Threshold(Mat gray,  Mat& binary);
extern void Not(Mat& im);

// Calculate the contour of connected region
// erease the image that is too large or too small
void getSizeContours(vector<vector<Point>> &contours)
{
int cmin = 10;   // min contour length
int cmax = 10000;   // max contour length
vector<vector<Point>>::const_iterator itc = contours.begin();
while(itc != contours.end())
{
if((itc->size()) < cmin || (itc->size()) > cmax)
{
itc = contours.erase(itc);
}
else ++ itc;
}
}

// Calculate min convex oval
void MinConvexOval(Mat gray, vector<vector<Point>> &contours, vector<Mat> oval_result)
{
Mat oval(gray.rows*3,gray.cols*3,CV_8UC3);
vector<vector<Point>>::const_iterator itc=contours.begin();
while(itc != contours.end())
{
double perimeter=arcLength(*itc,true);
double area= contourArea(*itc,false);
Rect rect=boundingRect(*itc);
CvBox2D box=minAreaRect(*itc);

Point pt1, pt2;
pt1.x=rect.x;
pt1.y=rect.y;
pt2.x=rect.x+rect.width;
pt2.y=rect.y+rect.height;

RotatedRect box2=fitEllipse(*itc);

ellipse(oval, box2, Scalar(255,0,255), 1, 8);
Point2f vertices;
box2.points(vertices);
for (int i = 0; i < 4; i++)
line(oval, vertices[i], vertices[(i+1)%4], Scalar(0,255,0));
++itc;
}
drawContours(oval,contours,-1, Scalar(0,255,255), 1);

imshow("oval",oval);
waitKey(0);
}

void cvText(Mat img, const char* text, int x, int y)
{
CvFont font;

double hscale = 1.0;
double vscale = 1.0;
int linewidth = 2;
cvInitFont(&font,CV_FONT_HERSHEY_SIMPLEX |CV_FONT_ITALIC,hscale,vscale,0,linewidth);

Scalar textColor =Scalar(0,255,255);
Point textPos =Point(x, y);

putText(img, text, textPos, CV_FONT_HERSHEY_COMPLEX, 1,textColor);
}

void GetContour(Mat grayim)
{
Mat binaryim;
Threshold(grayim, binaryim);   // binaryzation

vector<vector<Point>> contours;
//CV_CHAIN_APPROX_NONE  Get all of the pixels of the contours

findContours(binaryim, contours, CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE, cvPoint(0,0));
getSizeContours(contours);

vector<Mat> oval_result;
MinConvexOval(grayim,contours,oval_result);

}


Testing image: Result image: The fitting result of the right-down shape is wrong, could anyone help me, please? Thanks a lot!

edit retag close merge delete

Sorry, the problem is with the fitting of the last contour (bottom right one)... it is strange... What is also strange is that the ellipses do not really encapsulate the whole contour... is it ok that?

I am not sure, can you post the code that is computing the contours? And by the way,what version do you use?

I posted all the codes, plz help me to check it, my opencv version is 2.4.11.

The code start from the GetContour() function.

I made some tests and it appears the right bottom banana like shape can't be fitted. Imho this issue has already been fixed in OpenCV 3.0, see https://github.com/Itseez/opencv/pull... . Maybe you can try it with OpenCV 3.0 again?

I will try to help too. "Why the ellipses do not really encapsulate the whole contour". I think that the reason is the function fits the point in a "least-squares sense" by minimizing the overall distance error between the real points and the best fitted ellipse.

Maybe it is a bug, I may be wrong but I think that the problem is due to the method used to fit the ellipse (Fitzgibbon95) with some specific set of points.

Another example of bad ellipse fitting here.

Sort by » oldest newest most voted

One possible solution to avoid cases where cv::fitEllipse seems to fail like for the bottom right shape or like here is to use minAreaRect instead of fitEllipse.

You won't have an ellipse that will enclose your contour either with minAreaRect (Finds a rotated rectangle of the minimum area enclosing the input 2D point set) or with fitEllipse (Calculates the ellipse that fits (in a least-squares sense) a set of 2D points best of all).

Finally, a custom method of mine that computes the fitted ellipse only on a subset of contour points and keep the fiited ellipse that overlaps the more with the contour:

Result with fitEllipse: Result with minAreaRect: Result with custom method: #include <iostream>
#include <opencv2/opencv.hpp>

cv::RotatedRect getBestFittedEllipse(const std::vector<cv::Point> &points) {
std::map<double, cv::RotatedRect> mapOfFittedEllipse;

srand(time(NULL));
for(int nbIterations = 0; nbIterations < 100; nbIterations++) {
int index;
bool match = false;
std::vector<cv::Point> pointsMSS;
for(int i = 0; i < 5; i++){
do {
match = false;
index[i] = rand() % points.size();
for(int j = 0; j < i; j++) {
if(index[i] == index[j]) {
match = true;
}
}

if(!match) {
pointsMSS.push_back(points[index[i]]);
}
} while(match);
}

cv::RotatedRect ellipse = cv::fitEllipse(pointsMSS);

std::vector<cv::Point2f> fitEllipseContours;
float u = ellipse.center.x;
float v = ellipse.center.y;
float a = ellipse.size.width / 2.0f;
float b = ellipse.size.height / 2.0f;
for(int i = 0; i < 100; i++) {
float t = 2.0 * CV_PI * i / 100.0;
float phi = ellipse.angle * CV_PI / 180.0;
float coordX = u + a*cos(t) * cos(phi) - b*sin(t)*sin(phi);
float coordY = v + a*cos(t) * sin(phi) + b*sin(t)*cos(phi);
fitEllipseContours.push_back(cv::Point2f(coordX, coordY));
}

cv::Rect bbContours = cv::boundingRect(points);
cv::Rect bbFitEllipse = cv::boundingRect(fitEllipseContours);
cv::Rect intersection = bbContours & bbFitEllipse;

if(intersection.width > 0 && intersection.height > 0) {
double overlapping = intersection.area() / (double) (bbFitEllipse.area() + bbContours.area() - intersection.area());
mapOfFittedEllipse[overlapping] = ellipse;
}
}

return mapOfFittedEllipse.rbegin()->second;
}

void getFittedEllipseContour(const cv::Mat &grayImg) {
cv::Mat binaryImg;
cv::threshold(grayImg, binaryImg, 100, 255, cv::THRESH_BINARY);
cv::imshow("binaryImg", binaryImg);

std::vector<std::vector<cv::Point> > contours;
findContours(binaryImg, contours, CV_RETR_TREE, CV_CHAIN_APPROX_NONE);

cv::Mat imgWithEllipse(cv::Size(binaryImg.cols, binaryImg.rows), CV_8UC3);
cv::Mat imgWithEllipse2(cv::Size(binaryImg.cols, binaryImg.rows), CV_8UC3);
cv::Mat imgWithEllipse3(cv::Size(binaryImg.cols, binaryImg.rows), CV_8UC3);
for(size_t i = 1; i < contours.size(); i++) {
std::vector<CvPoint> points;
if(contours[i].size() >= 5) {
cv::RotatedRect rotatedRect = cv::fitEllipse(contours[i]);
cv::ellipse(imgWithEllipse, rotatedRect, cv::Scalar(255, 0, 255));
cv::drawContours(imgWithEllipse, std::vector<std::vector<cv::Point> > (1, contours[i]), -1, cv::Scalar(0, 255, 0));

cv::RotatedRect rotatedRect2 = getBestFittedEllipse(contours[i]);
cv::ellipse(imgWithEllipse2, rotatedRect2, cv::Scalar(255, 0, 0));
cv::drawContours(imgWithEllipse2, std::vector<std::vector<cv::Point> > (1, contours[i]), -1, cv::Scalar(0, 255, 0));

cv::RotatedRect minRect = cv::minAreaRect(cv::Mat(contours[i]));
cv::ellipse(imgWithEllipse3, minRect, cv::Scalar(0, 0, 255));
cv::Point2f vtx;
minRect.points(vtx);
for ...
more

Official site

GitHub

Wiki

Documentation