Add video playback functionality to MainWindow and update app configuration
This commit is contained in:
@@ -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,8 +280,55 @@ 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)
|
||||||
|
if self.worker is not None:
|
||||||
self.worker.update_camera_config(camera_config)
|
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:
|
||||||
return
|
return
|
||||||
@@ -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})"
|
||||||
|
|||||||
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user