Drawing shapes and text

OpenCV · Essentials · 7 min

Visualising detections, boxes and debug info on frames is how you check that a vision pipeline works.

OpenCV's drawing functions modify the image in place and take coordinates as (x, y) tuples of ints:

  • cv2.line(img, pt1, pt2, color, thickness)
  • cv2.rectangle(img, top_left, bottom_right, color, thickness)
  • cv2.circle(img, center, radius, color, thickness)
  • cv2.polylines(img, [pts], isClosed, color, thickness) where pts is an int32 array of shape (n, 1, 2) or (n, 2)
  • cv2.putText(img, text, origin, font, scale, color, thickness, lineType); the origin is the bottom-left of the text

Colours are BGR tuples, (0, 0, 255) is red. A thickness of -1 (or cv2.FILLED) fills the shape. Pass lineType=cv2.LINE_AA for anti-aliased, smoother edges.

Coordinates must be integers. Detection boxes often come as floats, so convert with int(...) or np.round(...).astype(int) before drawing; recent versions raise an error on floats.

Because drawing changes the array, draw on img.copy() when you still need the clean image, for example to feed it to the next stage of a pipeline.

cv2.getTextSize(text, font, scale, thickness) returns the text's width and height plus the baseline, which lets you draw a filled background box behind a label so it stays readable on any image. Only a small set of Hershey fonts is built in, without Unicode; for other scripts, render text with Pillow.

Example

import cv2
import numpy as np

canvas = np.zeros((200, 300, 3), np.uint8)
cv2.rectangle(canvas, (20, 30), (140, 120), (0, 255, 0), 2)    # green box
cv2.circle(canvas, (220, 75), 40, (0, 0, 255), -1)             # filled red
cv2.line(canvas, (0, 199), (299, 150), (255, 255, 255), 1, cv2.LINE_AA)

pts = np.array([[160, 180], [200, 140], [240, 180]], np.int32)
cv2.polylines(canvas, [pts], True, (255, 0, 0), 2)              # blue triangle

label = "cat 0.93"
font = cv2.FONT_HERSHEY_SIMPLEX
(tw, th), base = cv2.getTextSize(label, font, 0.5, 1)
cv2.rectangle(canvas, (20, 30 - th - base), (20 + tw, 30), (0, 255, 0), -1)
cv2.putText(canvas, label, (20, 30 - base), font, 0.5, (0, 0, 0), 1, cv2.LINE_AA)

print(canvas[75, 220])      # [  0   0 255]  inside the red circle
print(canvas[30, 80])       # [  0 255   0]  on the box edge

Remember

  • Drawing functions modify the image in place; copy first if needed
  • Points are integer (x, y) tuples, colours are BGR
  • thickness=-1 fills; LINE_AA smooths edges
  • putText's origin is the text's bottom-left corner

Watch out for

  • Passing float coordinates from a detector straight into cv2.rectangle.

Check yourself

  1. What does `cv2.circle(img, (50, 20), 10, (255, 0, 0), -1)` draw?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.opencv.org/4.x/dc/da5/tutorial_py_drawing_functions.html