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):
frame_ready = Signal(object)
detection_ready = Signal(object)
camera_error = Signal(str)
def __init__(self, config: dict[str, Any], app_config: Any) -> None:
super().__init__()
self.config = config
self.app_config = app_config
self.pipeline = DetectionPipeline(config, app_config)
self._running = threading.Event()
self._running.set()
self._detecting = False
self._accepted = False
self._frame_count = 0
self._capture: cv2.VideoCapture | None = None
self._lock = threading.Lock()
def stop(self) -> None:
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)
def update_camera_config(self, camera_config: dict[str, Any]) -> None:
with self._lock:
@@ -115,7 +97,6 @@ class CameraWorker(QThread):
rotation_degrees = int(self.config["camera"].get("rotation_degrees", 0))
frame = rotate_frame(frame, rotation_degrees)
self.frame_ready.emit(frame)
self._maybe_detect(frame)
finally:
capture.release()
self._capture = None
@@ -131,15 +112,53 @@ class CameraWorker(QThread):
continue
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:
detecting = self._detecting and not self._accepted
frame_stride = max(1, int(self.config["detection"].get("frame_stride", 5)))
self._frame_count += 1
should_detect = detecting and self._frame_count % frame_stride == 0
if self._busy or self._pending_frame is not None:
return False
self._pending_frame = frame.copy()
self._pending.set()
return True
if not should_detect:
return
def run(self) -> None:
while self._running.is_set():
self._pending.wait(0.2)
if not self._running.is_set():
break
result: DetectionResult = self.pipeline.process(frame)
self.detection_ready.emit(result)
with self._lock:
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,
)
from app.camera import CameraWorker
from app.camera import CameraWorker, DetectionWorker
from app.config import AppConfig
from app.detection import DetectionResult
from app.media import MediaStore, VideoRecorder
@@ -36,6 +36,8 @@ class MainWindow(QMainWindow):
self.last_frame: np.ndarray | None = None
self.overlay_result: 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.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.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.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:
self.stage = QWidget()
self.setCentralWidget(self.stage)
@@ -150,7 +155,9 @@ class MainWindow(QMainWindow):
if self.video_recorder.is_recording:
self.video_recorder.stop(self.current_metadata("video"))
self.worker.stop()
self.detection_worker.stop()
self.worker.wait(2000)
self.detection_worker.wait(2000)
super().closeEvent(event)
@Slot(object)
@@ -158,10 +165,13 @@ class MainWindow(QMainWindow):
self.last_frame = frame.copy()
if self.video_recorder.is_recording:
self.video_recorder.write(frame)
self._maybe_request_detection(frame)
self._show_frame(frame)
@Slot(object)
def on_detection_ready(self, result: DetectionResult) -> None:
if not self.detecting:
return
self.last_detection = result
self.overlay_result = result if result.xyxy else None
self._update_result_text(result)
@@ -172,11 +182,12 @@ class MainWindow(QMainWindow):
def start_detection(self) -> None:
self.overlay_result = None
self.detecting = True
self.detection_frame_count = 0
self.result_text.setPlainText("Wykrywanie...")
self.worker.start_detection()
def accept_detection(self) -> None:
self.worker.accept_detection()
self.detecting = False
self.overlay_result = None
if self.last_detection:
self._update_result_text(self.last_detection, accepted=True)
@@ -218,6 +229,17 @@ class MainWindow(QMainWindow):
self.app_config.save(self.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]:
return {
"media_type": media_type,