pythonopencvdrawingellipse

how to force cv2.ellipse to draw a clockwise circular arc?


cv2.ellipse seems to have a mind of its own about which way it interprets start-and end angles. I want to draw a circular arc from 300° to 130° in the clockwise direction, i.e. like this:

Image

with this code:

import numpy as np
import cv2

center = (200, 200) # x,y
axes = (100, 100) # first, second
angle = 0. # clockwise, first axis, starts horizontal
for i in range( 330,130,-1):
  image = np.zeros((400, 400, 3)) # creates a black image
  image = cv2.ellipse(image, center, axes, angle, 0., 360, (0,0,255))
  image = cv2.ellipse(image, center, axes, angle, 330, i, (0,255,0))
  cv2.imshow("image", image)
  cv2.waitKey(5)

cv2.waitKey(0)
cv2.destroyAllWindows()

I got this instead, with the green segment being drawn counter-clockwise from 330 to 130 instead of clockwise.

Image

The code generates the red circle first and then overlays the green arc, but it demonstrates the problem.


Solution

  • Note: You should have probably mentioned that your code comes from [SO]: Understanding the ellipse parameters in Open CV using Python (@api55's answer).

    Given a circle, and 2 points on it A and B, where each point name is also the degrees (A, B ∈ [0, 360)), there are 2 ways to move from A to B (on the circle), as there are 2 arches. Choosing which arch to take has the following implications:

    1. How much to move (distance):

      • Arch 1 (let this be the shorter): D1 = abs(B - A) - as distance (even in angles) is always positive

      • Arch 2: D2 = 360 - D1 - the remainder of the circle

    2. Direction to move to (CCW, CW). This translates to the angle increment being added / subtracted to / from the initial value (A).
      Here, I must also mention that moving from B to A is like moving from A to B but in the opposite direction

    The 2 things I mentioned above (combined with the fact that CCW is the default direction), translate into the 2 if clauses in the modified version of your code (below). Maybe the conditions are not very obvious (that's why I tried expanding them in the commented lines - code has same effect), but I couldn't find a nicer version (yet).
    Anyway, it should work for any angles and directions.

    code00.py:

    #!/usr/bin/env python
    
    import sys
    
    import numpy as np
    import cv2
    
    
    def main(*argv):
        center = (200, 200)
        axes = (100, 100)
        angle = 0.
        start = 330
        end = 130
        cw = 0 #len(sys.argv) > 1  # @TODO cfati: ClockWise?
        if start < end:
            steps = 360 + start - end
        else:
            steps = start - end
        if cw:
            steps = 360 - steps
            cw_factor = 1
        else:
            cw_factor = -1
        """
        # The above if conditions, expanded (same effect)
        if start < end:
            if cw:
                steps = end - start
                cw_factor = 1
            else:
                steps = 360 + start - end
                cw_factor = -1
        else:
            if cw:
                steps = 360 - start + end
                cw_factor = 1
            else:
                steps = start - end
                cw_factor = -1
        """
        step = 1
        base_color = (0, 0, 255)
        active_color = (0, 255, 0)
        image = np.zeros((400, 400, 3))
        for i in range(1, steps, step):
            cv2.ellipse(image, center, axes, angle, 0, 360, base_color)
            cv2.ellipse(image, center, axes, angle, start, start + cw_factor * i, active_color)
            cv2.imshow("image", image)
            cv2.waitKey(1)
    
        print("Close drawing window to exit...")
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    
    
    if __name__ == "__main__":
        print(
            "Python {:s} {:03d}bit on {:s}\n".format(
                " ".join(elem.strip() for elem in sys.version.split("\n")),
                64 if sys.maxsize > 0x100000000 else 32,
                sys.platform,
            )
        )
        rc = main(*sys.argv[1:])
        print("\nDone.\n")
        sys.exit(rc)