This commit is contained in:
agomizi1cm committed 2026-08-09 21:41:44 +09:00
commit c5bcb95d74
172 files changed
+9487

No files matched your search

+78
View File
@@ -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()