Separe http image stream from wired camera image capture

Fixes the errors with not being able to detect video source or not being able to find starting number
This commit is contained in:
lorow 2022-06-14 00:01:10 +02:00 committed by qDot
parent 5580d482f9
commit d30b6fb37f

View File

@ -1,8 +1,13 @@
from time import sleep from time import sleep
import numpy
from config import RansacConfig from config import RansacConfig
import requests
from enum import Enum from enum import Enum
import threading import threading
import queue import queue
import runpy
import cv2 import cv2
@ -10,8 +15,11 @@ class CameraState(Enum):
CONNECTED = 1 CONNECTED = 1
DISCONNECTED = 2 DISCONNECTED = 2
class Camera: class Camera:
def __init__(self, config: RansacConfig, camera_index: int, cancellation_event: "threading.Event", capture_event: "threading.Event", camera_status_outgoing: "queue.Queue[CameraState]", camera_output_outgoing: "queue.Queue"): def __init__(self, config: RansacConfig, camera_index: int, cancellation_event: "threading.Event",
capture_event: "threading.Event", camera_status_outgoing: "queue.Queue[CameraState]",
camera_output_outgoing: "queue.Queue"):
self.config = config self.config = config
self.camera_index = camera_index self.camera_index = camera_index
self.camera_address = config.capture_source self.camera_address = config.capture_source
@ -20,7 +28,11 @@ class Camera:
self.capture_event = capture_event self.capture_event = capture_event
self.cancellation_event = cancellation_event self.cancellation_event = cancellation_event
self.current_capture_source = config.capture_source self.current_capture_source = config.capture_source
self.capture_source: "cv2.VideoCapture" = cv2.VideoCapture(config.capture_source) self.wired_camera: "cv2.VideoCapture" = cv2.VideoCapture(config.capture_source, cv2.CAP_FFMPEG)
self.stream = None
self.stream_frame_number = 0
self.stream_bytes = bytes()
self.error_message = "Capture source {} not found, retrying in 500ms"
def set_output_queue(self, camera_output_outgoing: "queue.Queue"): def set_output_queue(self, camera_output_outgoing: "queue.Queue"):
self.camera_output_outgoing = camera_output_outgoing self.camera_output_outgoing = camera_output_outgoing
@ -33,11 +45,20 @@ class Camera:
# If things aren't open, retry until they are. Don't let read requests come in any earlier # If things aren't open, retry until they are. Don't let read requests come in any earlier
# than this, otherwise we can deadlock ourselves. # than this, otherwise we can deadlock ourselves.
if not self.capture_source.isOpened() or (self.config.capture_source != self.current_capture_source): if type(self.config.capture_source) == str and "http" in self.config.capture_source:
print(f"Capture source {self.config.capture_source} not found, retrying in 500ms") try:
self.stream = requests.get(self.config.capture_source, stream=True)
except requests.exceptions.RequestException:
print(self.error_message.format(self.config.capture_source))
else:
# so, they might have switched to a wired camera for some reason, no need to keep the stream running
self.cleanup_stream()
if not self.wired_camera.isOpened() or self.config.capture_source != self.current_capture_source:
print(self.error_message.format(self.config.capture_source))
sleep(0.5) sleep(0.5)
self.current_capture_source = self.config.capture_source self.current_capture_source = self.config.capture_source
self.capture_source = cv2.VideoCapture(self.current_capture_source) self.wired_camera = cv2.VideoCapture(self.current_capture_source, cv2.CAP_FFMPEG)
continue continue
# Assuming we can access our capture source, wait for another thread to request a capture. # Assuming we can access our capture source, wait for another thread to request a capture.
@ -46,13 +67,46 @@ class Camera:
if not self.capture_event.wait(timeout=0.02): if not self.capture_event.wait(timeout=0.02):
continue continue
if self.stream:
self.get_stream_picture()
else:
self.get_wired_camera_picture()
def get_stream_picture(self):
for chunk in self.stream.iter_content(chunk_size=1024):
self.stream_bytes += chunk
# mjpeg streams are encoded into frames starting and ending with those bytes
starting_section = self.stream_bytes.find(b'\xff\xd8')
closing_section = self.stream_bytes.find(b'\xff\xd9')
if starting_section != -1 and closing_section != -1:
# we extract the encoded image first, and then prepare the buffer for another frame
jpg_data = self.stream_bytes[starting_section:closing_section+2]
self.stream_frame_number += 1
self.stream_bytes = self.stream_bytes[closing_section+2:]
image = cv2.imdecode(numpy.fromstring(jpg_data, dtype=numpy.uint8), cv2.IMREAD_COLOR)
self.push_image_to_queue(image, self.stream_frame_number, 60)
def get_wired_camera_picture(self):
try: try:
ret, image = self.capture_source.read() ret, image = self.wired_camera.read()
if not ret: if not ret:
self.capture_source.set(cv2.CAP_PROP_POS_FRAMES, 0) self.wired_camera.set(cv2.CAP_PROP_POS_FRAMES, 0)
raise RuntimeError("Problem while getting frame") raise RuntimeError("Problem while getting frame")
frame_number = self.capture_source.get(cv2.CAP_PROP_POS_FRAMES) frame_number = self.wired_camera.get(cv2.CAP_PROP_POS_FRAMES)
fps = self.capture_source.get(cv2.CAP_PROP_FPS) fps = self.wired_camera.get(cv2.CAP_PROP_FPS)
self.push_image_to_queue(image, frame_number, fps)
except:
# print("Capture source problem, assuming camera disconnected, waiting for reconnect.")
pass
def cleanup_stream(self):
if self.stream:
self.stream.close()
self.stream_bytes = None
self.stream_frame_number = 0
def push_image_to_queue(self, image, frame_number, fps):
# If there's backpressure, just yell. We really shouldn't have this unless we start getting # If there's backpressure, just yell. We really shouldn't have this unless we start getting
# some sort of capture event conflict though. # some sort of capture event conflict though.
qsize = self.camera_output_outgoing.qsize() qsize = self.camera_output_outgoing.qsize()
@ -60,6 +114,3 @@ class Camera:
print(f"CAPTURE QUEUE BACKPRESSURE OF {qsize}. CHECK FOR CRASH OR TIMING ISSUES IN ALGORITHM.") print(f"CAPTURE QUEUE BACKPRESSURE OF {qsize}. CHECK FOR CRASH OR TIMING ISSUES IN ALGORITHM.")
self.camera_output_outgoing.put((image, frame_number, fps)) self.camera_output_outgoing.put((image, frame_number, fps))
self.capture_event.clear() self.capture_event.clear()
except:
# print("Capture source problem, assuming camera disconnected, waiting for reconnect.")
continue