test sleep zamiast qtimer
This commit is contained in:
@@ -80,9 +80,10 @@ class MainWindow(QMainWindow):
|
|||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
self.video_controller.start()
|
self.video_controller.start()
|
||||||
|
self._counter = 0
|
||||||
self.timer = QTimer()
|
self.timer = QTimer()
|
||||||
self.timer.timeout.connect(self.update_metrics)
|
self.timer.timeout.connect(self.update_metrics)
|
||||||
self.timer.start(1000) # Update metrics every second
|
self.timer.start(500) # Update metrics every second
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
self.central_widget = QWidget()
|
self.central_widget = QWidget()
|
||||||
@@ -219,10 +220,12 @@ class MainWindow(QMainWindow):
|
|||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
def update_frame(self, frame):
|
def update_frame(self, frame):
|
||||||
# self.video_label.setPixmap(QPixmap.fromImage(frame))
|
self.video_label.setPixmap(QPixmap.fromImage(frame))
|
||||||
pass
|
# pass
|
||||||
|
|
||||||
def update_metrics(self, metrics: FrameMetrics):
|
def update_metrics(self):
|
||||||
|
self._counter += 1
|
||||||
|
metrics = self.video_controller.get_metrics()
|
||||||
if metrics is None:
|
if metrics is None:
|
||||||
self.metrics_label.setPlainText("No metrics available")
|
self.metrics_label.setPlainText("No metrics available")
|
||||||
return
|
return
|
||||||
@@ -232,3 +235,7 @@ class MainWindow(QMainWindow):
|
|||||||
metrics_text += f"{key}: {value}\n"
|
metrics_text += f"{key}: {value}\n"
|
||||||
|
|
||||||
self.metrics_label.setPlainText(metrics_text)
|
self.metrics_label.setPlainText(metrics_text)
|
||||||
|
|
||||||
|
if self._counter >= 20:
|
||||||
|
self._counter = 0
|
||||||
|
self.video_controller.reset_metrics()
|
||||||
@@ -27,6 +27,7 @@ class FrameMetrics:
|
|||||||
total_start_delta: float = 0.0 # Total accumulated frame read start delta (used for calculating average)
|
total_start_delta: float = 0.0 # Total accumulated frame read start delta (used for calculating average)
|
||||||
b_fr_time_delta: deque = field(init=False, repr=False) # Buffer for recent frame read time deltas (used for calculating average)
|
b_fr_time_delta: deque = field(init=False, repr=False) # Buffer for recent frame read time deltas (used for calculating average)
|
||||||
total_frame_time: float = 0.0 # Total accumulated frame read time delta (used for calculating average)
|
total_frame_time: float = 0.0 # Total accumulated frame read time delta (used for calculating average)
|
||||||
|
frame_drops: int = 0 # Total number of frames dropped (if frame read time exceeds expected frame interval)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.b_fr_start_delta = deque(maxlen=self.buf_size)
|
self.b_fr_start_delta = deque(maxlen=self.buf_size)
|
||||||
@@ -72,11 +73,12 @@ class FrameMetrics:
|
|||||||
|
|
||||||
self.fps_average_1s = 1.0 / self.fr_start_delta_avg if self.fr_start_delta_avg > 0 else 0.0
|
self.fps_average_1s = 1.0 / self.fr_start_delta_avg if self.fr_start_delta_avg > 0 else 0.0
|
||||||
|
|
||||||
def update_metrics(self, start_time: float, end_time: float) -> None:
|
def update_metrics(self, start_time: float, end_time: float, frames_dropped: int) -> None:
|
||||||
self.frame_count += 1
|
self.frame_count += 1
|
||||||
self.calc_start_delta_min_max_avg(start_time)
|
self.calc_start_delta_min_max_avg(start_time)
|
||||||
self.calc_frame_time_min_max_avg(start_time, end_time)
|
self.calc_frame_time_min_max_avg(start_time, end_time)
|
||||||
self.calc_fps()
|
self.calc_fps()
|
||||||
|
self.frame_drops = frames_dropped
|
||||||
|
|
||||||
def reset_metrics(self) -> None:
|
def reset_metrics(self) -> None:
|
||||||
self.fr_start_delta_min = float('inf')
|
self.fr_start_delta_min = float('inf')
|
||||||
@@ -92,6 +94,7 @@ class FrameMetrics:
|
|||||||
self.total_start_delta = 0.0
|
self.total_start_delta = 0.0
|
||||||
self.b_fr_time_delta.clear()
|
self.b_fr_time_delta.clear()
|
||||||
self.total_frame_time = 0.0
|
self.total_frame_time = 0.0
|
||||||
|
self.frame_drops = 0
|
||||||
|
|
||||||
def get_metrics(self) -> "FrameMetrics":
|
def get_metrics(self) -> "FrameMetrics":
|
||||||
return FrameMetrics(
|
return FrameMetrics(
|
||||||
@@ -102,7 +105,11 @@ class FrameMetrics:
|
|||||||
fr_time_max= round(self.fr_time_max * 1000, 3),
|
fr_time_max= round(self.fr_time_max * 1000, 3),
|
||||||
fr_time_avg= round(self.fr_time_avg * 1000, 3),
|
fr_time_avg= round(self.fr_time_avg * 1000, 3),
|
||||||
fps_average_1s= round(self.fps_average_1s, 3),
|
fps_average_1s= round(self.fps_average_1s, 3),
|
||||||
frame_count=self.frame_count
|
frame_count=self.frame_count,
|
||||||
|
last_start_time=self.last_start_time,
|
||||||
|
buf_size=self.buf_size,
|
||||||
|
total_frame_time=round(self.total_frame_time * 1000, 3),
|
||||||
|
total_start_delta=round(self.total_start_delta * 1000, 3)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -150,6 +157,13 @@ class VideoStreamController(QObject):
|
|||||||
"""
|
"""
|
||||||
return self._metrics.get_metrics() if self._metrics else None
|
return self._metrics.get_metrics() if self._metrics else None
|
||||||
|
|
||||||
|
def reset_metrics(self):
|
||||||
|
"""
|
||||||
|
Resets the frame metrics to initial state.
|
||||||
|
"""
|
||||||
|
if self._metrics:
|
||||||
|
self._metrics.reset_metrics()
|
||||||
|
|
||||||
@Slot(object)
|
@Slot(object)
|
||||||
def change_source(self, source):
|
def change_source(self, source):
|
||||||
"""
|
"""
|
||||||
@@ -170,7 +184,7 @@ class VideoStreamController(QObject):
|
|||||||
@Slot(float, tuple)
|
@Slot(float, tuple)
|
||||||
def started(self, fps: float, video_res: tuple):
|
def started(self, fps: float, video_res: tuple):
|
||||||
logger.debug(f"Video stream worker started with FPS: {fps}, Resolution: {video_res[0]}x{video_res[1]}")
|
logger.debug(f"Video stream worker started with FPS: {fps}, Resolution: {video_res[0]}x{video_res[1]}")
|
||||||
self._metrics = FrameMetrics()
|
self._metrics = FrameMetrics(buf_size=int(fps))
|
||||||
self.status_changed.emit(True)
|
self.status_changed.emit(True)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
@@ -229,10 +243,10 @@ class VideoStreamController(QObject):
|
|||||||
self.error_occurred.emit(f"Image conversion error: {str(e)}")
|
self.error_occurred.emit(f"Image conversion error: {str(e)}")
|
||||||
return QImage()
|
return QImage()
|
||||||
|
|
||||||
@Slot(float, float)
|
@Slot(float, float, int)
|
||||||
def _handle_frame_time(self, start_time, end_time):
|
def _handle_frame_time(self, start_time, end_time, frames_dropped):
|
||||||
"""
|
"""
|
||||||
Optional: Handle frame timing information for performance monitoring.
|
Optional: Handle frame timing information for performance monitoring.
|
||||||
"""
|
"""
|
||||||
if self._metrics:
|
if self._metrics:
|
||||||
self._metrics.update_metrics(start_time, end_time)
|
self._metrics.update_metrics(start_time, end_time, frames_dropped)
|
||||||
@@ -12,11 +12,12 @@ class VideoStreamWorker(QObject):
|
|||||||
Runs in a separate QThread.
|
Runs in a separate QThread.
|
||||||
"""
|
"""
|
||||||
frame_ready = Signal(np.ndarray)
|
frame_ready = Signal(np.ndarray)
|
||||||
frame_time = Signal(float, float) # (start_time, end_time)
|
frame_time = Signal(float, float, int) # (start_time, end_time)
|
||||||
error_occurred = Signal(str)
|
error_occurred = Signal(str)
|
||||||
started = Signal(float, tuple) # (fps, (width, height))
|
started = Signal(float, tuple) # (fps, (width, height))
|
||||||
stopped = Signal()
|
stopped = Signal()
|
||||||
source_changed = Signal(object) # Nowy sygnał dla zmiany źródła
|
source_changed = Signal(object) # Nowy sygnał dla zmiany źródła
|
||||||
|
_run = Signal() # Sygnał do uruchomienia pętli wątku
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -26,6 +27,7 @@ class VideoStreamWorker(QObject):
|
|||||||
self._timer = QTimer()
|
self._timer = QTimer()
|
||||||
self._timer.timeout.connect(self._process_frame)
|
self._timer.timeout.connect(self._process_frame)
|
||||||
self.source_changed.connect(self._on_source_changed) # Łączymy sygnał ze slotem
|
self.source_changed.connect(self._on_source_changed) # Łączymy sygnał ze slotem
|
||||||
|
self._run.connect(self.run) # Łączymy sygnał ze slotem
|
||||||
|
|
||||||
@Slot(object)
|
@Slot(object)
|
||||||
def set_source(self, source):
|
def set_source(self, source):
|
||||||
@@ -69,12 +71,13 @@ class VideoStreamWorker(QObject):
|
|||||||
# Using a timer for consistent frame rate and to play nice with the event loop
|
# Using a timer for consistent frame rate and to play nice with the event loop
|
||||||
# For files, we might want to calculate the interval from FPS.
|
# For files, we might want to calculate the interval from FPS.
|
||||||
fps = self._cap.get(cv2.CAP_PROP_FPS)
|
fps = self._cap.get(cv2.CAP_PROP_FPS)
|
||||||
interval = int(1000 / fps) if fps > 0 else 30
|
interval = int(600 / fps) if fps > 0 else 20
|
||||||
|
|
||||||
video_res = (int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self._cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
|
video_res = (int(self._cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(self._cap.get(cv2.CAP_PROP_FRAME_HEIGHT)))
|
||||||
logger.debug(f"Video opened: {self._source} (fps: {fps}, width: {video_res[0]}, height: {video_res[1]})")
|
logger.debug(f"Video opened: {self._source} (fps: {fps}, width: {video_res[0]}, height: {video_res[1]})")
|
||||||
|
|
||||||
self._timer.start(interval)
|
# self._timer.start(interval)
|
||||||
|
self._run.emit() # Uruchamiamy pętlę wątku za pomocą sygnału
|
||||||
self.started.emit(fps, video_res)
|
self.started.emit(fps, video_res)
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
@@ -87,6 +90,63 @@ class VideoStreamWorker(QObject):
|
|||||||
self._cap = None
|
self._cap = None
|
||||||
self.stopped.emit()
|
self.stopped.emit()
|
||||||
|
|
||||||
|
|
||||||
|
# def run(self):
|
||||||
|
# while self._is_running:
|
||||||
|
# self._process_frame()
|
||||||
|
# time.sleep(0.028) # Krótka przerwa, aby nie zablokować CPU
|
||||||
|
|
||||||
|
|
||||||
|
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 + 1)
|
||||||
|
self.running = True
|
||||||
|
frame_emit_time = time.perf_counter()
|
||||||
|
read_start_time = 0.0
|
||||||
|
read_end_time = 0.0
|
||||||
|
frames_dropped = 0
|
||||||
|
while self.running:
|
||||||
|
next_frame_time = frame_emit_time + frame_interval
|
||||||
|
|
||||||
|
ret, frame = self.cap.read()
|
||||||
|
read_end_time = time.perf_counter()
|
||||||
|
self.frame_time.emit(read_start_time, read_end_time, frames_dropped)
|
||||||
|
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:
|
||||||
|
frames_dropped += 1
|
||||||
|
logger.debug(f"Frame drops counted: {frames_dropped}")
|
||||||
|
|
||||||
|
frame_emit_time = time.perf_counter()
|
||||||
|
read_start_time = time.perf_counter()
|
||||||
|
self.frame_ready.emit(frame)
|
||||||
|
|
||||||
def _process_frame(self):
|
def _process_frame(self):
|
||||||
if not self._is_running:
|
if not self._is_running:
|
||||||
logger.debug("VideoStreamWorker is not running. Skipping frame processing.")
|
logger.debug("VideoStreamWorker is not running. Skipping frame processing.")
|
||||||
|
|||||||
Reference in New Issue
Block a user