Ask Your Question

Revision history [back]

click to hide/show revision 1
initial version

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(int cpt = 0; cpt < 4; cpt++) {
        cv::line(imgWithEllipse3, vtx[cpt], vtx[(cpt+1) % 4], cv::Scalar(0, 0, 255));
      }
      cv::drawContours(imgWithEllipse3, std::vector<std::vector<cv::Point> > (1, contours[i]), -1, cv::Scalar(0, 255, 0));

      cv::imshow("imgWithEllipse", imgWithEllipse);
      cv::imshow("imgWithEllipse2", imgWithEllipse2);
      cv::imshow("imgWithEllipse3", imgWithEllipse3);
      cv::waitKey(0);
    }
  }

  cv::imwrite("imgWithEllipse.png", imgWithEllipse);
  cv::imwrite("imgWithEllipse2.png", imgWithEllipse2);
  cv::imwrite("imgWithEllipse3.png", imgWithEllipse3);
}

int main() {
  cv::Mat input;
  cv::VideoCapture capture("http://answers.opencv.org/upfiles/1430382437634716.bmp");
  if(!capture.isOpened()) {
    return -1;
  }
  capture >> input;
  if(input.empty()) {
    return -1;
  }

  cv::Mat gray;
  cv::cvtColor(input, gray, CV_BGR2GRAY);

  getFittedEllipseContour(gray);

  return 0;
}