hotfix/camera not connected error

This commit is contained in:
ykcheungab
2026-09-02 21:37:40 +08:00
parent 32823a7666
commit 5be2d9759a
11 changed files with 427 additions and 677 deletions
+49 -31
View File
@@ -31,7 +31,7 @@ from .signal_processing import (
)
from .capture import (
CaptureConfig,
CaptureWorker,
CaptureSource,
ReconnectPolicy,
is_live_camera,
normalize_source,
@@ -76,7 +76,8 @@ class Sensor:
config_name="prototype", cal_path=None, override=False, box=None, mirror=True,
backend='auto', capture_config: Optional[CaptureConfig] = None,
auto_reconnect: bool = False, capture_mode: str = "latest",
capture_queue_size: int = 1, capture_overflow: str = "drop_oldest"):
capture_queue_size: int = 1, capture_overflow: str = "drop_oldest",
**_deprecated_capture_kwargs):
"""
Initialize Sensor with video source and configuration.
@@ -96,9 +97,9 @@ class Sensor:
"cpu": Use CPU for computation
capture_config: Optional ``CaptureConfig`` overriding capture kwargs below.
auto_reconnect: Enable USB disconnect auto-reconnect for live cameras.
capture_mode: ``"latest"`` (low latency, default) or ``"queue"``.
capture_queue_size: Bounded queue size for queue mode (1..10).
capture_overflow: ``drop_oldest`` | ``drop_newest`` | ``block``.
capture_mode: Deprecated; ignored (latest-frame capture only).
capture_queue_size: Deprecated; ignored.
capture_overflow: Deprecated; ignored.
"""
self.CUPY_AVAILABLE = check_cupy_available()
@@ -120,9 +121,6 @@ class Sensor:
self.mirror = mirror
self._capture_config_override = capture_config
self._auto_reconnect = bool(auto_reconnect)
self._capture_mode = capture_mode
self._capture_queue_size = int(capture_queue_size)
self._capture_overflow = capture_overflow
# Load configuration
self.__config_cam = None
@@ -231,7 +229,7 @@ class Sensor:
# self._av_stream = None
# self._av_decoder = None
self.cap = None # compat alias for _capture_worker.source.cap
self._capture_worker: Optional[CaptureWorker] = None
self._capture_source: Optional[CaptureSource] = None
self._is_live_camera = (
is_live_camera(self.vid_src) and not self._external_only
)
@@ -705,9 +703,9 @@ class Sensor:
def disconnect(self):
"""Release resources and stop async threads."""
if self._capture_worker is not None:
self._capture_worker.close()
self._capture_worker = None
if self._capture_source is not None:
self._capture_source.close()
self._capture_source = None
self.cap = None
if self.verbose:
print("Sensor disconnected.")
@@ -934,25 +932,23 @@ class Sensor:
capture_config = CaptureConfig.from_camera_dict(
self.vid_src,
self.__config_cam,
mode=self._capture_mode,
queue_size=self._capture_queue_size,
overflow=self._capture_overflow,
reconnect=ReconnectPolicy(enabled=self._auto_reconnect),
verbose=self.verbose,
start_worker=False,
background=False,
)
try:
self._capture_worker = open_capture(capture_config, start_worker=False)
self._capture_source = open_capture(capture_config, start_background=False)
except Exception as exc:
raise Exception("Failed to open video source.") from exc
self.cap = self._capture_worker.source.cap
# self._use_pyav = False
self.cap = self._capture_source.cap
if not self._is_source_open():
raise Exception("Failed to open video source.")
self._capture_source.warmup_frames(3)
# Wait for first valid frame
frame = None
while frame is None:
@@ -967,16 +963,24 @@ class Sensor:
if box is not None:
self.stitcher.isstitch = self.isstitch = False
self.stitcher.boxes = box
self.stitch_valid = bool(self.isstitch)
img = self.frame_processor.process(frame)
self.initial_img = self._make_initial_gray(img)
else:
print("Override=True but no box provided.")
else:
self._find_stitch_boxes(frame)
if not self.stitch_valid:
raise RuntimeError(
f"Failed to detect {self._config_units} vision tiles for stitching. "
"Check camera index, lighting, and that MJPG 1080p is negotiated."
)
img = self.frame_processor.process(frame)
self.initial_img = self._make_initial_gray(img)
# For live cameras, start a capture worker to avoid latency buildup.
if self._is_live_camera and self._capture_worker is not None:
self._capture_worker.start()
# For live cameras, start background capture to avoid latency buildup.
if self._is_live_camera and self._capture_source is not None:
self._capture_source.start_background()
def _make_initial_gray(self, img_bgr):
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
@@ -990,6 +994,9 @@ class Sensor:
def _find_stitch_boxes(self, img_src, show=False):
"""Detect vision tile boxes with camera stabilization."""
n_boxes, v_mean0, timeout = 0, 0, 5
boxes = []
best_n_boxes = 0
best_boxes = []
start_time = cv2.getTickCount()
while n_boxes < self._config_units:
@@ -1000,12 +1007,18 @@ class Sensor:
if abs(v_mean - v_mean0) < 5:
n_boxes, boxes = self.stitcher.find_stitch_boxes(frame, show=show)
if n_boxes > best_n_boxes:
best_n_boxes = n_boxes
best_boxes = boxes
if (cv2.getTickCount() - start_time) / cv2.getTickFrequency() > timeout:
print("Timeout finding vision units.")
self.stitcher.isstitch = self.isstitch = False
break
v_mean0 = v_mean
if n_boxes < self._config_units and best_n_boxes >= self._config_units:
n_boxes = best_n_boxes
boxes = best_boxes
self.n_boxes = n_boxes
self.boxes = boxes
@@ -1023,8 +1036,8 @@ class Sensor:
# return True
def _is_source_open(self):
if self._capture_worker is not None:
return self._capture_worker.source.is_open()
if self._capture_source is not None:
return self._capture_source.is_open()
return False
# Legacy PyAV / direct-cap fallbacks (unused after orisys.capture refactor).
# if self._use_pyav:
@@ -1032,13 +1045,13 @@ class Sensor:
# return self.cap is not None and self.cap.isOpened()
def _read_frame(self):
if self._capture_worker is not None and self._capture_worker.running:
return self._capture_worker.read(copy=True, wait_s=1.0)
if self._capture_source is not None and self._capture_source.running:
return self._capture_source.read_latest(copy=True, wait_s=1.0)
return self._read_frame_raw()
def _read_frame_raw(self):
if self._capture_worker is not None:
return self._capture_worker.source.read_raw()
if self._capture_source is not None:
return self._capture_source.read_raw()
return False, None
# Legacy PyAV / direct-cap fallbacks (unused after orisys.capture refactor).
# if self._use_pyav:
@@ -1058,9 +1071,14 @@ class Sensor:
@property
def capture_status(self):
"""Return capture health snapshot, or None in external-frame mode."""
if self._capture_worker is None:
if self._capture_source is None:
return None
return self._capture_worker.source.status
return self._capture_source.status
@property
def _capture_worker(self):
"""Deprecated alias for ``_capture_source``."""
return self._capture_source
# =========================================================================