ウォームアップ時のFPSを平均FPSから除外するように修正し,平均FPSを総フレーム数/経過時間で計算するように変更

This commit is contained in:
agomizi1cm committed 2026-10-02 23:25:01 +09:00
1 parent 6d6b6a0ede
commit acd08951c8
4 files changed
+64 -18

No files matched your search

+1
View File
@@ -337,6 +337,7 @@ performance:
skip_frames: 0 skip_frames: 0
fps_average_window: 30 fps_average_window: 30
warn_fps_threshold: 10.0 warn_fps_threshold: 10.0
warmup_frames: 5
debug: debug:
enabled: false enabled: false
show_raw_model_output: false show_raw_model_output: false
+4 -4
View File
@@ -143,7 +143,7 @@ class Application:
person_state_manager: PersonStateManager | None = None person_state_manager: PersonStateManager | None = None
unified_builder: UnifiedEventBuilder | None = None unified_builder: UnifiedEventBuilder | None = None
analysis_builder: AnalysisTableBuilder | None = None analysis_builder: AnalysisTableBuilder | None = None
fps = FPSCounter(self.config.performance.fps_average_window) fps = FPSCounter(self.config.performance.fps_average_window, self.config.performance.warmup_frames)
visualizer = Visualizer(self.config.display, self.config.performance.warn_fps_threshold, self.config.performance.fps_average_window) visualizer = Visualizer(self.config.display, self.config.performance.warn_fps_threshold, self.config.performance.fps_average_window)
frame_number, processed_frames = 0, 0 frame_number, processed_frames = 0, 0
start = time.perf_counter() start = time.perf_counter()
@@ -277,10 +277,10 @@ class Application:
should_infer = (frame_number - 1) % (self.config.performance.skip_frames + 1) == 0 should_infer = (frame_number - 1) % (self.config.performance.skip_frames + 1) == 0
detections: list[Detection] = detector.detect(frame, region) if should_infer else [] detections: list[Detection] = detector.detect(frame, region) if should_infer else []
pre_info = FrameInfo(frame_number, datetime.now().astimezone().isoformat(timespec="milliseconds"), pre_info = FrameInfo(frame_number, datetime.now().astimezone().isoformat(timespec="milliseconds"),
time.perf_counter() - start - paused_total, width, height, fps.average_fps) time.perf_counter() - start - paused_total, width, height, fps.recent_average_fps)
tracked = tracker.update(detections, frame, pre_info) if tracker is not None else [] tracked = tracker.update(detections, frame, pre_info) if tracker is not None else []
fps.tick() fps.tick()
measured = fps.average_fps measured = fps.recent_average_fps
now = datetime.now().astimezone() now = datetime.now().astimezone()
info = FrameInfo(frame_number, now.isoformat(timespec="milliseconds"), time.perf_counter() - start - paused_total, width, height, measured) info = FrameInfo(frame_number, now.isoformat(timespec="milliseconds"), time.perf_counter() - start - paused_total, width, height, measured)
events = track_manager.update(tracked, info) if track_manager is not None else [] events = track_manager.update(tracked, info) if track_manager is not None else []
@@ -537,7 +537,7 @@ class Application:
self._writer.release() self._writer.release()
cv2.destroyAllWindows() cv2.destroyAllWindows()
elapsed = time.perf_counter() - start elapsed = time.perf_counter() - start
LOGGER.info("Run summary: frames=%d elapsed=%.2fs average=%.2f FPS", processed_frames, elapsed, processed_frames / elapsed if elapsed else 0.0) LOGGER.info("Run summary: frames=%d elapsed=%.2fs average=%.2f FPS", processed_frames, elapsed, fps.average_fps)
@staticmethod @staticmethod
def _tracking_counters(manager: TrackManager | None) -> dict[str, int] | None: def _tracking_counters(manager: TrackManager | None) -> dict[str, int] | None:
+3
View File
@@ -435,6 +435,7 @@ class PerformanceSettings:
skip_frames: int skip_frames: int
fps_average_window: int fps_average_window: int
warn_fps_threshold: float warn_fps_threshold: float
warmup_frames: float
@dataclass(frozen=True, slots=True) @dataclass(frozen=True, slots=True)
@@ -549,6 +550,8 @@ def _validate(config: AppConfig) -> None:
raise ConfigurationError("detector image_size/max_detections must be positive and class ID non-negative") raise ConfigurationError("detector image_size/max_detections must be positive and class ID non-negative")
if config.performance.fps_average_window <= 0 or config.performance.skip_frames < 0: if config.performance.fps_average_window <= 0 or config.performance.skip_frames < 0:
raise ConfigurationError("performance window must be positive and skip_frames non-negative") raise ConfigurationError("performance window must be positive and skip_frames non-negative")
if config.performance.warmup_frames < 0:
raise ConfigurationError("performance warmup_frames must be non-negative")
if config.display.resize_scale <= 0: if config.display.resize_scale <= 0:
raise ConfigurationError("display.resize_scale must be positive") raise ConfigurationError("display.resize_scale must be positive")
if config.display.trajectory_max_points <= 0: if config.display.trajectory_max_points <= 0:
+56 -14
View File
@@ -5,25 +5,56 @@ from collections import deque
class FPSCounter: class FPSCounter:
"""Calculate instantaneous and moving-average FPS.""" """Calculate instantaneous, overall-average and recent-average FPS.
def __init__(self, average_window: int) -> None: - current_fps: 直近1フレームの瞬間FPS(表示用)
- average_fps: ウォームアップ後の「総フレーム数 ÷ 経過時間」
- recent_average_fps: 直近 average_window フレームの「フレーム数 ÷ 経過時間」
"""
def __init__(self, average_window: int, warmup_frames: int) -> None:
if average_window <= 0: if average_window <= 0:
raise ValueError("average_window must be positive") raise ValueError("average_window must be positive")
self._samples: deque[float] = deque(maxlen=average_window) if warmup_frames < 0:
raise ValueError("warmup_frames must be non-negative")
self._average_window = average_window
# 起動直後の数フレームは初期化処理で遅くなるため平均から除外する
self._warmup_frames = warmup_frames
# 計測の基準となる tick 番号(1回目の tick は基準時刻の記録のみ)
self._baseline_tick = max(warmup_frames, 1)
self._reset_state()
def _reset_state(self) -> None:
self._last_time: float | None = None self._last_time: float | None = None
self._current_fps = 0.0 self._current_fps = 0.0
self._tick_count = 0
self._start_time: float | None = None
self._frames = 0 # 基準時刻以降に経過したフレーム間隔の数
# 窓内の平均用タイムスタンプ(N個の間隔に N+1 点必要)
self._timestamps: deque[float] = deque(maxlen=self._average_window + 1)
def tick(self) -> float: def tick(self) -> float:
now = time.perf_counter() now = time.perf_counter()
self._tick_count += 1
if self._last_time is None: if self._last_time is None:
self._last_time = now self._last_time = now
return 0.0 self._current_fps = 0.0
elapsed = now - self._last_time else:
self._last_time = now elapsed = now - self._last_time
self._current_fps = 1.0 / elapsed if elapsed > 0.0 else 0.0 self._last_time = now
if self._current_fps > 0.0: self._current_fps = 1.0 / elapsed if elapsed > 0.0 else 0.0
self._samples.append(self._current_fps)
# ウォームアップ期間中は表示用の瞬間FPSのみ更新し、平均には含めない
if self._tick_count == self._baseline_tick:
self._start_time = now
self._frames = 0
self._timestamps.clear()
self._timestamps.append(now)
elif self._tick_count > self._baseline_tick:
self._frames += 1
self._timestamps.append(now)
return self._current_fps return self._current_fps
@property @property
@@ -32,13 +63,24 @@ class FPSCounter:
@property @property
def average_fps(self) -> float: def average_fps(self) -> float:
return sum(self._samples) / len(self._samples) if self._samples else 0.0 """ウォームアップ後の総フレーム数 ÷ 経過時間。"""
if self._start_time is None or self._last_time is None or self._frames == 0:
return 0.0
elapsed = self._last_time - self._start_time
return self._frames / elapsed if elapsed > 0.0 else 0.0
@property
def recent_average_fps(self) -> float:
"""直近 average_window フレームのフレーム数 ÷ 経過時間。"""
if len(self._timestamps) < 2:
return 0.0
elapsed = self._timestamps[-1] - self._timestamps[0]
return (len(self._timestamps) - 1) / elapsed if elapsed > 0.0 else 0.0
@property @property
def sample_count(self) -> int: def sample_count(self) -> int:
return len(self._samples) """平均に含まれているフレーム数(ウォームアップ除外後)。"""
return self._frames
def reset(self) -> None: def reset(self) -> None:
self._samples.clear() self._reset_state()
self._last_time = None
self._current_fps = 0.0