4

I have an image like this:

enter image description here

I need to fit an ellipse to the dark area (note: must be an ellipse, not a circle). What is the best way to do this in OpenCV? My first step so far has been to apply an adaptive (Otsu) threshold to it, which results in:

enter image description here

But I'm not sure where to go from there. I'm writing the app in Python, but it's more the algorithm design I'm looking for.

EDIT based on response/comment:

OK, so I have already tried the morphology. Based on the OpenCV documentation, I did a 3-iteration "close" operation on it (dilation, then erosion) to remove the small particles, which results in:

enter image description here

Then, to expand it back out to closerto the original shape, I did a 3-iteration "open" operation (erosion, then dilation), which results in:

enter image description here

From here, I did Canny edge detection, which resulted in:

enter image description here

Now, I used findContours on it, but ran into an issue. It found dozens of contours along the edge, each one a short segment along the circumference. Which means, even if I take the maximum size contour, it might only represent 10% of the circumference, which is insufficient to accurately fit an ellipse. This is why the other questions that @Demi-Lune suggested didn't work for me; they all have very clean, sharp edges and findContours finds a nice single contour that covers the entire perimiter of each shape, but that doesn't happen for my messier image. So, what's the best way to fit the ellipse from here?

Jordan
  • 3,998
  • 9
  • 45
  • 81
  • Did you try https://stackoverflow.com/questions/42206042/ellipse-detection-in-opencv-python ? or https://stackoverflow.com/questions/35121045/find-cost-of-ellipse-in-opencv ? – Demi-Lune Apr 10 '19 at 22:12
  • @Demi-Lune see my edit in the question. Basically, those questions have nice clean shapes with clearly defined edges, and `findContours` finds nice single contours for those; that doesn't work for my messier images. – Jordan Apr 10 '19 at 23:02
  • Find the minarearect of the black points.. It represents your ellipse – Miki Apr 10 '19 at 23:31
  • That would only work if there were no extraneous dots outside the circle, right? That is the case for this particular image, but not universal (some images I process might have the odd dot here and there outside of the circle, even after morphology). Also, if one side of the circle was "flattened" (so it looked more like a 'D'), it wouldn't fit the best ellipse, it would fit one that was smaller. – Jordan Apr 10 '19 at 23:33
  • #1 correct, #2 the "best" is according to a metric... "least square error" and "has more overlap with the boundary points" produce different results – Miki Apr 10 '19 at 23:39

2 Answers2

4

If the object has circle shape, then use cv2.minEnclosingCircle is good. Or else, you can use cv2.fitEllipse to find the most fitted ellipse around the object. Remember to do find contour with white object in black background.

import cv2
import numpy as np

img = cv2.imread("1.jpg")

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
_,thresh = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
thresh = cv2.bitwise_not(thresh)

element = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(5, 5))

morph_img = thresh.copy()
cv2.morphologyEx(src=thresh, op=cv2.MORPH_CLOSE, kernel=element, dst=morph_img)

contours,_ = cv2.findContours(morph_img,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

areas = [cv2.contourArea(c) for c in contours]
sorted_areas = np.sort(areas)

#bounding box (red)
cnt=contours[areas.index(sorted_areas[-1])] #the biggest contour
r = cv2.boundingRect(cnt)
cv2.rectangle(img,(r[0],r[1]),(r[0]+r[2],r[1]+r[3]),(0,0,255),2)

#min circle (green)
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
cv2.circle(img,center,radius,(0,255,0),2)

#fit ellipse (blue)
ellipse = cv2.fitEllipse(cnt)
cv2.ellipse(img,ellipse,(255,0,0),2)


cv2.imshow("morph_img",morph_img)
cv2.imshow("img", img)
cv2.waitKey()

enter image description here enter image description here

Ha Bom
  • 2,787
  • 3
  • 15
  • 29
  • Thank you, this worked fairly well. Say I wanted to "Average" the results from the `minBoundingRectangle` (specifically, the ellipse that can be inscribed within it) and the `fitEllipse` functions. How would I go about doing so? – Jordan Apr 11 '19 at 17:45
1

why don't you do thing like "close" then "open" to clear all the mess.

Raw image :

Raw image

Otsu :

Otsu

Close + open ; both with 7x7 kernel ; The binary image is beauty and clean now.

Close + Open

Only one contour is detect :

One contour

The ellipse is as : (please not that your image is circle so ellipse should be in circle shape)

Ellipse

Vu Gia Truong
  • 1,022
  • 6
  • 14
  • Thanks @Vu. Now, let's say the binary thresholded, morphed image is solid, but has a "bulge" out the side (a pointy lump sticking out). If I use `fitEllipse` on this, the bulge will affect the fit and make it more oblong, but I need a solution that maximizes the number of points fit, not the least-square. That is, it should match the contour of the shape for as many points possible. – Jordan Apr 29 '19 at 02:17
  • Don't understand what're you talking about. You should add some picture to clarify your problems – Vu Gia Truong May 01 '19 at 10:35