* VideoInfo.fps returns float instead of int Truncating the raw CAP_PROP_FPS value with int() causes timing drift for non-integer frame rates (23.976, 29.97, 59.94). Over a long video this accumulates into noticeable sync errors — e.g. 23 vs 23.976 drifts ~1s per minute of footage. Changes: - VideoInfo.fps type annotation: int -> float - from_video_path: int(video.get(CAP_PROP_FPS)) -> float(...) - ByteTrack.frame_rate type annotation: int -> float (already converts to int internally via max_time_lost = int(frame_rate / 30.0 * buffer)) - Tests: assert fps is float, add float_fps_video_path fixture at 23.976 * fix: update examples to cast float fps to int where required * fix: wrap long docstring line in FPSBasedTimer (ruff E501) * test: remove unused float_fps_video_path fixture --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
89 lines
2.8 KiB
Python
89 lines
2.8 KiB
Python
from datetime import datetime
|
|
|
|
import numpy as np
|
|
|
|
import supervision as sv
|
|
|
|
|
|
class FPSBasedTimer:
|
|
"""
|
|
A timer that calculates the duration each object has been detected based on frames
|
|
per second (FPS).
|
|
|
|
Attributes:
|
|
fps (float): The frame rate of the video stream, used to calculate
|
|
time durations.
|
|
frame_id (int): The current frame number in the sequence.
|
|
tracker_id2frame_id (Dict[int, int]): Maps each tracker's ID to the frame number
|
|
at which it was first detected.
|
|
"""
|
|
|
|
def __init__(self, fps: float = 30) -> None:
|
|
"""Initializes the FPSBasedTimer with the specified frames per second rate.
|
|
|
|
Args:
|
|
fps (float): The frame rate of the video stream. Defaults to 30.
|
|
"""
|
|
self.fps = fps
|
|
self.frame_id = 0
|
|
self.tracker_id2frame_id: dict[int, int] = {}
|
|
|
|
def tick(self, detections: sv.Detections) -> np.ndarray:
|
|
"""Processes the current frame, updating time durations for each tracker.
|
|
|
|
Args:
|
|
detections: The detections for the current frame, including tracker IDs.
|
|
|
|
Returns:
|
|
np.ndarray: Time durations (in seconds) for each detected tracker, since
|
|
their first detection.
|
|
"""
|
|
self.frame_id += 1
|
|
times = []
|
|
|
|
for tracker_id in detections.tracker_id:
|
|
self.tracker_id2frame_id.setdefault(tracker_id, self.frame_id)
|
|
|
|
start_frame_id = self.tracker_id2frame_id[tracker_id]
|
|
time_duration = (self.frame_id - start_frame_id) / self.fps
|
|
times.append(time_duration)
|
|
|
|
return np.array(times)
|
|
|
|
|
|
class ClockBasedTimer:
|
|
"""
|
|
A timer that calculates the duration each object has been detected based on the
|
|
system clock.
|
|
|
|
Attributes:
|
|
tracker_id2start_time (Dict[int, datetime]): Maps each tracker's ID to the
|
|
datetime when it was first detected.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initializes the ClockBasedTimer."""
|
|
self.tracker_id2start_time: dict[int, datetime] = {}
|
|
|
|
def tick(self, detections: sv.Detections) -> np.ndarray:
|
|
"""Processes the current frame, updating time durations for each tracker.
|
|
|
|
Args:
|
|
detections: The detections for the current frame, including tracker IDs.
|
|
|
|
Returns:
|
|
np.ndarray: Time durations (in seconds) for each detected tracker, since
|
|
their first detection.
|
|
"""
|
|
current_time = datetime.now()
|
|
times = []
|
|
|
|
for tracker_id in detections.tracker_id:
|
|
self.tracker_id2start_time.setdefault(tracker_id, current_time)
|
|
|
|
start_time = self.tracker_id2start_time[tracker_id]
|
|
time_duration = (current_time - start_time).total_seconds()
|
|
times.append(time_duration)
|
|
|
|
return np.array(times)
|