# Ellipses detecting at the image

I have the following image:

I need to get two ellipses which will 'describe' my 'ring'.

I have the following code at the moment:

import cv2
import numpy as np
import imutils

# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

_, gray = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)

# Downsize image (by factor 4) to speed up morphological operations
gray = cv2.resize(gray, dsize=(0, 0), fx=0.25, fy=0.25)
cv2.imshow("Gray resized", gray)
cv2.imwrite('Gray_resized.png', gray)

# Morphological opening: Get rid of the stuff at the top of the ellipse
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
cv2.imshow("Gray removed noise", gray)
cv2.imwrite('Gray_removed_noise.png', gray)

# Resize image to original size
gray = cv2.resize(gray, dsize=(image.shape[1], image.shape[0]))

# Find contours
cnts, _ = cv2.findContours(gray, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)

# Draw found contours in input image
image = cv2.drawContours(image, cnts, -1, (0, 0, 255), 2)

if len(cnts) != 0:
for cont in cnts:
if cont.size < 10 or cv2.contourArea(cont) < 100:
continue
elps = cv2.fitEllipse(cont)
cv2.ellipse(image,elps,(0,255,0),2)

# Downsize image
out_image = cv2.resize(image, dsize=(0, 0), fx=0.25, fy=0.25)
cv2.imshow("Output image", out_image)
cv2.imwrite('Output_image.png', out_image)


I've got images like:

In this way it detected one ellipse well enough but it hasn't detected the second one.

As i read it'd be more logical to use 'cv2.THRESH_BINARY_INV' in threshold but in this way it doesn't detect ellipse in the center:

Also if i try to lower the threshold my ring starts to fade:

The main questions: How to get rid of this noise at the top of my ring for detecting contours? And how to detect these ellipses?

I've looked at these answers: 1st 2nd 3rd but i haven't managed to achive my goal.

P.S. I can't use Hough-Circle method here. Because i have few similar images and radius for 'x' and 'y' axis may different and i need this precision(it means i need only ellipses and their radiuses for two axises).

I would recommend to use adaptive threshold rather than global threshold. Adaptive threshold deals better with uneven illumination. I have modified your code accordingly. You may want to experiment further with the value of block size and C value.

I have also used RETR_CCOMP and only show contours which either have a parent (index 3 != -1) or don't have any children (index 2 == -1).

I don't guarantee that this will work for other images but it may be a good starting point for your further experimentation.

import cv2
import numpy as np
import imutils

# Convert to grayscale
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
h, w = gray.shape
s = int(w / 8)
gray = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, s, 7.0)

# Downsize image (by factor 4) to speed up morphological operations
gray = cv2.resize(gray, dsize=(0, 0), fx=0.25, fy=0.25)
cv2.imshow("Gray resized", gray)
cv2.imwrite('Gray_resized.png', gray)

# Morphological opening: Get rid of the stuff at the top of the ellipse
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)))
cv2.imshow("Gray removed noise", gray)
cv2.imwrite('Gray_removed_noise.png', gray)

# Resize image to original size
gray = cv2.resize(gray, dsize=(image.shape[1], image.shape[0]))

# Find contours
cnts, hier = cv2.findContours(gray, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

# Draw found contours in input image
image = cv2.drawContours(image, cnts, -1, (0, 0, 255), 2)

for i, cont in enumerate(cnts):
h = hier[0, i, :]
print(h)
if h[3] != -1:
elps = cv2.fitEllipse(cnts[i])
elif h[2] == -1:
elps = cv2.fitEllipse(cnts[i])
cv2.ellipse(image, elps, (0, 255, 0), 2)

# Downsize image
out_image = cv2.resize(image, dsize=(0, 0), fx=0.25, fy=0.25)
cv2.imshow("Output image", out_image)
cv2.imwrite('Output_image.png', out_image)
cv2.waitKey(0)
cv2.destroyAllWindows()