Files
wehub-resource-sync 9194ef5abd
Docs/Test Workflow / Test docs build (push) Failing after 0s
Check links & references / links-check (push) Failing after 1s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.10) (push) Failing after 0s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.11) (push) Failing after 0s
PR Conflict Labeler / main (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.12) (push) Failing after 2s
Pytest/Test Workflow / Import Test and Pytest Run (ubuntu-latest, 3.13) (push) Failing after 0s
Pytest/Test Workflow / Build this Package (push) Failing after 5s
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (macos-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.10) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.11) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.12) (push) Has been cancelled
Pytest/Test Workflow / Import Test and Pytest Run (windows-latest, 3.13) (push) Has been cancelled
Pytest/Test Workflow / testing-guardian (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:06:10 +08:00

175 lines
6.0 KiB
Python

from __future__ import annotations
from enum import Enum
import cv2
import numpy as np
from rfdetr import RFDETRBase, RFDETRLarge, RFDETRMedium, RFDETRNano, RFDETRSmall
from utils.general import find_in_list, load_zones_config
from utils.timers import FPSBasedTimer
import supervision as sv
COLORS = sv.ColorPalette.from_hex(["#E6194B", "#3CB44B", "#FFE119", "#3C76D1"])
COLOR_ANNOTATOR = sv.ColorAnnotator(color=COLORS)
LABEL_ANNOTATOR = sv.LabelAnnotator(
color=COLORS, text_color=sv.Color.from_hex("#000000")
)
class ModelSize(Enum):
NANO = "nano"
SMALL = "small"
MEDIUM = "medium"
BASE = "base"
LARGE = "large"
@classmethod
def list(cls) -> list[str]:
return list(map(lambda c: c.value, cls))
@classmethod
def from_value(cls, value: ModelSize | str) -> ModelSize:
if isinstance(value, cls):
return value
if isinstance(value, str):
value = value.lower()
try:
return cls(value)
except ValueError:
raise ValueError(f"Invalid value: {value}. Must be one of {cls.list()}")
raise ValueError(
f"Invalid value type: {type(value)}. Must be an instance of "
f"{cls.__name__} or str."
)
def load_model(
checkpoint: ModelSize | str, device: str, resolution: int
) -> RFDETRBase | RFDETRLarge | RFDETRMedium | RFDETRNano | RFDETRSmall:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint == ModelSize.NANO:
return RFDETRNano(device=device, resolution=resolution)
if checkpoint == ModelSize.SMALL:
return RFDETRSmall(device=device, resolution=resolution)
if checkpoint == ModelSize.MEDIUM:
return RFDETRMedium(device=device, resolution=resolution)
if checkpoint == ModelSize.BASE:
return RFDETRBase(device=device, resolution=resolution)
if checkpoint == ModelSize.LARGE:
return RFDETRLarge(device=device, resolution=resolution)
raise ValueError(
f"Invalid checkpoint: {checkpoint}. Must be one of: {ModelSize.list()}."
)
def adjust_resolution(checkpoint: ModelSize | str, resolution: int) -> int:
checkpoint = ModelSize.from_value(checkpoint)
if checkpoint in {ModelSize.NANO, ModelSize.SMALL, ModelSize.MEDIUM}:
divisor = 32
elif checkpoint in {ModelSize.BASE, ModelSize.LARGE}:
divisor = 56
else:
raise ValueError(
f"Unknown checkpoint: {checkpoint}. Must be one of: {ModelSize.list()}."
)
remainder = resolution % divisor
if remainder == 0:
return resolution
lower = resolution - remainder
upper = lower + divisor
if resolution - lower < upper - resolution:
return lower
else:
return upper
def main(
source_video_path: str,
zone_configuration_path: str,
resolution: int,
model_size: str = "small",
device: str = "cpu",
confidence_threshold: float = 0.3,
iou_threshold: float = 0.7,
classes: list[int] = [],
) -> None:
"""
Calculating detections dwell time in zones, using video file.
Args:
source_video_path: Path to the source video file
zone_configuration_path: Path to the zone configuration JSON file
resolution: Input resolution for the model
model_size: RF-DETR model size ('nano', 'small', 'medium', 'base' or 'large')
device: Computation device ('cpu', 'mps' or 'cuda')
confidence_threshold: Confidence level for detections (0 to 1)
iou_threshold: IOU threshold for non-max suppression
classes: List of class IDs to track. If empty, all classes are tracked
"""
resolution = adjust_resolution(checkpoint=model_size, resolution=resolution)
model = load_model(checkpoint=model_size, device=device, resolution=resolution)
tracker = sv.ByteTrack(minimum_matching_threshold=0.5)
video_info = sv.VideoInfo.from_video_path(video_path=source_video_path)
frames_generator = sv.get_video_frames_generator(source_video_path)
polygons = load_zones_config(file_path=zone_configuration_path)
zones = [
sv.PolygonZone(
polygon=polygon,
triggering_anchors=(sv.Position.CENTER,),
)
for polygon in polygons
]
timers = [FPSBasedTimer(video_info.fps) for _ in zones]
for frame in frames_generator:
detections = model.predict(frame, threshold=confidence_threshold)
detections = detections[find_in_list(detections.class_id, classes)]
detections = detections.with_nms(threshold=iou_threshold)
detections = tracker.update_with_detections(detections)
annotated_frame = frame.copy()
for idx, zone in enumerate(zones):
annotated_frame = sv.draw_polygon(
scene=annotated_frame, polygon=zone.polygon, color=COLORS.by_idx(idx)
)
detections_in_zone = detections[zone.trigger(detections)]
time_in_zone = timers[idx].tick(detections_in_zone)
custom_color_lookup = np.full(detections_in_zone.class_id.shape, idx)
annotated_frame = COLOR_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
custom_color_lookup=custom_color_lookup,
)
labels = [
f"#{tracker_id} {int(time // 60):02d}:{int(time % 60):02d}"
for tracker_id, time in zip(detections_in_zone.tracker_id, time_in_zone)
]
annotated_frame = LABEL_ANNOTATOR.annotate(
scene=annotated_frame,
detections=detections_in_zone,
labels=labels,
custom_color_lookup=custom_color_lookup,
)
cv2.imshow("Processed Video", annotated_frame)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
cv2.destroyAllWindows()
if __name__ == "__main__":
from jsonargparse import auto_cli, set_parsing_settings
set_parsing_settings(parse_optionals_as_positionals=True)
auto_cli(main, as_positional=False)