Add FPS display feature and update configuration for display settings

This commit is contained in:
2026-05-07 19:56:04 +02:00
parent b65d0e2130
commit d117be5eec
4 changed files with 66 additions and 4 deletions

View File

@@ -53,6 +53,9 @@ DEFAULT_CONFIG: dict[str, Any] = {
"video_extension": "mp4", "video_extension": "mp4",
"video_codec": "mp4v", "video_codec": "mp4v",
}, },
"display": {
"show_fps": True,
},
"label_data": {"models": ["Regius", "Duvell"], "colors": ["T-NF-BLK-OUT-BST-G", "T-BLK-G"]}, "label_data": {"models": ["Regius", "Duvell"], "colors": ["T-NF-BLK-OUT-BST-G", "T-BLK-G"]},
} }

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import time
from datetime import datetime from datetime import datetime
from typing import Any from typing import Any
@@ -38,6 +39,9 @@ class MainWindow(QMainWindow):
self.last_detection: DetectionResult | None = None self.last_detection: DetectionResult | None = None
self.detecting = False self.detecting = False
self.detection_frame_count = 0 self.detection_frame_count = 0
self.fps_frame_count = 0
self.fps_last_time = time.monotonic()
self.display_fps = 0.0
self.media_store = MediaStore(self.config, self.app_config) self.media_store = MediaStore(self.config, self.app_config)
self.video_recorder = VideoRecorder(self.config, self.app_config) self.video_recorder = VideoRecorder(self.config, self.app_config)
@@ -162,6 +166,7 @@ class MainWindow(QMainWindow):
@Slot(object) @Slot(object)
def on_frame_ready(self, frame: np.ndarray) -> None: def on_frame_ready(self, frame: np.ndarray) -> None:
self._update_fps()
self.last_frame = frame.copy() self.last_frame = frame.copy()
if self.video_recorder.is_recording: if self.video_recorder.is_recording:
self.video_recorder.write(frame) self.video_recorder.write(frame)
@@ -240,6 +245,17 @@ class MainWindow(QMainWindow):
self.detection_worker.request_detection(frame) self.detection_worker.request_detection(frame)
def _update_fps(self) -> None:
self.fps_frame_count += 1
now = time.monotonic()
elapsed = now - self.fps_last_time
if elapsed < 1.0:
return
self.display_fps = self.fps_frame_count / elapsed
self.fps_frame_count = 0
self.fps_last_time = now
def current_metadata(self, media_type: str) -> dict[str, Any]: def current_metadata(self, media_type: str) -> dict[str, Any]:
return { return {
"media_type": media_type, "media_type": media_type,
@@ -274,6 +290,8 @@ class MainWindow(QMainWindow):
display_frame = frame_bgr.copy() display_frame = frame_bgr.copy()
if self.overlay_result is not None: if self.overlay_result is not None:
self._draw_detection(display_frame, self.overlay_result) self._draw_detection(display_frame, self.overlay_result)
if self.config.get("display", {}).get("show_fps", True):
self._draw_fps(display_frame)
frame_rgb = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB) frame_rgb = cv2.cvtColor(display_frame, cv2.COLOR_BGR2RGB)
h, w, channels = frame_rgb.shape h, w, channels = frame_rgb.shape
@@ -306,6 +324,20 @@ class MainWindow(QMainWindow):
cv2.LINE_AA, cv2.LINE_AA,
) )
def _draw_fps(self, frame_bgr: np.ndarray) -> None:
label = f"FPS: {self.display_fps:.1f}"
cv2.rectangle(frame_bgr, (12, 12), (122, 46), (0, 0, 0), -1)
cv2.putText(
frame_bgr,
label,
(20, 36),
cv2.FONT_HERSHEY_SIMPLEX,
0.7,
(255, 255, 255),
2,
cv2.LINE_AA,
)
def run_app(app_config: AppConfig) -> int: def run_app(app_config: AppConfig) -> int:
app = QApplication([]) app = QApplication([])

