148
app/camera.py
148
app/camera.py
@@ -1,148 +0,0 @@
|
|||||||
from PySide6.QtCore import QThread, QTimer, QMutex, QWaitCondition, Signal, QObject, QElapsedTimer
|
|
||||||
import cv2
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from dataclasses import dataclass
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class VideoMetrics:
|
|
||||||
frames_processed: int
|
|
||||||
frames_dropped: int
|
|
||||||
last_frame_time: float
|
|
||||||
last_cap_time: float
|
|
||||||
fps_average: float
|
|
||||||
fps_last_time: float
|
|
||||||
fps_frame_count: float
|
|
||||||
|
|
||||||
def update_fps(self) -> None:
|
|
||||||
fps_now = time.perf_counter()
|
|
||||||
elypsed = fps_now - self.fps_last_time
|
|
||||||
if elypsed < 1.0:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.fps_frame_count = self.frames_processed - self.fps_frame_count
|
|
||||||
self.fps_average = self.fps_frame_count / elypsed
|
|
||||||
self.fps_frame_count = self.frames_processed
|
|
||||||
self.fps_last_time = fps_now
|
|
||||||
|
|
||||||
|
|
||||||
class VideoStreamWorker(QObject):
|
|
||||||
# Emitowany z wątku roboczego - będzie przenoszony do głównego wątku przez VideoStream
|
|
||||||
_internal_frame = Signal(object)
|
|
||||||
|
|
||||||
def __init__(self, source):
|
|
||||||
super().__init__()
|
|
||||||
self.source = source
|
|
||||||
self.cap = None
|
|
||||||
self.fps = 30.0
|
|
||||||
self.width = 0
|
|
||||||
self.height = 0
|
|
||||||
self.running = False
|
|
||||||
self.metrics = VideoMetrics(
|
|
||||||
frames_processed=0,
|
|
||||||
frames_dropped=0,
|
|
||||||
last_frame_time=0.0,
|
|
||||||
last_cap_time=0.0,
|
|
||||||
fps_average=0.0,
|
|
||||||
fps_last_time=time.perf_counter(),
|
|
||||||
fps_frame_count=0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.debug(f"VideoStreamWorker initialized with source: {source}")
|
|
||||||
|
|
||||||
def set_source(self, source):
|
|
||||||
logger.debug(f"Setting new video source: {source}")
|
|
||||||
self.source = source
|
|
||||||
if self.running:
|
|
||||||
self.stop()
|
|
||||||
self.run()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Główna pętla wątku roboczego."""
|
|
||||||
if self.source is None:
|
|
||||||
logger.warning("No video source provided")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.cap = cv2.VideoCapture(self.source)
|
|
||||||
if not self.cap.isOpened():
|
|
||||||
logger.error(f"Failed to open video source: {self.source}")
|
|
||||||
return
|
|
||||||
|
|
||||||
self.fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
||||||
if self.fps <= 0:
|
|
||||||
self.fps = 30.0
|
|
||||||
|
|
||||||
self.width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
||||||
self.height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
||||||
|
|
||||||
logger.debug(f"Video opened: {self.source} (fps: {self.fps}, size: {self.width}x{self.height})")
|
|
||||||
|
|
||||||
frame_interval = 1.0 / self.fps
|
|
||||||
self.running = True
|
|
||||||
frame_emit_time = time.perf_counter()
|
|
||||||
|
|
||||||
while self.running:
|
|
||||||
next_frame_time = frame_emit_time + frame_interval
|
|
||||||
|
|
||||||
ret, frame = self.cap.read()
|
|
||||||
|
|
||||||
if not ret:
|
|
||||||
logger.debug("End of video stream or read error")
|
|
||||||
break
|
|
||||||
|
|
||||||
current_time = time.perf_counter()
|
|
||||||
self.metrics.last_cap_time = current_time - frame_emit_time
|
|
||||||
|
|
||||||
if current_time < next_frame_time:
|
|
||||||
sleep_time = next_frame_time - current_time
|
|
||||||
time.sleep(sleep_time)
|
|
||||||
else:
|
|
||||||
self.metrics.frames_dropped += 1
|
|
||||||
logger.debug(f"Frame drops counted: {self.metrics.frames_dropped}")
|
|
||||||
|
|
||||||
frame_emit_time = time.perf_counter()
|
|
||||||
self._internal_frame.emit(frame)
|
|
||||||
|
|
||||||
self.metrics.frames_processed += 1
|
|
||||||
self.metrics.update_fps()
|
|
||||||
|
|
||||||
self.cap.release()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.running = False
|
|
||||||
self.mutex.lock()
|
|
||||||
self.condition.wakeAll()
|
|
||||||
self.mutex.unlock()
|
|
||||||
|
|
||||||
|
|
||||||
class VideoStream(QObject):
|
|
||||||
"""Klasa fasadowa do użycia w głównym wątku GUI."""
|
|
||||||
frame_ready = Signal(object) # To będzie emitowane z głównego wątku
|
|
||||||
|
|
||||||
def __init__(self, source):
|
|
||||||
super().__init__()
|
|
||||||
self.worker = VideoStreamWorker(source)
|
|
||||||
self.worker_thread = QThread()
|
|
||||||
|
|
||||||
# Przenosimy workera do osobnego wątku
|
|
||||||
self.worker.moveToThread(self.worker_thread)
|
|
||||||
|
|
||||||
# Łączymy sygnały
|
|
||||||
self.worker._internal_frame.connect(self._on_frame)
|
|
||||||
self.worker_thread.started.connect(self.worker.run)
|
|
||||||
self.worker_thread.finished.connect(self.worker.deleteLater)
|
|
||||||
self.worker_thread.finished.connect(self.worker_thread.deleteLater)
|
|
||||||
|
|
||||||
def start(self):
|
|
||||||
self.worker_thread.start()
|
|
||||||
|
|
||||||
def stop(self):
|
|
||||||
self.worker.stop()
|
|
||||||
self.worker_thread.quit()
|
|
||||||
self.worker_thread.wait()
|
|
||||||
|
|
||||||
def _on_frame(self, frame):
|
|
||||||
"""Slot wywoływany w głównym wątku po otrzymaniu klatki."""
|
|
||||||
# Tutaj możesz dodać korekcję ekspozycji jeśli nie zrobiłeś tego w workerze
|
|
||||||
self.frame_ready.emit(frame)
|
|
||||||
@@ -2,10 +2,11 @@ from enum import Enum
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMainWindow, QStyle, QToolButton, QWidget, QVBoxLayout, QPushButton
|
from PySide6.QtWidgets import QGridLayout, QHBoxLayout, QMainWindow, QStyle, QToolButton, QWidget, QVBoxLayout, QPushButton, QLabel
|
||||||
from PySide6.QtGui import QIcon
|
from PySide6.QtGui import QIcon, QPixmap
|
||||||
from PySide6.QtCore import Qt, QSize
|
from PySide6.QtCore import Qt, QSize
|
||||||
|
|
||||||
|
from .video_stream.controller import VideoStreamController, FrameMetrics
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class VideoMode(Enum):
|
class VideoMode(Enum):
|
||||||
@@ -70,13 +71,22 @@ class MainWindow(QMainWindow):
|
|||||||
self.video_mode = VideoMode.STREAMING
|
self.video_mode = VideoMode.STREAMING
|
||||||
logger.debug(f"Initial video mode: {self.video_mode}")
|
logger.debug(f"Initial video mode: {self.video_mode}")
|
||||||
|
|
||||||
|
|
||||||
|
self.video_controller = VideoStreamController()
|
||||||
|
self.video_controller.change_source(0) # Start with default camera
|
||||||
|
self.video_controller.image_ready.connect(self.update_frame)
|
||||||
|
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
|
self.video_controller.start()
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
self.central_widget = QWidget()
|
self.central_widget = QWidget()
|
||||||
self.setCentralWidget(self.central_widget)
|
self.setCentralWidget(self.central_widget)
|
||||||
self.central_widget.setStyleSheet("background-color: #001e1e;")
|
self.central_widget.setStyleSheet("background-color: #001e1e;")
|
||||||
|
|
||||||
|
self.video_label = QLabel(self.central_widget)
|
||||||
|
|
||||||
self.toolbar_widget = QWidget(self.central_widget)
|
self.toolbar_widget = QWidget(self.central_widget)
|
||||||
self.toolbar_widget.setMinimumWidth(400)
|
self.toolbar_widget.setMinimumWidth(400)
|
||||||
self.toolbar_widget.setObjectName("bottomToolbar")
|
self.toolbar_widget.setObjectName("bottomToolbar")
|
||||||
@@ -185,4 +195,12 @@ class MainWindow(QMainWindow):
|
|||||||
def pause_video(self):
|
def pause_video(self):
|
||||||
logger.debug("Pausing video")
|
logger.debug("Pausing video")
|
||||||
self.video_mode = VideoMode.STOPPED
|
self.video_mode = VideoMode.STOPPED
|
||||||
set_icon(self.action_button, "play")
|
set_icon(self.action_button, "play")
|
||||||
|
|
||||||
|
def closeEvent(self, event: Any) -> None:
|
||||||
|
logger.debug("Closing main window, cleaning up resources.")
|
||||||
|
self.video_controller.cleanup()
|
||||||
|
super().closeEvent(event)
|
||||||
|
|
||||||
|
def update_frame(self, frame):
|
||||||
|
self.video_label.setPixmap(QPixmap.fromImage(frame))
|
||||||
Reference in New Issue
Block a user