refractor capture to a module
This commit is contained in:
@@ -2,9 +2,16 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from ..capture import (
|
||||
CaptureConfig,
|
||||
CaptureWorker,
|
||||
ReconnectPolicy,
|
||||
is_live_camera,
|
||||
normalize_source,
|
||||
open_capture,
|
||||
)
|
||||
from ..configs import load_config
|
||||
from ..frame_processors import FrameUnwarper
|
||||
from ..sensor import Sensor
|
||||
@@ -24,25 +31,28 @@ from .exports import (
|
||||
|
||||
def normalize_video_source(video):
|
||||
"""Accept camera index, numeric string, or filesystem video path."""
|
||||
if video is None:
|
||||
return 0
|
||||
if isinstance(video, str):
|
||||
stripped = video.strip().strip("/\\")
|
||||
if not stripped:
|
||||
return 0
|
||||
if stripped.isdigit():
|
||||
return int(stripped)
|
||||
return stripped
|
||||
return video
|
||||
return normalize_source(video)
|
||||
|
||||
|
||||
def open_source(video, config):
|
||||
capture = cv2.VideoCapture(normalize_video_source(video))
|
||||
capture.set(cv2.CAP_PROP_FRAME_WIDTH, config.get("camera", {}).get("resolution_raw")[0])
|
||||
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, config.get("camera", {}).get("resolution_raw")[1])
|
||||
capture.set(cv2.CAP_PROP_FPS, config.get("camera", {}).get("fps", 30))
|
||||
capture.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M', 'J', 'P', 'G'))
|
||||
return capture
|
||||
def open_source(video, config, *, auto_reconnect: bool = False, verbose: bool = False):
|
||||
"""Open a capture worker for finger pipelines (compat: returns worker.cap-like via ``.cap``).
|
||||
|
||||
Historically returned a bare ``cv2.VideoCapture``. Callers that need the raw
|
||||
OpenCV handle can use ``worker.source.cap``; prefer ``worker.read()``.
|
||||
"""
|
||||
capture_config = CaptureConfig.from_camera_dict(
|
||||
normalize_video_source(video),
|
||||
config.get("camera", {}),
|
||||
reconnect=ReconnectPolicy(enabled=auto_reconnect),
|
||||
verbose=verbose,
|
||||
start_worker=False,
|
||||
)
|
||||
worker = open_capture(capture_config, start_worker=False)
|
||||
# Preserve prior attribute access: treat returned object like VideoCapture for
|
||||
# simple .read()/.release()/.isOpened() callers, while exposing capture worker.
|
||||
cap = worker.source.cap
|
||||
cap._orisys_worker = worker # type: ignore[attr-defined]
|
||||
return cap
|
||||
|
||||
|
||||
class TactileProcessor:
|
||||
@@ -210,6 +220,7 @@ class FingerRuntime:
|
||||
tactile: TactileProcessor,
|
||||
frame_processor: FrameUnwarper,
|
||||
capture,
|
||||
capture_worker: CaptureWorker | None = None,
|
||||
):
|
||||
self.geometry = geometry
|
||||
self.domain = domain
|
||||
@@ -217,6 +228,7 @@ class FingerRuntime:
|
||||
self.sensor = tactile
|
||||
self.frame_processor = frame_processor
|
||||
self.capture = capture
|
||||
self.capture_worker = capture_worker
|
||||
|
||||
@classmethod
|
||||
def open(
|
||||
@@ -232,6 +244,8 @@ class FingerRuntime:
|
||||
flip_source_vertical=True,
|
||||
verbose=False,
|
||||
mirror=False,
|
||||
auto_reconnect: bool = False,
|
||||
capture_mode: str = "latest",
|
||||
) -> "FingerRuntime":
|
||||
"""Open a live finger runtime from export bundle, config, and video source."""
|
||||
if camera_id is not None:
|
||||
@@ -263,9 +277,22 @@ class FingerRuntime:
|
||||
)
|
||||
domain.remap.ensure_fast_maps()
|
||||
|
||||
capture = open_source(video, geometry.config)
|
||||
if not capture.isOpened():
|
||||
capture_config = CaptureConfig.from_camera_dict(
|
||||
video,
|
||||
geometry.config.get("camera", {}),
|
||||
mode=capture_mode,
|
||||
reconnect=ReconnectPolicy(enabled=auto_reconnect),
|
||||
verbose=verbose,
|
||||
start_worker=False,
|
||||
)
|
||||
capture_worker = open_capture(capture_config, start_worker=False)
|
||||
if not capture_worker.source.is_open():
|
||||
raise RuntimeError("Cannot open camera/video source")
|
||||
# Start latest-frame worker for live cameras so processing speed does not
|
||||
# stall the device buffer.
|
||||
if is_live_camera(video):
|
||||
capture_worker.start()
|
||||
capture = capture_worker.source.cap
|
||||
|
||||
sensor = Sensor("external", config_name=config, verbose=verbose, mirror=mirror)
|
||||
frame_processor = FrameUnwarper(
|
||||
@@ -283,6 +310,7 @@ class FingerRuntime:
|
||||
tactile=tactile,
|
||||
frame_processor=frame_processor,
|
||||
capture=capture,
|
||||
capture_worker=capture_worker,
|
||||
)
|
||||
|
||||
@property
|
||||
@@ -290,6 +318,10 @@ class FingerRuntime:
|
||||
return self.frame_processor.valid_mask
|
||||
|
||||
def read_source(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_worker is not None:
|
||||
return self.capture_worker.source.read_raw()
|
||||
return self.capture.read()
|
||||
|
||||
def process_frame(self, frame, *, color=False):
|
||||
@@ -323,6 +355,11 @@ class FingerRuntime:
|
||||
return ok, unrolled
|
||||
|
||||
def close(self):
|
||||
if self.capture is not None:
|
||||
if self.capture_worker is not None:
|
||||
self.capture_worker.close()
|
||||
self.capture_worker = None
|
||||
self.capture = None
|
||||
elif self.capture is not None:
|
||||
self.capture.release()
|
||||
self.capture = None
|
||||
self.tactile.disconnect()
|
||||
|
||||
Reference in New Issue
Block a user