Jordan
Jordan

Reputation: 4472

OpenCV: Fitting a single circle to an image (in Python)

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?

Upvotes: 6

Views: 10684

Answers (2)

Ha Bom
Ha Bom

Reputation: 2917

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

Upvotes: 6

Vu Gia Truong
Vu Gia Truong

Reputation: 1032

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

Upvotes: 1

Related Questions