4
$\begingroup$

Main task: I fit the ellipse using the fitEllipse() method and then I'd like to count the rotation angle between the horizontal axis and the major axis of the generated ellipse. I'm going to do this using the 3rd returned argument from the fitEllipse() method - $\theta$ (rotation angle).

Main issue: I can't find exact information about which axes this angle is located between.

Other: If I'm right the length of minor axis and the length of major axis in ellipse it's the same lengths as two sides in a rotated rectangle.

Sources:

  1. From documentation here (section nb. 9) it seems that this angle is between horizontal axis and the first side how it's written in CvBox2D section. So, it means that the angle can be between the horizontal axis and minor or major axis. But:
  2. In this article (section 3) the first example is good but in the 2nd example the angle should be between horizontal axis and height instead of width (referring to my aforementioned first point).
  3. In this article it shows that the angle is between vertical axis and one side of rectangle.

So, where is the rotated angle located in fitEllipse() method?

Any hints how it works are welcome.

$\endgroup$

1 Answer 1

2
$\begingroup$

I've implemented the following simple code:

import cv2
import numpy as np

nr_im = 9876
font = cv2.FONT_HERSHEY_SIMPLEX 
fontScale = 1
colorText = (0, 0, 255)
thickness = 2

img = cv2.imread('testing/' + str(nr_im) + '.jpg')
original = img.copy()
blured_img = cv2.GaussianBlur(img,(17,17),5)

image = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
lower = np.array([0, 0, 140], dtype="uint8")
upper = np.array([0, 0, 255], dtype="uint8")
mask = cv2.inRange(image, lower, upper)

# Morphological Closing: Get rid of the noise inside the object
mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (25, 25)))

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

print(len(cnts))

cntsElps = []
for num_cnt, cnt in enumerate(cnts): 
    genEllipse= cv2.fitEllipse(cnt)
    cntsElps.append(genEllipse)
    cv2.ellipse(original,genEllipse,(0,255,0),2) 
    cv2.putText(original, str(num_cnt+1), (int(genEllipse[0][0]),int(genEllipse[0][1])), font, fontScale, colorText, thickness, cv2.LINE_AA) 
    print("Ellipse nb: " + str(num_cnt+1) + " has angle: " + str(genEllipse[2]) + "\n")
    

cv2.imwrite('testing/' + str(nr_im) + '_' + 'trash2' + '.png', original)

And I used this image as example: enter image description here

I've got the following image result: enter image description here

And the rotation angle for each ellipse was:

  1. Ellipse nb: 1 has angle: 55.63788986206055
  2. Ellipse nb: 2 has angle: 108.58539581298828
  3. Ellipse nb: 3 has angle: 170.23861694335938
  4. Ellipse nb: 4 has angle: 73.59089660644531

So, my conclusion is that an angle between vertical axis and major side of rectangle(=major ellipse axis) is the rotation angle in fitEllipse() method.

Addendum

If you look at this question from the point of view of how opencv-python defines axes (positive x-axis to the right, positive y-axis downwards), the angle is defined between the horizontal axis and the minor ellipse diameter. To demonstrate this, different angles are plotted on a white canvas

import math

import cv2
import numpy as np

# create white canvas
img = np.zeros([512, 512, 3], dtype=np.uint8)
img.fill(255)

xc = 256
yc = 256

angles = list(range(0, 360, 30))
# radii = np.linspace(30, 200, len(angles))
radii = [175] * len(angles)

for idx, (angle, radius) in enumerate(zip(angles, radii)):
    xtop = xc + math.cos(math.radians(angle)) * radius
    ytop = yc + math.sin(math.radians(angle)) * radius

    cv2.line(img, (int(xtop), int(ytop)), (int(xc), int(yc)), (0, 0, 255), 1)

    # Put the contour index in the ellipse
    cv2.putText(img, f'{round(math.radians(angle) / math.pi, 2)} pi', (int(xtop), int(ytop)),
                cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255),
                2, cv2.LINE_AA)

cv2.imwrite('opencv_angles.jpg', img)
cv2.imshow('Definition of angle', img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Example of various angles in opencv

The text in the image shows the angles for that line in radians


The angle can also be plotted for the fitted ellipses, using the angle returned by fitEllipse.

(xc, yc), (width, height), angle = genEllipse
rminor = min(width, height) / 2
xtop = xc + math.cos(math.radians(angle)) * rminor
ytop = yc + math.sin(math.radians(angle)) * rminor
cv2.line(result, (int(xtop), int(ytop)), (int(xc), int(yc)), (0, 0, 255), 3)

Fitted ellipses and minor diameter

Here you can see that the angle is between the horizontal axis and minor diameter. Remember the rotation angle for each ellipse:

  1. Ellipse nb: 1 has angle: 55.63788986206055
  2. Ellipse nb: 2 has angle: 108.58539581298828
  3. Ellipse nb: 3 has angle: 170.23861694335938
  4. Ellipse nb: 4 has angle: 73.59089660644531

Conclusion

Angles in opencv, where positive x is to the right and positive y is downwards in images, means that the rotation angle for ellipses is best seen as between positive x-axis and minor ellipse diameter (downwards towards the positive y-axis).

However, if you flip the axes (in your mind), so that positive y-axis is upwards and positive x-axis is to the right in the image, then you can also interpret the ellipse rotation angle as between the positive y-axis and major ellipse diameter (to the right towards the positive x-axis)

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.