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 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.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QFileDialog,
QMainWindow,
QMessageBox,
QPushButton,
@@ -42,6 +43,10 @@ class MainWindow(QMainWindow):
self.fps_frame_count = 0
self.fps_last_time = time.monotonic()
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.video_recorder = VideoRecorder(self.config, self.app_config)
@@ -119,12 +124,19 @@ class MainWindow(QMainWindow):
)
toolbar_layout = QHBoxLayout(self.toolbar)
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.record_button = self._tool_button(QStyle.SP_MediaPlay, "Start/stop nagrywania")
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.record_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.record_button.clicked.connect(self.toggle_recording)
self.settings_button.clicked.connect(self.open_settings)
@@ -158,9 +170,10 @@ class MainWindow(QMainWindow):
def closeEvent(self, event: Any) -> None:
if self.video_recorder.is_recording:
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.worker.wait(2000)
self.detection_worker.wait(2000)
super().closeEvent(event)
@@ -223,6 +236,41 @@ class MainWindow(QMainWindow):
self.record_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
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:
dialog = SettingsDialog(self.config, self)
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:
self.config["camera"] = camera_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:
if not self.detecting:
@@ -277,10 +372,18 @@ class MainWindow(QMainWindow):
lines.append(f"Komunikat: {result.error}")
if result.confidence is not None:
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:
lines.append(f"Zamowienie: {result.parsed.order_number or '-'}")
lines.append(f"Kolor: {result.parsed.color_code or '-'}")
lines.append(f"Model: {result.parsed.product_model or '-'}")
color_score = _format_score(result.parsed.color_score)
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:
lines.append("")
lines.append(result.raw_text)
@@ -326,7 +429,7 @@ class MainWindow(QMainWindow):
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.rectangle(frame_bgr, (12, 12), (142, 46), (0, 0, 0), -1)
cv2.putText(
frame_bgr,
label,
@@ -344,3 +447,9 @@ def run_app(app_config: AppConfig) -> int:
window = MainWindow(app_config)
window.show()
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": {
"model_path": "models/best.pt",
"model_path": "models/best_v1.pt",
"confidence_threshold": 0.25,
"mode": "best",
"frame_stride": 5,
"frame_stride": 30,
"image_size": 640,
"device": "cpu"
},
"ocr": {
"enabled": true,
"engine": "paddle",
"language": "eng",
"tesseract_cmd": null,
"psm": 6,
"margin": 0,
"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": {
"photos_dir": "captures/photos",
@@ -45,13 +58,33 @@
"show_fps": true
},
"label_data": {
"model_min_score": 0.72,
"color_min_score": 0.72,
"models": [
"Regius",
"Duvell"
"Regius 6",
"Regius 7",
"Duvell 6",
"Duvell 7",
"Duvell Elite 6",
"Duvell Elite 7"
],
"colors": [
"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"
]
}
}