π‘ Problem Formulation: Users often need to interact with images in a graphical user interface. A common task is to draw shapes, like circles, based on mouse events. For example, you might want to click on a point in an image, and a circle is drawn around that point. This article demonstrates how to draw circles using mouse events in OpenCV with Python.
Method 1: Simple Click-and-Draw
This method involves setting up a mouse callback to listen for clicks and draw a circle at the point where the mouse is clicked. It is an effective way to draw a circle with predefined dimensions at any position on the window by simply clicking.
Here’s an example:
import cv2 import numpy as np # Create a function to be called when a mouse event happens def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDOWN: cv2.circle(img, (x, y), 20, (255, 0, 0), -1) # Create a black image and a window img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') # Bind the draw_circle function to mouse clicks cv2.setMouseCallback('image', draw_circle) while True: cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF == 27: # exit on ESC break cv2.destroyAllWindows()
The output of this code is a window that displays a black image. When the user clicks anywhere on the image, a blue circle with a radius of 20 pixels is drawn where the mouse is clicked.
This snippet sets up an event listener for mouse clicks within an OpenCV window and responds by drawing a circle on the image at the click coordinates. The color and size of the circle are pre-defined, and the window will display the updated image until the ‘ESC’ key is pressed.
Method 2: Click-and-Drag Circle Drawing
Drawing a circle with click-and-drag functionality allows more control over the size of the circle. Users press the mouse button down at one point, drag to resize the circle, and release to finalize its dimensions and position.
Here’s an example:
import cv2 import numpy as np # Initialize the starting position and radius start_point = None radius = -1 # Create a function to handle mouse clicks and movements def draw_circle(event, x, y, flags, param): global start_point, radius if event == cv2.EVENT_LBUTTONDOWN: start_point = (x, y) radius = 0 elif event == cv2.EVENT_MOUSEMOVE: if start_point is not None: radius = int(np.sqrt((x - start_point[0])**2 + (y - start_point[1])**2)) elif event == cv2.EVENT_LBUTTONUP: cv2.circle(img, start_point, radius, (0, 255, 0), 2) start_point = None # Create a black image and window, and bind the mouse callback img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image', draw_circle) # Main application loop while True: cv2.imshow('image', img) if start_point and radius >= 0: temp_img = img.copy() cv2.circle(temp_img, start_point, radius, (0, 255, 0), 2) cv2.imshow('image', temp_img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
The output of this code is a dynamic drawing experience. The user begins drawing a circle by pressing the mouse button down, drags to adjust the size, and releases to draw the final circle.
The code manages a live update of the circle as the mouse is moved, with the circle completing once the mouse is released. It demonstrates utilizing global variables for states and provides a more interactive drawing experience than the first method.
Method 3: Drawing Multiple Circles
For applications that require multiple circles to be drawn, this method stores the center and radius of each circle and redraws the entire set each time a new circle is added.
Here’s an example:
import cv2 import numpy as np # List to store circles' center points and radii circles = [] # Mouse callback function to draw multiple circles def draw_circle(event, x, y, flags, param): if event == cv2.EVENT_LBUTTONDBLCLK: radius = 15 circles.append((x, y, radius)) # Create a function to draw all circles in the list def redraw_all_circles(img): for center_x, center_y, radius in circles: cv2.circle(img, (center_x, center_y), radius, (0, 0, 255), -1) # Create black image, window and set the mouse callback function img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image', draw_circle) # Main application loop while True: # Redraw all circles img_copy = img.copy() redraw_all_circles(img_copy) cv2.imshow('image', img_copy) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
The output of this code is a window where the user can double-click to draw multiple red circles, each with a fixed radius of 15 pixels.
This code effectively handles the creation of multiple circles using a double-click event. It uses a separate function to redraw all circles whenever a new one is added, allowing the circles to persist on the window.
Method 4: Real-Time Circle Preview
This method offers a real-time preview of the circle as the user moves the mouse after an initial click. The circle size adjusts dynamically with the distance between the initial point and the current mouse position.
Here’s an example:
import cv2 import numpy as np # Global variables store the initial point and the preview status initial_point = None drawing = False # Mouse callback function for circle preview def draw_circle(event, x, y, flags, param): global initial_point, drawing if event == cv2.EVENT_LBUTTONDOWN: drawing = True initial_point = (x, y) elif event == cv2.EVENT_MOUSEMOVE and drawing: radius = int(np.sqrt((x - initial_point[0])**2 + (y - initial_point[1])**2)) temp_img = img.copy() cv2.circle(temp_img, initial_point, radius, (255, 255, 0), 1) cv2.imshow('image', temp_img) elif event == cv2.EVENT_LBUTTONUP: drawing = False radius = int(np.sqrt((x - initial_point[0])**2 + (y - initial_point[1])**2)) cv2.circle(img, initial_point, radius, (255, 255, 0), 1) # Initialize the image and window and set callbacks img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') cv2.setMouseCallback('image', draw_circle) # Main application loop while True: cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
The output of this code is a real-time preview of the yellow circle as it is being drawn. The circle is finalized when the user releases the mouse button.
This method provides immediate visual feedback, enhancing the user experience. It employs conditional drawing logic within the mouse callback to update the circle’s dimensions as the mouse moves.
Bonus One-Liner Method 5: Circle Drawing with Lambda
For a concise and quick approach, a lambda function can be used within the mouse callback to draw circles with a one-liner function call.
Here’s an example:
import cv2 import numpy as np # Initialize the image and window img = np.zeros((512, 512, 3), np.uint8) cv2.namedWindow('image') # One-liner mouse callback function using lambda cv2.setMouseCallback('image', lambda event, x, y, flags, param: cv2.circle(img, (x, y), 20, (127, 127, 127), -1) if event == cv2.EVENT_LBUTTONDBLCLK else None) # Main application loop while True: cv2.imshow('image', img) if cv2.waitKey(20) & 0xFF == 27: break cv2.destroyAllWindows()
The output of this code is a window where double-clicking draws a gray circle of fixed size.
This minimalist approach simplifies the callback function to one line using a lambda. It can draw a circle with fixed parameters but lacks advanced features like adjusting the circle’s size or providing a preview.
Summary/Discussion
- Method 1: Simple Click-and-Draw. Strengths: Easy to implement, good for static circle size. Weaknesses: No size control or real-time preview.
- Method 2: Click-and-Drag Circle Drawing. Strengths: Allows resizing during drawing, more interactive. Weaknesses: Implementation slightly more complex.
- Method 3: Drawing Multiple Circles. Strengths: Suitable for drawing multiple circles without losing previous ones. Weaknesses: May get slow with a large number of circles.
- Method 4: Real-Time Circle Preview. Strengths: Provides immediate visual feedback with dynamic radius. Weaknesses: Might be overkill for simple tasks.
- Bonus Method 5: One-Liner Lambda. Strengths: Extremely concise code. Weaknesses: Highly limited, no customization or preview.