Visual Annotation Toolkit for Ultralytics YOLO

Overview

The Ultralytcis ecosystem ships with a lightweight Annotator utility that can overlay detection masks, bounding boxes, oriented boxes, and keypoints on any image or video stream. The snippets below demonstrate typical use-cases.

Interactive sweep counter on a video

The following example tracks every object that crosses a user-draggable veritcal line and continuously prints the running count. The line positino is updated through mouse events.

import cv2
import numpy as np
from ultralytics import YOLO
from ultralytics.utils.plotting import Annotator, colors

video_path = "demo.mp4"
model_path = "yolo11s-seg.pt"

capture = cv2.VideoCapture(video_path)
model = YOLO(model_path)

if not capture.isOpened():
    raise RuntimeError("Cannot open video file")

width  = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
fps    = int(capture.get(cv2.CAP_PROP_FPS))

writer = cv2.VideoWriter("annotated.avi",
                         cv2.VideoWriter_fourcc(*"mp4v"),
                         fps, (width, height))

line_x = width          # vertical line starts at the right border
drag   = False
frame_id = 0

def on_mouse(event, mx, my, flags, _):
    global line_x, drag
    if event == cv2.EVENT_LBUTTONDOWN:
        drag = True
    if drag and (flags & cv2.EVENT_FLAG_LBUTTON):
        line_x = max(0, min(mx, width))

cv2.namedWindow("Sweep")
cv2.setMouseCallback("Sweep", on_mouse)

while capture.isOpened():
    ok, img = capture.read()
    if not ok:
        break
    frame_id += 1

    annot = Annotator(img)
    preds = model.track(img, persist=True)[0]

    counter = 0
    if preds.boxes.id is not None:
        ids   = preds.boxes.id.int().cpu().tolist()
        cls   = preds.boxes.cls.cpu().tolist()
        boxes = preds.boxes.xyxy.cpu().numpy()
        masks = preds.masks.xy if preds.masks else [None] * len(boxes)

        for m, b, c, tid in zip(masks, boxes, cls, ids):
            col = colors(tid, True)
            if b[0] > line_x:
                counter += 1
                if m is not None and len(m):
                    annot.seg_bbox(mask=m, mask_color=col, label=str(model.names[int(c)]))
                else:
                    annot.box_label(box=b, color=col, label=str(model.names[int(c)]))

    annot.sweep_annotator(line_x=line_x, line_y=height, label=f"Count:{counter}")
    cv2.imshow("Sweep", img)
    writer.write(img)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break

capture.release()
writer.release()
cv2.destroyAllWindows()

Horizontal bounding boxes on a static image

Quickly draw classic axis-aligned boxes with custom labels.

import cv2
import numpy as np
from ultralytics.utils.plotting import Annotator, colors

labels = {0: "person", 5: "bus", 11: "stop sign"}
img = cv2.imread("bus.jpg")

ann = Annotator(img,_width=None, font_size=None, font="Arial.ttf", pil=False)

boxes = np.array([
    [5,  22.9, 231.3, 805.0, 756.8],
    [0,  48.6, 398.6, 245.4, 902.7],
    [0, 669.5, 392.2, 809.7, 877.0],
    [0, 221.5, 405.8, 345.0, 857.5],
    [0,   0.0, 550.5,  63.0, 873.4],
    [11,  0.1, 254.5,  32.6, 324.9],
])

for row in boxes:
    cid, *xyxy = row
    ann.box_label(xyxy, label=f"{int(cid):02d}:{labels[int(cid)]}",
                  color=colors(int(cid), bgr=True))

cv2.imwrite("bus_bboxes.jpg", ann.result())

Oriented bounding boxes (OBB)

For datasets such as DOTA, you can render rotated rectangles by passing the four corner points.

import cv2
import numpy as np
from ultralytics.utils.plotting import Annotator, colors

names = {0: "small vehicle"}
img = cv2.imread("P1142__1024__0___824.jpg")

obb = np.array([
    [0, 635, 560, 919, 719, 1087, 420, 803, 261],
    [0, 331,  19, 493, 260, 776,  70, 613, -171],
])

ann = Annotator(img, line_width=None, font_size=None, font="Arial.ttf", pil=False)

for row in obb:
    cid, *pts = row
    pts = np.array(pts).reshape(4, 2)
    ann.box_label(pts, label=names[int(cid)],
                  color=colors(int(cid), True), rotated=True)

cv2.imwrite("obb_demo.jpg", ann.result())

Tags: ultralytics YOLO OpenCV Computer Vision Object Detection

Posted on Wed, 24 Jun 2026 16:42:36 +0000 by neex1233