5 Best Ways to Fit an Ellipse to an Object in an Image Using OpenCV Python

πŸ’‘ Problem Formulation: Many computer vision tasks involve identifying and tracking objects within images. Sometimes, these objects are best represented geometricallyβ€”a common case being elliptical shapes for objects such as eyes, wheels, or planets. For example, given an image of a car, we may wish to fit an ellipse around one of the wheels to analyze its dimensions or position. Our input is the image, and the desired output is the parameters of the ellipse that best fits the selected wheel.

Method 1: Utilize Contour Detection and FitEllipse Function

Detecting contours and then fitting an ellipse to those contours is a primary method in OpenCV. The cv2.findContours() function helps in retrieving the contours, which can then be passed to cv2.fitEllipse() to estimate the ellipse that best fits the contour. The function returns the rotated rectangle in which the ellipse is inscribed.

Here’s an example:

import cv2
import numpy as np

image = cv2.imread('path_to_image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, 0)
contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
ellipse = cv2.fitEllipse(cnt)
image = cv2.ellipse(image, ellipse, (0,255,0), 2)
cv2.imshow('Fitted ellipse', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output: A window displaying the image with a green ellipse fitted to the largest contour.

The code snippet above reads an image, converts it to grayscale, and applies a binary threshold. Once we have a binary image, we find the contours and fit an ellipse to the first detected contour. The fitted ellipse is then drawn on the original image in green, and the result is displayed.

Method 2: Fit Ellipse to Binary Images with Noise Filtering

This method enhances the first by adding an extra step of noise filtering, using functions such as cv2.medianBlur() or cv2.GaussianBlur() to remove unwanted noise from the image before thresholding and contour detection. Filtering helps in improving the accuracy of the contour detection.

Here’s an example:

import cv2
import numpy as np

image = cv2.imread('path_to_image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5, 5), 0)
_, thresh = cv2.threshold(blur, 127, 255, 0)
contours, _ = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
cnt = contours[0]
ellipse = cv2.fitEllipse(cnt)
image = cv2.ellipse(image, ellipse, (0,255,0), 2)
cv2.imshow('Fitted ellipse with noise filtering', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output: A window displaying the image with a green ellipse fitted to the largest contour with reduced noise.

This code snippet expands on the first by introducing a blurring process before detecting contours. This helps to reduce the effect of small artifacts or noise that could interfere with the accuracy of contour detection, leading to a more precise ellipse fitting.

Method 3: Probabilistic Hough Transform

When objects are linear or circular with imperfections, the Probabilistic Hough Transform can be used for detection before fitting an ellipse. This transform, invoked using cv2.HoughCircles(), attempts to find circles in the image, which are then used as a base for fitting an ellipse.

Here’s an example:

import cv2
import numpy as np

image = cv2.imread('path_to_image.jpg')
output = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
circles = cv2.HoughCircles(gray, cv2.HOUGH_GRADIENT, 1, 20,
                           param1=50, param2=30, minRadius=0, maxRadius=0)
if circles is not None:
    circles = np.round(circles[0, :]).astype("int")
    for (x, y, r) in circles:
        cv2.circle(output, (x, y), r, (0, 255, 0), 4)
cv2.imshow('Hough Circles', output)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output: A window displaying the image with green circles drawn over detected circular objects.

Though not directly fitting an ellipse, this code demonstrates how the HoughCircles function can be utilized to detect circular areas that may be candidates for ellipse fitting, especially in cases where objects are not completely elliptical but may be partially occluded or damaged.

Method 4: Using Machine Learning for Shape Detection

Advanced methods for fitting an ellipse might involve machine learning algorithms. A pre-trained model could be used to detect the elliptical object, and the bounding box coordinates obtained could be translated into an ellipse fitting problem by using the same cv2.fitEllipse() method.

Here’s an example:

import cv2
import numpy as np
# Suppose we have pre-trained ML model that outputs bounding box coords
# bbox_coords = machine_learning_model.predict(image)
# For illustration purposes, let's create dummy coords
bbox_coords = [100, 50, 200, 150] # x, y, width, height

image = cv2.imread('path_to_image.jpg')
mask = np.zeros(image.shape[:2], dtype="uint8")
cv2.rectangle(mask, (bbox_coords[0], bbox_coords[1]), 
              (bbox_coords[0]+bbox_coords[2], 
               bbox_coords[1]+bbox_coords[3]), 255, -1)
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
ellipse = cv2.fitEllipse(contours[0])
cv2.ellipse(image, ellipse, (0, 255, 0), 2)

cv2.imshow('Ellipse Fitting with ML', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output: A window displaying the image with a green ellipse fitted to the bounding box generated by a hypothetical machine learning model.

Although this code uses a dummy bounding box for demonstration, in a real-world scenario, the machine learning model would produce a bounding box around the object of interest. Then, a mask is created over this area, from which an ellipse is fitted.

Bonus One-Liner Method 5: Direct Ellipse Fitting from Edges

For a quicker, albeit less precise approach, one can fit an ellipse directly on the edges of the object by using cv2.Canny() to detect edges followed by cv2.fitEllipse().

Here’s an example:

import cv2
import numpy as np

image = cv2.imread('path_to_image.jpg')
edges = cv2.Canny(image, 100, 200)
contours, _ = cv2.findContours(edges, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
ellipse = cv2.fitEllipse(contours[0])
image = cv2.ellipse(image, ellipse, (0, 255, 0), 2)

cv2.imshow('Direct Ellipse Fitting from Edges', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Output: A window displaying the image with a green ellipse fitted directly to the detected edges.

This concise code directly calculates edges using the Canny edge detector, followed by ellipse fitting. It’s a quick method but may not be as robust or precise as the methods mentioned above that involve preprocessing steps.

Summary/Discussion

  • Method 1: Contour Detection and FitEllipse. Strengths: classic, widely applicable. Weaknesses: requires clear object boundaries.
  • Method 2: Binary Images with Noise Filtering. Strengths: improved accuracy with pre-processing. Weaknesses: may lose some edge information with excessive blurring.
  • Method 3: Probabilistic Hough Transform. Strengths: useful for imperfect circular shapes. Weaknesses: assumes the object is nearly circular, may not detect true ellipses.
  • Method 4: Machine Learning for Shape Detection. Strengths: powerful and adaptable. Weaknesses: requires training data and computational resources.
  • Method 5: Direct Ellipse Fitting from Edges. Strengths: fast and one-liner. Weaknesses: lacks precision and assumes prominent edges.