GUI: axis camera optimization
This commit is contained in:
1 parent
e3e1e97712
commit
177199c60a
1 file changed
+96
-44
@@ -1,6 +1,9 @@
|
||||
import cv2
|
||||
import requests
|
||||
import numpy as np
|
||||
from PySide6.QtCore import QThread, Signal
|
||||
from PySide6.QtGui import QImage
|
||||
from io import BytesIO
|
||||
|
||||
|
||||
class VideoThread(QThread):
|
||||
@@ -11,71 +14,120 @@ class VideoThread(QThread):
|
||||
super().__init__()
|
||||
self.camera_ip = ip
|
||||
self.running = False
|
||||
self.cap = None
|
||||
self.session = None
|
||||
self.last_good_frame = None
|
||||
|
||||
def set_camera_ip(self, ip: str):
|
||||
cv2.setLogLevel(cv2.LOG_LEVEL_ERROR)
|
||||
self.camera_ip = ip
|
||||
|
||||
def run(self):
|
||||
# RTSP URL without credentials
|
||||
rtsp_url = f"rtsp://{self.camera_ip}:554/axis-media/media.amp?videocodec=h264&resolution=1280x720"
|
||||
# MJPEG URL
|
||||
mjpeg_url = f"http://{self.camera_ip}/axis-cgi/mjpg/video.cgi?resolution=1280x720&fps=10"
|
||||
|
||||
self.cap = cv2.VideoCapture(rtsp_url)
|
||||
# Create a session for connection reuse
|
||||
self.session = requests.Session()
|
||||
|
||||
if not self.cap.isOpened():
|
||||
self.error_occurred.emit(f"Could not connect to camera at {self.camera_ip}")
|
||||
return
|
||||
# Set connection timeout and read timeout
|
||||
timeout = (5, 30) # (connect_timeout, read_timeout)
|
||||
|
||||
# Set buffer size to reduce latency
|
||||
self.cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
|
||||
self.cap.set(cv2.CAP_PROP_FPS, 10) # Request 10 FPSs
|
||||
try:
|
||||
# Start streaming
|
||||
response = self.session.get(mjpeg_url, stream=True, timeout=timeout)
|
||||
|
||||
self.running = True
|
||||
if response.status_code != 200:
|
||||
self.error_occurred.emit(
|
||||
f"Could not connect to camera at {self.camera_ip}. Status: {response.status_code}")
|
||||
return
|
||||
|
||||
while self.running:
|
||||
ret, frame = self.cap.read()
|
||||
self.running = True
|
||||
|
||||
if ret and frame is not None:
|
||||
# Check if frame is valid (not corrupted)
|
||||
if frame.shape[0] > 0 and frame.shape[1] > 0:
|
||||
try:
|
||||
# Convert BGR to RGB
|
||||
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
# Buffer to accumulate data
|
||||
buffer = b''
|
||||
|
||||
# Convert to QImage
|
||||
height, width, channel = rgb_frame.shape
|
||||
bytes_per_line = 3 * width
|
||||
qt_image = QImage(rgb_frame.data, width, height, bytes_per_line, QImage.Format.Format_RGB888)
|
||||
# MJPEG boundary detection
|
||||
boundary = None
|
||||
|
||||
# Store as last good frame
|
||||
self.last_good_frame = qt_image
|
||||
self.frame_ready.emit(qt_image)
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if not self.running:
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
# If frame processing fails, use last good frame if available
|
||||
if self.last_good_frame is not None:
|
||||
self.frame_ready.emit(self.last_good_frame)
|
||||
# Don't emit error for minor decoding issues, just continue
|
||||
pass
|
||||
else:
|
||||
# Invalid frame dimensions, use last good frame if available
|
||||
if self.last_good_frame is not None:
|
||||
self.frame_ready.emit(self.last_good_frame)
|
||||
else:
|
||||
# Frame read failed, use last good frame if available
|
||||
if chunk:
|
||||
buffer += chunk
|
||||
|
||||
# Find boundary on first iteration
|
||||
if boundary is None:
|
||||
boundary_start = buffer.find(b'--')
|
||||
if boundary_start != -1:
|
||||
boundary_end = buffer.find(b'\r\n', boundary_start)
|
||||
if boundary_end != -1:
|
||||
boundary = buffer[boundary_start:boundary_end]
|
||||
|
||||
# Process frames if we have a boundary
|
||||
if boundary:
|
||||
self._process_buffer(buffer, boundary)
|
||||
# Keep only the last part of buffer that might contain partial frame
|
||||
last_boundary = buffer.rfind(boundary)
|
||||
if last_boundary != -1:
|
||||
buffer = buffer[last_boundary:]
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
self.error_occurred.emit(f"Connection error: {str(e)}")
|
||||
except Exception as e:
|
||||
self.error_occurred.emit(f"Unexpected error: {str(e)}")
|
||||
finally:
|
||||
if self.session:
|
||||
self.session.close()
|
||||
|
||||
def _process_buffer(self, buffer, boundary):
|
||||
"""Process buffer to extract JPEG frames"""
|
||||
parts = buffer.split(boundary)
|
||||
|
||||
for part in parts[:-1]: # Skip the last part as it might be incomplete
|
||||
if not part:
|
||||
continue
|
||||
|
||||
# Find the start of JPEG data (after headers)
|
||||
jpeg_start = part.find(b'\xff\xd8') # JPEG SOI marker
|
||||
if jpeg_start == -1:
|
||||
continue
|
||||
|
||||
# Find the end of JPEG data
|
||||
jpeg_end = part.find(b'\xff\xd9', jpeg_start) # JPEG EOI marker
|
||||
if jpeg_end == -1:
|
||||
continue
|
||||
|
||||
# Extract JPEG data
|
||||
jpeg_data = part[jpeg_start:jpeg_end + 2]
|
||||
|
||||
try:
|
||||
# Decode JPEG using OpenCV
|
||||
nparr = np.frombuffer(jpeg_data, np.uint8)
|
||||
frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
|
||||
|
||||
if frame is not None and frame.shape[0] > 0 and frame.shape[1] > 0:
|
||||
# Convert BGR to RGB
|
||||
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
|
||||
# Convert to QImage
|
||||
height, width, channel = rgb_frame.shape
|
||||
bytes_per_line = 3 * width
|
||||
qt_image = QImage(rgb_frame.data, width, height, bytes_per_line, QImage.Format.Format_RGB888)
|
||||
|
||||
# Store as last good frame
|
||||
self.last_good_frame = qt_image
|
||||
self.frame_ready.emit(qt_image)
|
||||
|
||||
except Exception as e:
|
||||
# If frame processing fails, use last good frame if available
|
||||
if self.last_good_frame is not None:
|
||||
self.frame_ready.emit(self.last_good_frame)
|
||||
|
||||
# Small delay to prevent overwhelming the GUI
|
||||
self.msleep(100) # ~10 FPS
|
||||
|
||||
def stop(self):
|
||||
self.running = False
|
||||
print("Video thread stop requested")
|
||||
if self.cap:
|
||||
self.cap.release()
|
||||
if self.session:
|
||||
self.session.close()
|
||||
self.quit()
|
||||
print("Video thread stopped. Exiting...")
|
||||
self.wait()
|
||||
|
||||
Reference in new issue
Block a user