Refactor detection handling by separating DetectionWorker and integrating it into MainWindow

This commit is contained in:
2026-05-07 19:37:40 +02:00
parent 673cc64169
commit b65d0e2130
2 changed files with 73 additions and 32 deletions

View File

@@ -50,38 +50,20 @@ def rotate_frame(frame: np.ndarray, degrees: int) -> np.ndarray:
class CameraWorker(QThread): class CameraWorker(QThread):
frame_ready = Signal(object) frame_ready = Signal(object)
detection_ready = Signal(object)
camera_error = Signal(str) camera_error = Signal(str)
def __init__(self, config: dict[str, Any], app_config: Any) -> None: def __init__(self, config: dict[str, Any], app_config: Any) -> None:
super().__init__() super().__init__()
self.config = config self.config = config
self.app_config = app_config self.app_config = app_config
self.pipeline = DetectionPipeline(config, app_config)
self._running = threading.Event() self._running = threading.Event()
self._running.set() self._running.set()
self._detecting = False
self._accepted = False
self._frame_count = 0
self._capture: cv2.VideoCapture | None = None self._capture: cv2.VideoCapture | None = None
self._lock = threading.Lock() self._lock = threading.Lock()
def stop(self) -> None: def stop(self) -> None:
self._running.clear() self._running.clear()
@Slot()
def start_detection(self) -> None:
with self._lock:
self._detecting = True
self._accepted = False
self._frame_count = 0
@Slot()
def accept_detection(self) -> None:
with self._lock:
self._detecting = False
self._accepted = True
@Slot(dict) @Slot(dict)
def update_camera_config(self, camera_config: dict[str, Any]) -> None: def update_camera_config(self, camera_config: dict[str, Any]) -> None:
with self._lock: with self._lock:
@@ -115,7 +97,6 @@ class CameraWorker(QThread):
rotation_degrees = int(self.config["camera"].get("rotation_degrees", 0)) rotation_degrees = int(self.config["camera"].get("rotation_degrees", 0))
frame = rotate_frame(frame, rotation_degrees) frame = rotate_frame(frame, rotation_degrees)
self.frame_ready.emit(frame) self.frame_ready.emit(frame)
self._maybe_detect(frame)
finally: finally:
capture.release() capture.release()
self._capture = None self._capture = None
@@ -131,15 +112,53 @@ class CameraWorker(QThread):
continue continue
capture.set(CV_CAP_PROPS[name], float(value)) capture.set(CV_CAP_PROPS[name], float(value))
def _maybe_detect(self, frame: np.ndarray) -> None:
class DetectionWorker(QThread):
detection_ready = Signal(object)
def __init__(self, config: dict[str, Any], app_config: Any) -> None:
super().__init__()
self.config = config
self.app_config = app_config
self.pipeline: DetectionPipeline | None = None
self._running = threading.Event()
self._running.set()
self._pending = threading.Event()
self._lock = threading.Lock()
self._pending_frame: np.ndarray | None = None
self._busy = False
def stop(self) -> None:
self._running.clear()
self._pending.set()
def request_detection(self, frame: np.ndarray) -> bool:
with self._lock: with self._lock:
detecting = self._detecting and not self._accepted if self._busy or self._pending_frame is not None:
frame_stride = max(1, int(self.config["detection"].get("frame_stride", 5))) return False
self._frame_count += 1 self._pending_frame = frame.copy()
should_detect = detecting and self._frame_count % frame_stride == 0 self._pending.set()
return True
if not should_detect: def run(self) -> None:
return while self._running.is_set():
self._pending.wait(0.2)
if not self._running.is_set():
break
result: DetectionResult = self.pipeline.process(frame) with self._lock:
self.detection_ready.emit(result) frame = self._pending_frame
self._pending_frame = None
self._pending.clear()
if frame is None:
continue
self._busy = True
try:
if self.pipeline is None:
self.pipeline = DetectionPipeline(self.config, self.app_config)
result: DetectionResult = self.pipeline.process(frame)
self.detection_ready.emit(result)
finally:
with self._lock:
self._busy = False

