Phase 11
This commit is contained in:
commit
c5bcb95d74
172 files changed
+9487
No files matched your search
@@ -0,0 +1,78 @@
|
||||
"""OpenCV camera and video input abstraction."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from .config_loader import InputSettings
|
||||
from .exceptions import CameraOpenError, FrameReadError
|
||||
|
||||
|
||||
class Camera:
|
||||
"""Read frames from a camera device or video file."""
|
||||
|
||||
def __init__(self, config: InputSettings) -> None:
|
||||
self.config = config
|
||||
self._capture: cv2.VideoCapture | None = None
|
||||
self._consecutive_failures = 0
|
||||
|
||||
def open(self) -> None:
|
||||
backend_map = {"auto": cv2.CAP_ANY, "dshow": cv2.CAP_DSHOW, "msmf": cv2.CAP_MSMF, "v4l2": cv2.CAP_V4L2}
|
||||
source: int | str
|
||||
if self.config.type == "camera":
|
||||
source = self.config.camera_id
|
||||
else:
|
||||
path = Path(self.config.video_path or "")
|
||||
if not path.is_file():
|
||||
raise CameraOpenError(f"Video file does not exist: {path}")
|
||||
source = str(path)
|
||||
self._capture = cv2.VideoCapture(source, backend_map[self.config.backend])
|
||||
if not self._capture.isOpened():
|
||||
self.release()
|
||||
raise CameraOpenError(f"Could not open {self.config.type} input: {source}")
|
||||
if self.config.type == "camera":
|
||||
self._capture.set(cv2.CAP_PROP_FRAME_WIDTH, self.config.width)
|
||||
self._capture.set(cv2.CAP_PROP_FRAME_HEIGHT, self.config.height)
|
||||
self._capture.set(cv2.CAP_PROP_FPS, self.config.fps)
|
||||
|
||||
def read(self) -> np.ndarray | None:
|
||||
if self._capture is None or not self._capture.isOpened():
|
||||
raise CameraOpenError("Input is not open")
|
||||
ok, frame = self._capture.read()
|
||||
if ok and frame is not None:
|
||||
self._consecutive_failures = 0
|
||||
return frame
|
||||
if self.config.type == "video":
|
||||
return None
|
||||
self._consecutive_failures += 1
|
||||
if self._consecutive_failures >= self.config.max_consecutive_read_failures:
|
||||
raise FrameReadError(f"Camera read failed {self._consecutive_failures} consecutive times")
|
||||
return None
|
||||
|
||||
def is_opened(self) -> bool:
|
||||
return self._capture is not None and self._capture.isOpened()
|
||||
|
||||
def _property(self, property_id: int) -> float:
|
||||
return float(self._capture.get(property_id)) if self._capture is not None else 0.0
|
||||
|
||||
def get_actual_width(self) -> int:
|
||||
return round(self._property(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
|
||||
def get_actual_height(self) -> int:
|
||||
return round(self._property(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
|
||||
def get_actual_fps(self) -> float:
|
||||
return self._property(cv2.CAP_PROP_FPS)
|
||||
|
||||
def release(self) -> None:
|
||||
if self._capture is not None:
|
||||
self._capture.release()
|
||||
self._capture = None
|
||||
|
||||
def __enter__(self) -> "Camera":
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, *_args: object) -> None:
|
||||
self.release()
|
||||
Reference in new issue
Block a user