Interactive Drawing in OpenCV with Python: Crafting Curves with Mouse Events

πŸ’‘ Problem Formulation: When working with computer vision or graphics in Python, a common challenge is enabling users to interact directly with images by drawing with the mouse. This becomes especially interesting when the task at hand is to draw curves rather than straight lines. We look for a solution to translate mouse movements into smooth curved lines in an image window using OpenCV, with the input being mouse clicks and movements, and the desired output being a graphical representation of the user-drawn curve.

Method 1: Simple Drag and Create Curves

This method involves setting up a mouse callback function in OpenCV which listens for mouse events and allows the user to hold and drag to create a curve. As the mouse is moved, the positions are recorded and lines are drawn between points to simulate a curve.

Here’s an example:

import cv2
import numpy as np

# Creating a window
windowName = 'Drawing'
cv2.namedWindow(windowName)

# Mouse callback function
points = []

def draw_curve(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        points.append((x, y))
    elif event == cv2.EVENT_MOUSEMOVE and flags == cv2.EVENT_FLAG_LBUTTON:
        points.append((x, y))
        cv2.line(img, points[-2], points[-1], (0, 255, 0), 5)
    elif event == cv2.EVENT_LBUTTONUP:
        points.clear()

# Bind the callback function to window
cv2.setMouseCallback(windowName, draw_curve)

img = np.zeros((512, 512, 3), np.uint8)
while True:
    cv2.imshow(windowName, img)
    if cv2.waitKey(20) == 27:
        break
cv2.destroyAllWindows()

The output of this code snippet is a window where the user can draw curves using the mouse by clicking and dragging.

This method’s example code sets up an OpenCV window and continuously listens for mouse events, updating the image with connecting lines as the user drags the mouse. When the user releases the button, the curve is completed, and the points list is cleared for a new curve.

Method 2: Bezier Curve Drawing with Mouse Events

Bezier curves provide a more controlled approach to drawing curves by allowing users to set control points that define the curve’s shape. We will capture the required control points through mouse clicks and then use them to calculate and draw a Bezier curve.

Here’s an example:

import cv2
import numpy as np

img = np.zeros((512, 512, 3), np.uint8)
windowName = 'Bezier Curve'
cv2.namedWindow(windowName)

# Store points for the Bezier curve
control_points = []

def draw_bezier_curve(control_points):
    # Algorithm to draw a quadratic Bezier curve 
    # given control points would go here
    pass

def mouse_callback(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN:
        cv2.circle(img, (x, y), 3, (0, 0, 255), -1)
        control_points.append((x, y))
        if len(control_points) == 3:
            draw_bezier_curve(control_points)

cv2.setMouseCallback(windowName, mouse_callback)

while True:
    cv2.imshow(windowName, img)
    if cv2.waitKey(20) & 0xFF == 27:
        break

cv2.destroyAllWindows()

The output of this code snippet would be a window in which users can click to set three control points and the program then draws a corresponding quadratic Bezier curve.

The example code snippet sets up an OpenCV window to register mouse clicks for control points. The draw_bezier_curve() function, which would contain the Bezier curve algorithm, would then create the curve once three control points are established.

Method 3: Continuous Free-Hand Drawing with Brush Strokes

Free-hand drawing mimics the natural motion of drawing with a pen on paper. Here, we continuously capture the mouse position as it moves, and at very close intervals, we draw short lines that together form the smooth curve.

Here’s an example:

import cv2
import numpy as np

img = np.zeros((512, 512, 3), np.uint8)
windowName = 'Free-hand Drawing'
cv2.namedWindow(windowName)

drawing = False
last_point = (0, 0)

def mouse_callback(event, x, y, flags, param):
    global last_point, drawing
    if event == cv2.EVENT_LBUTTONDOWN:
        drawing = True
        last_point = (x, y)
    elif event == cv2.EVENT_MOUSEMOVE:
        if drawing is True:
            cv2.line(img, last_point, (x, y), (255, 0, 0), 5)
            last_point = (x, y)
    elif event == cv2.EVENT_LBUTTONUP:
        drawing = False

cv2.setMouseCallback(windowName, mouse_callback)

while True:
    cv2.imshow(windowName, img)
    if cv2.waitKey(1) & 0xFF == 27:
        break

cv2.destroyAllWindows()

The output of this code is a window in which users can draw smooth curves freely as if they were using a brush.

In the provided example, we set the initial state to not drawing. When the user presses the left mouse button, we mark the beginning of the drawing action and record positions as the mouse moves. When the button is released, we stop the drawing action.

Method 4: Using Splines to Draw Curves

Splines are mathematical representations of curves that, much like Bezier curves, are defined by control points. However, splines tend to create a more relaxed curve that passes closer to each control point. In this method, we will use mice events to define control points for a spline.

Here’s an example:

# A hypothetical example would be similar to the Bezier example, but
# with a function implementing spline interpolation instead.

The output would be a more natural, smooth curve that closely fits the given control points, which are acquired via mouse interactions.

While concrete implementation for splines is not provided here, the idea is to replace the draw_bezier_curve() function with a spline drawing algorithm. The user interface for capturing control points remains essentially the same, providing a seamless experience for the interaction.

Bonus One-Liner Method 5: Drawing with a Pre-Defined Shape

For some applications, it may be beneficial to allow users to draw pre-defined shapes or curves by simply clicking and dragging. In this method, we propose a one-liner method that binds mouse events to a function that stamps a curve template at the cursor’s location.

Here’s an example:

# One-liner that binds mouse move event to drawing a pre-defined curve shape would go here.

The output would be a sequence of identical curve stamps following the user’s mouse path in the window.

This method is intentionally left abstract to emphasize its simplicity and conceptual approach. The actual implementation would consist of a handler for mouse events that attaches a static curve image or shape to the cursor position each time it is moved.

Summary/Discussion

Method 1: Simple Drag and Create. Strength: Easy to implement, allowing for immediate interaction. Weakness: Curves may not be very smooth.
Method 2: Bezier Curve Drawing. Strength: Greater control over the curve’s shape. Weakness: Requires implementation of Bezier curve algorithm.
Method 3: Continuous Free-Hand. Strength: Natural drawing feel. Weakness: Heavily reliant on mouse movement precision.
Method 4: Splines. Strength: Smooth and natural curves. Weakness: More complex mathematics involved; typically heavier computation.
Bonus Method 5: Pre-Defined Shapes. Strength: Consistency in the shape of curves. Weakness: Limited to pre-set curve types, less creative freedom.