View File

@@ -21,7 +21,7 @@ from PySide6.QtWidgets import (
QStyle, QStyle,
) )
from app.camera import CameraWorker from app.camera import CameraWorker, DetectionWorker
from app.config import AppConfig from app.config import AppConfig
from app.detection import DetectionResult from app.detection import DetectionResult
from app.media import MediaStore, VideoRecorder from app.media import MediaStore, VideoRecorder
@@ -36,6 +36,8 @@ class MainWindow(QMainWindow):
self.last_frame: np.ndarray | None = None self.last_frame: np.ndarray | None = None
self.overlay_result: DetectionResult | None = None self.overlay_result: DetectionResult | None = None
self.last_detection: DetectionResult | None = None self.last_detection: DetectionResult | None = None
self.detecting = False
self.detection_frame_count = 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)
@@ -45,10 +47,13 @@ class MainWindow(QMainWindow):
self.worker = CameraWorker(self.config, self.app_config) self.worker = CameraWorker(self.config, self.app_config)
self.worker.frame_ready.connect(self.on_frame_ready) self.worker.frame_ready.connect(self.on_frame_ready)
self.worker.detection_ready.connect(self.on_detection_ready)
self.worker.camera_error.connect(self.on_camera_error) self.worker.camera_error.connect(self.on_camera_error)
self.worker.start() self.worker.start()
self.detection_worker = DetectionWorker(self.config, self.app_config)
self.detection_worker.detection_ready.connect(self.on_detection_ready)
self.detection_worker.start()
def _build_ui(self) -> None: def _build_ui(self) -> None:
self.stage = QWidget() self.stage = QWidget()
self.setCentralWidget(self.stage) self.setCentralWidget(self.stage)
@@ -150,7 +155,9 @@ class MainWindow(QMainWindow):
if self.video_recorder.is_recording: if self.video_recorder.is_recording:
self.video_recorder.stop(self.current_metadata("video")) self.video_recorder.stop(self.current_metadata("video"))
self.worker.stop() self.worker.stop()
self.detection_worker.stop()
self.worker.wait(2000) self.worker.wait(2000)
self.detection_worker.wait(2000)
super().closeEvent(event) super().closeEvent(event)
@Slot(object) @Slot(object)
@@ -158,10 +165,13 @@ class MainWindow(QMainWindow):
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)
self._maybe_request_detection(frame)
self._show_frame(frame) self._show_frame(frame)
@Slot(object) @Slot(object)
def on_detection_ready(self, result: DetectionResult) -> None: def on_detection_ready(self, result: DetectionResult) -> None:
if not self.detecting:
return
self.last_detection = result self.last_detection = result
self.overlay_result = result if result.xyxy else None self.overlay_result = result if result.xyxy else None
self._update_result_text(result) self._update_result_text(result)
@@ -172,11 +182,12 @@ class MainWindow(QMainWindow):
def start_detection(self) -> None: def start_detection(self) -> None:
self.overlay_result = None self.overlay_result = None
self.detecting = True
self.detection_frame_count = 0
self.result_text.setPlainText("Wykrywanie...") self.result_text.setPlainText("Wykrywanie...")
self.worker.start_detection()
def accept_detection(self) -> None: def accept_detection(self) -> None:
self.worker.accept_detection() self.detecting = False
self.overlay_result = None self.overlay_result = None
if self.last_detection: if self.last_detection:
self._update_result_text(self.last_detection, accepted=True) self._update_result_text(self.last_detection, accepted=True)
@@ -218,6 +229,17 @@ class MainWindow(QMainWindow):
self.app_config.save(self.config) self.app_config.save(self.config)
self.worker.update_camera_config(camera_config) self.worker.update_camera_config(camera_config)
def _maybe_request_detection(self, frame: np.ndarray) -> None:
if not self.detecting:
return
frame_stride = max(1, int(self.config["detection"].get("frame_stride", 5)))
self.detection_frame_count += 1
if self.detection_frame_count % frame_stride != 0:
return
self.detection_worker.request_detection(frame)
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,