View File

@@ -1,6 +1,7 @@
from __future__ import annotations from __future__ import annotations
import json import json
import time
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Any from typing import Any
@@ -54,6 +55,10 @@ class VideoRecorder:
self.path: Path | None = None self.path: Path | None = None
self.writer: cv2.VideoWriter | None = None self.writer: cv2.VideoWriter | None = None
self.started_at: str | None = None self.started_at: str | None = None
self.started_monotonic: float | None = None
self.fps = 0.0
self.frames_written = 0
self.last_frame: np.ndarray | None = None
@property @property
def is_recording(self) -> bool: def is_recording(self) -> bool:
@@ -66,24 +71,37 @@ class VideoRecorder:
capture_cfg = self.config["capture"] capture_cfg = self.config["capture"]
self.path = MediaStore(self.config, self.app_config).video_path() self.path = MediaStore(self.config, self.app_config).video_path()
h, w = frame_bgr.shape[:2] h, w = frame_bgr.shape[:2]
fps = float(self.config["camera"].get("fps", 30)) self.fps = float(self.config["camera"].get("fps", 30))
codec = str(capture_cfg.get("video_codec", "mp4v")) codec = str(capture_cfg.get("video_codec", "mp4v"))
fourcc = cv2.VideoWriter_fourcc(*codec[:4]) fourcc = cv2.VideoWriter_fourcc(*codec[:4])
self.writer = cv2.VideoWriter(str(self.path), fourcc, fps, (w, h)) self.writer = cv2.VideoWriter(str(self.path), fourcc, self.fps, (w, h))
if not self.writer.isOpened(): if not self.writer.isOpened():
self.writer = None self.writer = None
raise RuntimeError("Nie mozna uruchomic zapisu wideo") raise RuntimeError("Nie mozna uruchomic zapisu wideo")
self.started_at = datetime.now().isoformat(timespec="seconds") self.started_at = datetime.now().isoformat(timespec="seconds")
self.started_monotonic = time.monotonic()
self.frames_written = 0
self.last_frame = None
self.write(frame_bgr) self.write(frame_bgr)
return self.path return self.path
def write(self, frame_bgr: np.ndarray) -> None: def write(self, frame_bgr: np.ndarray) -> None:
if self.writer is not None: if self.writer is None or self.started_monotonic is None:
self.writer.write(frame_bgr) return
elapsed = max(0.0, time.monotonic() - self.started_monotonic)
target_frames = max(1, int(elapsed * self.fps) + 1)
frame = frame_bgr.copy()
while self.frames_written < target_frames:
self.writer.write(frame)
self.frames_written += 1
self.last_frame = frame
def stop(self, metadata: dict[str, Any]) -> Path | None: def stop(self, metadata: dict[str, Any]) -> Path | None:
if self.writer is None: if self.writer is None:
return None return None
if self.last_frame is not None:
self.write(self.last_frame)
self.writer.release() self.writer.release()
self.writer = None self.writer = None
path = self.path path = self.path
@@ -93,9 +111,15 @@ class VideoRecorder:
"recording": { "recording": {
"started_at": self.started_at, "started_at": self.started_at,
"stopped_at": datetime.now().isoformat(timespec="seconds"), "stopped_at": datetime.now().isoformat(timespec="seconds"),
"fps": self.fps,
"frames_written": self.frames_written,
}, },
} }
write_metadata(path, metadata) write_metadata(path, metadata)
self.path = None self.path = None
self.started_at = None self.started_at = None
self.started_monotonic = None
self.fps = 0.0
self.frames_written = 0
self.last_frame = None
return path return path

View File

@@ -41,6 +41,9 @@
"video_extension": "mp4", "video_extension": "mp4",
"video_codec": "mp4v" "video_codec": "mp4v"
}, },
"display": {
"show_fps": true
},
"label_data": { "label_data": {
"models": [ "models": [
"Regius", "Regius",