Add video playback functionality to MainWindow and update app configuration

This commit is contained in:
2026-05-08 07:10:46 +02:00
parent 30c508287a
commit 711aee3334
2 changed files with 155 additions and 13 deletions

View File

@@ -6,12 +6,13 @@ from typing import Any
import cv2 import cv2
import numpy as np import numpy as np
from PySide6.QtCore import Qt, Slot from PySide6.QtCore import Qt, QTimer, Slot
from PySide6.QtGui import QAction, QImage, QPixmap from PySide6.QtGui import QAction, QImage, QPixmap
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QApplication, QApplication,
QHBoxLayout, QHBoxLayout,
QLabel, QLabel,
QFileDialog,
QMainWindow, QMainWindow,
QMessageBox, QMessageBox,
QPushButton, QPushButton,
@@ -42,6 +43,10 @@ class MainWindow(QMainWindow):
self.fps_frame_count = 0 self.fps_frame_count = 0
self.fps_last_time = time.monotonic() self.fps_last_time = time.monotonic()
self.display_fps = 0.0 self.display_fps = 0.0
self.video_capture: cv2.VideoCapture | None = None
self.video_timer = QTimer(self)
self.video_timer.timeout.connect(self._read_video_frame)
self.video_playing = False
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)
@@ -119,12 +124,19 @@ class MainWindow(QMainWindow):
) )
toolbar_layout = QHBoxLayout(self.toolbar) toolbar_layout = QHBoxLayout(self.toolbar)
toolbar_layout.setContentsMargins(8, 6, 8, 6) toolbar_layout.setContentsMargins(8, 6, 8, 6)
self.load_video_button = self._tool_button(QStyle.SP_DirOpenIcon, "Wczytaj film")
self.video_play_button = self._tool_button(QStyle.SP_MediaPlay, "Play/pauza filmu")
self.photo_button = self._tool_button(QStyle.SP_DialogSaveButton, "Zrob zdjecie") self.photo_button = self._tool_button(QStyle.SP_DialogSaveButton, "Zrob zdjecie")
self.record_button = self._tool_button(QStyle.SP_MediaPlay, "Start/stop nagrywania") self.record_button = self._tool_button(QStyle.SP_MediaPlay, "Start/stop nagrywania")
self.settings_button = self._tool_button(QStyle.SP_FileDialogDetailedView, "Ustawienia obrazu") self.settings_button = self._tool_button(QStyle.SP_FileDialogDetailedView, "Ustawienia obrazu")
toolbar_layout.addWidget(self.load_video_button)
toolbar_layout.addWidget(self.video_play_button)
toolbar_layout.addWidget(self.photo_button) toolbar_layout.addWidget(self.photo_button)
toolbar_layout.addWidget(self.record_button) toolbar_layout.addWidget(self.record_button)
toolbar_layout.addWidget(self.settings_button) toolbar_layout.addWidget(self.settings_button)
self.video_play_button.setEnabled(False)
self.load_video_button.clicked.connect(self.load_video)
self.video_play_button.clicked.connect(self.toggle_video_playback)
self.photo_button.clicked.connect(self.take_photo) self.photo_button.clicked.connect(self.take_photo)
self.record_button.clicked.connect(self.toggle_recording) self.record_button.clicked.connect(self.toggle_recording)
self.settings_button.clicked.connect(self.open_settings) self.settings_button.clicked.connect(self.open_settings)
@@ -158,9 +170,10 @@ class MainWindow(QMainWindow):
def closeEvent(self, event: Any) -> None: def closeEvent(self, event: Any) -> None:
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.video_timer.stop()
self._close_video_capture()
self._stop_camera_worker()
self.detection_worker.stop() self.detection_worker.stop()
self.worker.wait(2000)
self.detection_worker.wait(2000) self.detection_worker.wait(2000)
super().closeEvent(event) super().closeEvent(event)
@@ -223,6 +236,41 @@ class MainWindow(QMainWindow):
self.record_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.record_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
self.statusBar().showMessage(f"Nagrywanie: {path}", 5000) self.statusBar().showMessage(f"Nagrywanie: {path}", 5000)
def load_video(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self,
"Wczytaj film",
"",
"Filmy (*.mp4 *.avi *.mov *.mkv *.m4v);;Wszystkie pliki (*)",
)
if not path:
return
capture = cv2.VideoCapture(path)
if not capture.isOpened():
QMessageBox.warning(self, "Film", "Nie mozna otworzyc pliku wideo")
capture.release()
return
if self.video_recorder.is_recording:
self.video_recorder.stop(self.current_metadata("video"))
self.record_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self._stop_camera_worker()
self._close_video_capture()
self.video_capture = capture
self.video_play_button.setEnabled(True)
self._set_video_playing(False)
self.overlay_result = None
self.last_detection = None
self.result_text.setPlainText(f"Wczytano film: {path}")
self._read_video_frame()
def toggle_video_playback(self) -> None:
if self.video_capture is None:
return
self._set_video_playing(not self.video_playing)
def open_settings(self) -> None: def open_settings(self) -> None:
dialog = SettingsDialog(self.config, self) dialog = SettingsDialog(self.config, self)
dialog.settings_saved.connect(self.save_camera_settings) dialog.settings_saved.connect(self.save_camera_settings)
@@ -232,7 +280,54 @@ class MainWindow(QMainWindow):
def save_camera_settings(self, camera_config: dict[str, Any]) -> None: def save_camera_settings(self, camera_config: dict[str, Any]) -> None:
self.config["camera"] = camera_config self.config["camera"] = camera_config
self.app_config.save(self.config) self.app_config.save(self.config)
self.worker.update_camera_config(camera_config) if self.worker is not None:
self.worker.update_camera_config(camera_config)
def _read_video_frame(self) -> None:
if self.video_capture is None:
return
ok, frame = self.video_capture.read()
if not ok or frame is None:
self._set_video_playing(False)
self.video_capture.set(cv2.CAP_PROP_POS_FRAMES, 0)
self.statusBar().showMessage("Koniec filmu", 3000)
return
self.on_frame_ready(frame)
def _set_video_playing(self, playing: bool) -> None:
self.video_playing = playing
if self.video_capture is None:
self.video_timer.stop()
self.video_play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.video_play_button.setEnabled(False)
return
if playing:
fps = self.video_capture.get(cv2.CAP_PROP_FPS)
if fps <= 0:
fps = float(self.config["camera"].get("fps", 30))
interval_ms = max(1, int(round(1000 / fps)))
self.video_timer.start(interval_ms)
self.video_play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.video_timer.stop()
self.video_play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
def _close_video_capture(self) -> None:
self._set_video_playing(False)
if self.video_capture is not None:
self.video_capture.release()
self.video_capture = None
self.video_play_button.setEnabled(False)
def _stop_camera_worker(self) -> None:
if self.worker is None:
return
self.worker.stop()
self.worker.wait(2000)
self.worker = None
def _maybe_request_detection(self, frame: np.ndarray) -> None: def _maybe_request_detection(self, frame: np.ndarray) -> None:
if not self.detecting: if not self.detecting:
@@ -277,10 +372,18 @@ class MainWindow(QMainWindow):
lines.append(f"Komunikat: {result.error}") lines.append(f"Komunikat: {result.error}")
if result.confidence is not None: if result.confidence is not None:
lines.append(f"YOLO confidence: {result.confidence:.3f}") lines.append(f"YOLO confidence: {result.confidence:.3f}")
if result.ocr_engine:
lines.append(f"OCR: {result.ocr_engine}")
if result.ocr_confidence is not None:
lines.append(f"OCR confidence: {result.ocr_confidence:.3f}")
if result.ocr_elapsed_ms is not None:
lines.append(f"OCR czas: {result.ocr_elapsed_ms:.0f} ms")
if result.parsed: if result.parsed:
lines.append(f"Zamowienie: {result.parsed.order_number or '-'}") lines.append(f"Zamowienie: {result.parsed.order_number or '-'}")
lines.append(f"Kolor: {result.parsed.color_code or '-'}") color_score = _format_score(result.parsed.color_score)
lines.append(f"Model: {result.parsed.product_model or '-'}") model_score = _format_score(result.parsed.product_model_score)
lines.append(f"Kolor: {result.parsed.color_code or '-'}{color_score}")
lines.append(f"Model: {result.parsed.product_model or '-'}{model_score}")
if result.raw_text: if result.raw_text:
lines.append("") lines.append("")
lines.append(result.raw_text) lines.append(result.raw_text)
@@ -326,7 +429,7 @@ class MainWindow(QMainWindow):
def _draw_fps(self, frame_bgr: np.ndarray) -> None: def _draw_fps(self, frame_bgr: np.ndarray) -> None:
label = f"FPS: {self.display_fps:.1f}" label = f"FPS: {self.display_fps:.1f}"
cv2.rectangle(frame_bgr, (12, 12), (122, 46), (0, 0, 0), -1) cv2.rectangle(frame_bgr, (12, 12), (142, 46), (0, 0, 0), -1)
cv2.putText( cv2.putText(
frame_bgr, frame_bgr,
label, label,
@@ -344,3 +447,9 @@ def run_app(app_config: AppConfig) -> int:
window = MainWindow(app_config) window = MainWindow(app_config)
window.show() window.show()
return app.exec() return app.exec()
def _format_score(score: float | None) -> str:
if score is None:
return ""
return f" ({score:.2f})"

View File

@@ -20,19 +20,32 @@
} }
}, },
"detection": { "detection": {
"model_path": "models/best.pt", "model_path": "models/best_v1.pt",
"confidence_threshold": 0.25, "confidence_threshold": 0.25,
"mode": "best", "mode": "best",
"frame_stride": 5, "frame_stride": 30,
"image_size": 640, "image_size": 640,
"device": "cpu" "device": "cpu"
}, },
"ocr": { "ocr": {
"enabled": true, "enabled": true,
"engine": "paddle",
"language": "eng", "language": "eng",
"tesseract_cmd": null, "tesseract_cmd": null,
"psm": 6,
"margin": 0,
"threshold": true, "threshold": true,
"scale": 2.0 "paddle_threshold": false,
"scale": 2.0,
"config": "",
"use_angle_cls": true,
"paddle": {
"enable_mkldnn": false,
"lang": "en",
"use_doc_orientation_classify": false,
"use_doc_unwarping": false,
"use_textline_orientation": false
}
}, },
"capture": { "capture": {
"photos_dir": "captures/photos", "photos_dir": "captures/photos",
@@ -45,13 +58,33 @@
"show_fps": true "show_fps": true
}, },
"label_data": { "label_data": {
"model_min_score": 0.72,
"color_min_score": 0.72,
"models": [ "models": [
"Regius", "Regius 6",
"Duvell" "Regius 7",
"Duvell 6",
"Duvell 7",
"Duvell Elite 6",
"Duvell Elite 7"
], ],
"colors": [ "colors": [
"T-NF-BLK-OUT-BST-G", "T-NF-BLK-OUT-BST-G",
"T-BLK-G" "T-BLK-G",
"T-BLK-S",
"T-BLK-M",
"M-BLK-G",
"M-BLK-S",
"M-BLK-M",
"T-CST-G",
"T-CST-S",
"T-CST-M",
"T-ANTIQUE-G",
"T-ANTIQUE-S",
"T-ANTIQUE-M",
"T-NAT-G",
"T-NAT-S",
"T-NAT-M"
] ]
} }
} }