On-Screen Display

Overview

The On-Screen Display (OSD) sample demonstrates GPU-accelerated compositing of visual annotations—bounding boxes, text labels, lines, circles, arrows, polygons, and clock overlays—onto an image using CV-CUDA’s cvcuda.osd() operator.

Usage

Basic Usage

Draw the default set of OSD elements onto the sample cat image:

python3 osd.py -i input.jpg

Custom Input and Output

Specify a custom input image and output path:

python3 osd.py -i input.jpg -o annotated.jpg

Command-Line Arguments

Argument

Short Form

Default

Description

--input

-i

tabby_tiger_cat.jpg

Input image file path

--output

-o

cvcuda/.cache/cat_osd.jpg

Output image file path with OSD annotations

Implementation

OSD Element Setup

# The OSD operator requires an NHWC batch tensor and a list-of-lists of
# elements — one inner list per image in the batch.  We promote the single
# HWC image to a batch of 1 (N=1).
h, w, c = input_image.shape
nhwc_image: cvcuda.Tensor = input_image.reshape((1, h, w, c), "NHWC")

# Build a representative set of OSD primitives.  All coordinates are in
# pixels; colours are RGBA tuples.  We scale positions relative to the
# image dimensions so the overlay looks reasonable on any input size.
box_x, box_y = w // 8, h // 8
box_w, box_h = w // 4, h // 4

elements = cvcuda.Elements(
    elements=[
        [
            # Filled bounding box drawn around the top-left region
            cvcuda.BndBoxI(
                box=(box_x, box_y, box_w, box_h),
                thickness=3,
                borderColor=(255, 255, 0),
                fillColor=(0, 128, 255, 64),
            ),
            # Text label placed near the top-left of the image
            cvcuda.Label(
                utf8Text="CV-CUDA OSD",
                fontSize=24,
                tlPos=(box_x, box_y - 30 if box_y >= 30 else box_y + box_h + 5),
                fontColor=(255, 255, 255),
                bgColor=(0, 0, 0, 180),
            ),
            # Diagonal line across the image
            cvcuda.Line(
                pos0=(0, 0),
                pos1=(w - 1, h - 1),
                thickness=2,
                color=(0, 255, 0),
            ),
            # Circle at the image centre
            cvcuda.Circle(
                centerPos=(w // 2, h // 2),
                radius=min(w, h) // 8,
                thickness=2,
                borderColor=(255, 128, 0),
                bgColor=(255, 128, 0, 48),
            ),
            # Arrow pointing inward from the right edge
            cvcuda.Arrow(
                pos0=(w - 1, h // 2),
                pos1=(w * 3 // 4, h // 2),
                arrowSize=12,
                thickness=2,
                color=(255, 0, 128),
            ),
            # Closed polygon (diamond shape) at the image centre
            cvcuda.PolyLine(
                points=np.array(
                    [
                        [w // 2, h // 4],
                        [w * 3 // 4, h // 2],
                        [w // 2, h * 3 // 4],
                        [w // 4, h // 2],
                    ],
                    dtype=np.int32,
                ),
                thickness=2,
                isClosed=True,
                borderColor=(0, 255, 255),
                fillColor=(0, 255, 255, 32),
            ),
            # Timestamp overlay in the bottom-left corner
            cvcuda.Clock(
                clockFormat=cvcuda.ClockFormat.YYMMDD_HHMMSS,
                time=0,
                fontSize=14,
                tlPos=(10, h - 30 if h > 40 else 5),
                fontColor=(255, 255, 0),
                bgColor=(0, 0, 0, 160),
            ),
        ]
    ]
)

OSD Operator Call

# The OSD operator requires an NHWC batch tensor and a list-of-lists of
# elements — one inner list per image in the batch.  We promote the single
# HWC image to a batch of 1 (N=1).
h, w, c = input_image.shape
nhwc_image: cvcuda.Tensor = input_image.reshape((1, h, w, c), "NHWC")

# Build a representative set of OSD primitives.  All coordinates are in
# pixels; colours are RGBA tuples.  We scale positions relative to the
# image dimensions so the overlay looks reasonable on any input size.
box_x, box_y = w // 8, h // 8
box_w, box_h = w // 4, h // 4

elements = cvcuda.Elements(
    elements=[
        [
            # Filled bounding box drawn around the top-left region
            cvcuda.BndBoxI(
                box=(box_x, box_y, box_w, box_h),
                thickness=3,
                borderColor=(255, 255, 0),
                fillColor=(0, 128, 255, 64),
            ),
            # Text label placed near the top-left of the image
            cvcuda.Label(
                utf8Text="CV-CUDA OSD",
                fontSize=24,
                tlPos=(box_x, box_y - 30 if box_y >= 30 else box_y + box_h + 5),
                fontColor=(255, 255, 255),
                bgColor=(0, 0, 0, 180),
            ),
            # Diagonal line across the image
            cvcuda.Line(
                pos0=(0, 0),
                pos1=(w - 1, h - 1),
                thickness=2,
                color=(0, 255, 0),
            ),
            # Circle at the image centre
            cvcuda.Circle(
                centerPos=(w // 2, h // 2),
                radius=min(w, h) // 8,
                thickness=2,
                borderColor=(255, 128, 0),
                bgColor=(255, 128, 0, 48),
            ),
            # Arrow pointing inward from the right edge
            cvcuda.Arrow(
                pos0=(w - 1, h // 2),
                pos1=(w * 3 // 4, h // 2),
                arrowSize=12,
                thickness=2,
                color=(255, 0, 128),
            ),
            # Closed polygon (diamond shape) at the image centre
            cvcuda.PolyLine(
                points=np.array(
                    [
                        [w // 2, h // 4],
                        [w * 3 // 4, h // 2],
                        [w // 2, h * 3 // 4],
                        [w // 4, h // 2],
                    ],
                    dtype=np.int32,
                ),
                thickness=2,
                isClosed=True,
                borderColor=(0, 255, 255),
                fillColor=(0, 255, 255, 32),
            ),
            # Timestamp overlay in the bottom-left corner
            cvcuda.Clock(
                clockFormat=cvcuda.ClockFormat.YYMMDD_HHMMSS,
                time=0,
                fontSize=14,
                tlPos=(10, h - 30 if h > 40 else 5),
                fontColor=(255, 255, 0),
                bgColor=(0, 0, 0, 160),
            ),
        ]
    ]
)

Key points:

  1. Tensor layout: cvcuda.osd() supports NHWC/HWC and NCHW/CHW tensors. The sample uses NHWC: a single HWC image is reshaped to (1, H, W, C) before the call and squeezed back afterward.

  2. Elements list-of-lists: cvcuda.Elements takes a list with one inner list per image in the batch. Each inner list may contain any mix of the supported primitive types.

  3. In-place compositing: The output tensor shares shape, dtype, and layout with the input; all primitives are alpha-blended onto it in a single GPU pass.

  4. Coordinate scaling: Positions and sizes are computed relative to the image dimensions so the overlay adapts to any input resolution.

  5. Primitive variety: The sample showcases all major OSD primitives— cvcuda.BndBoxI, cvcuda.Label, cvcuda.Line, cvcuda.Circle, cvcuda.Arrow, cvcuda.PolyLine, and cvcuda.Clock.

Expected Output

The output is the input image with all OSD annotations composited on top:

../../_images/tabby_tiger_cat.jpg

Original Input Image

../../_images/cat_osd.jpg

Output: Image with OSD annotations

CV-CUDA Operators Used

Operator

Purpose

cvcuda.osd()

Composite bounding boxes, text, lines, shapes, and overlays onto images

Common Utilities Used

See Also