multi-object-tracking / src /annotator.py
Ashutosh Naveen
Deploy to Hugging Face Spaces: Multi-Object Tracking Pipeline
d2ada6d
Raw
History Blame Contribute Delete
3.79 kB
"""Frame annotation and visualization module."""
import cv2
import numpy as np
import supervision as sv
from config.settings import AnnotatorConfig
from src.utils import frame_to_time
class Annotator:
"""Draws bounding boxes, ID labels, and trajectory trails on video frames.
Uses the supervision library for production-quality annotations.
"""
def __init__(self, config: AnnotatorConfig, fps: float = 30.0):
self.config = config
self.fps = fps
# Supervision annotators
self.box_annotator = sv.BoxAnnotator(
thickness=config.box_thickness,
)
self.label_annotator = sv.LabelAnnotator(
text_scale=config.font_scale,
text_thickness=1,
text_position=sv.Position.TOP_LEFT,
)
# Trajectory trail annotator
if config.show_trajectory:
self.trace_annotator = sv.TraceAnnotator(
trace_length=config.trail_length,
thickness=2,
position=sv.Position.BOTTOM_CENTER,
)
else:
self.trace_annotator = None
def draw(
self,
frame: np.ndarray,
detections: sv.Detections,
frame_idx: int,
) -> np.ndarray:
"""Annotate a frame with tracked detections.
Args:
frame: Original BGR frame.
detections: Tracked detections with tracker_id.
frame_idx: Current frame index (for overlay).
Returns:
Annotated frame as numpy array.
"""
annotated = frame.copy()
if len(detections) == 0:
return self._add_overlay(annotated, frame_idx, 0)
# Build labels
labels = self._build_labels(detections)
# Draw trajectory trails first (behind boxes)
if self.trace_annotator is not None:
annotated = self.trace_annotator.annotate(
scene=annotated, detections=detections
)
# Draw bounding boxes
annotated = self.box_annotator.annotate(
scene=annotated, detections=detections
)
# Draw labels
annotated = self.label_annotator.annotate(
scene=annotated, detections=detections, labels=labels
)
# Add frame info overlay
annotated = self._add_overlay(annotated, frame_idx, len(detections))
return annotated
def _build_labels(self, detections: sv.Detections) -> list:
"""Build label strings for each detection."""
labels = []
for i in range(len(detections)):
track_id = (
detections.tracker_id[i]
if detections.tracker_id is not None
else "?"
)
if self.config.show_confidence and detections.confidence is not None:
conf = detections.confidence[i]
labels.append(f"ID:{track_id} ({conf:.2f})")
else:
labels.append(f"ID:{track_id}")
return labels
def _add_overlay(
self, frame: np.ndarray, frame_idx: int, num_tracked: int
) -> np.ndarray:
"""Add frame counter and tracking info overlay."""
if not self.config.show_frame_counter:
return frame
timestamp = frame_to_time(frame_idx, self.fps)
text = f"Frame: {frame_idx} | Time: {timestamp} | Tracked: {num_tracked}"
# Semi-transparent background bar
h, w = frame.shape[:2]
overlay = frame.copy()
cv2.rectangle(overlay, (0, 0), (w, 30), (0, 0, 0), -1)
frame = cv2.addWeighted(overlay, 0.6, frame, 0.4, 0)
cv2.putText(
frame, text, (10, 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1,
)
return frame