Ask Your Question
0

Problem when calculating the fitted ellipse

asked 2015-04-30 03:29:42 -0500

xyansdu gravatar image

updated 2015-05-01 07:40:07 -0500

Eduardo gravatar image

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[4];
        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:

image description

Result image:

image description

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

edit retag flag offensive close merge delete

Comments

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?

thdrksdfthmn gravatar imagethdrksdfthmn ( 2015-04-30 04:07:10 -0500 )edit

Absolutely, would you please explain the reason?

xyansdu gravatar imagexyansdu ( 2015-04-30 06:23:28 -0500 )edit

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

thdrksdfthmn gravatar imagethdrksdfthmn ( 2015-04-30 07:05:46 -0500 )edit

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.

xyansdu gravatar imagexyansdu ( 2015-04-30 21:17:15 -0500 )edit

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?

Guanta gravatar imageGuanta ( 2015-05-01 17:32:52 -0500 )edit

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.

Eduardo gravatar imageEduardo ( 2015-05-01 17:50:41 -0500 )edit

1 answer

Sort by ยป oldest newest most voted
1

answered 2015-05-02 00:25:15 -0500

Eduardo gravatar image

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 fitEllipse

Result with minAreaRect:

Result with minAreaRect

Result with custom method:

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[5];
    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[4];
      minRect.points(vtx);
      for ...
(more)
edit flag offensive delete link more
Login/Signup to Answer

Question Tools

1 follower

Stats

Asked: 2015-04-30 03:29:42 -0500

Seen: 3,200 times

Last updated: May 02 '15