Computer-vision results are easier to review when the saved frame carries the same boxes, labels, and landmarks used during processing. OpenCV can add those annotations to an image array before writing the result to disk.
Most drawing functions modify their destination array in place. Keeping image as the source and drawing on annotated = image.copy() preserves the original pixels for comparison while letting every coordinate scale with the actual image dimensions.
Three-channel colors use blue, green, red order in OpenCV, and cv.putText() places its origin at the lower-left corner of the text. A translucent fill needs a temporary image plus cv.addWeighted() because ordinary drawing calls replace the pixels they touch.
Related: How to read and write an image with OpenCV
Related: How to install OpenCV on Ubuntu
#!/usr/bin/env python3 import argparse from pathlib import Path import cv2 as cv import numpy as np parser = argparse.ArgumentParser(description="Draw text and shape overlays with OpenCV.") parser.add_argument("input_image", type=Path) parser.add_argument("output_image", type=Path) args = parser.parse_args() image = cv.imread(str(args.input_image), cv.IMREAD_COLOR) if image is None: raise SystemExit(f"could not read image: {args.input_image}") annotated = image.copy() height, width = annotated.shape[:2]
box_start = (int(width * 0.12), int(height * 0.18)) box_end = (int(width * 0.62), int(height * 0.58)) label_origin = (box_start[0] + 18, box_start[1] - 14) center = (int(width * 0.75), int(height * 0.34)) radius = max(18, min(width, height) // 16) polyline = np.array( [ (int(width * 0.18), int(height * 0.76)), (int(width * 0.36), int(height * 0.66)), (int(width * 0.55), int(height * 0.82)), ], dtype=np.int32, ).reshape((-1, 1, 2))
Each point is calculated from width and height so the same annotations stay in comparable regions on other image sizes.
overlay = annotated.copy() cv.rectangle(overlay, box_start, box_end, (0, 180, 255), cv.FILLED) cv.addWeighted(overlay, 0.35, annotated, 0.65, 0, annotated)
The two weights total 1.0. The orange fill contributes 35 percent of each output pixel while the current annotated image contributes 65 percent.
cv.rectangle(annotated, box_start, box_end, (0, 140, 255), 3, cv.LINE_AA) cv.putText( annotated, "inspection zone", label_origin, cv.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 4, cv.LINE_AA, ) cv.putText( annotated, "inspection zone", label_origin, cv.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2, cv.LINE_AA, ) cv.circle(annotated, center, radius, (255, 80, 80), 3, cv.LINE_AA) cv.drawMarker(annotated, center, (255, 255, 255), cv.MARKER_CROSS, radius * 2, 2, cv.LINE_AA) cv.polylines(annotated, [polyline], False, (80, 255, 80), 4, cv.LINE_AA)
The black text stroke is drawn first, then the narrower white stroke creates a readable outlined label. cv.LINE_AA smooths the visible edges on this 8-bit image.
Related: How to convert image color spaces with OpenCV
args.output_image.parent.mkdir(parents=True, exist_ok=True) if not cv.imwrite(str(args.output_image), annotated): raise SystemExit(f"could not write image: {args.output_image}") changed = cv.absdiff(image, annotated) changed_pixels = int(np.count_nonzero(cv.cvtColor(changed, cv.COLOR_BGR2GRAY))) if changed_pixels == 0: raise SystemExit("no overlay pixels changed") print(f"saved: {args.output_image}") print(f"size: {width}x{height}") print(f"changed pixels: {changed_pixels}")
$ python3 draw_image_overlays.py input/scene.png output/scene-overlays.png saved: output/scene-overlays.png size: 720x480 changed pixels: 79925
$ python3 - <<'PY'
import cv2 as cv
import numpy as np
source = cv.imread("input/scene.png")
result = cv.imread("output/scene-overlays.png")
if source is None or result is None:
raise SystemExit("could not read source or overlay output")
if source.shape != result.shape:
raise SystemExit("source and overlay dimensions differ")
changed = cv.absdiff(source, result)
changed_pixels = int(np.count_nonzero(cv.cvtColor(changed, cv.COLOR_BGR2GRAY)))
if changed_pixels == 0:
raise SystemExit("overlay output matches the source")
print(f"overlay verified: {result.shape[1]}x{result.shape[0]}")
print(f"changed pixels: {changed_pixels}")
PY
overlay verified: 720x480
changed pixels: 79925