from pathlib import Path
from typing import Any, Generator, Optional, Tuple
from cv2 import CAP_PROP_BUFFERSIZE, VideoCapture
from numpy import ndarray
from zoloto.marker_type import MarkerType
from .base import BaseCamera
from .mixins import IterableCameraMixin, VideoCaptureMixin, ViewableCameraMixin
from .utils import (
get_video_capture_resolution,
set_video_capture_resolution,
validate_calibrated_video_capture_resolution,
)
def find_camera_ids() -> Generator[int, None, None]:
"""
Find and return ids of connected cameras.
Works the same as VideoCapture(-1).
"""
for camera_id in range(8):
capture = VideoCapture(camera_id)
opened = capture.isOpened()
capture.release()
if opened:
yield camera_id
[docs]class Camera(VideoCaptureMixin, IterableCameraMixin, BaseCamera, ViewableCameraMixin):
[docs] def __init__(
self,
camera_id: int,
*,
marker_size: Optional[int] = None,
marker_type: MarkerType,
calibration_file: Optional[Path] = None,
resolution: Optional[Tuple[int, int]] = None,
) -> None:
super().__init__(
marker_size=marker_size,
marker_type=marker_type,
calibration_file=calibration_file,
)
self.camera_id = camera_id
self.video_capture = self.get_video_capture(self.camera_id)
if resolution is not None:
self._set_resolution(resolution)
if self.calibration_params is not None:
validate_calibrated_video_capture_resolution(
self.video_capture,
self.calibration_params,
override=resolution is not None,
)
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {self.camera_id}>"
[docs] def get_video_capture(self, camera_id: int) -> VideoCapture:
cap = VideoCapture(camera_id)
cap.set(CAP_PROP_BUFFERSIZE, 1)
return cap
def _set_resolution(self, resolution: Tuple[int, int]) -> None:
set_video_capture_resolution(self.video_capture, resolution)
[docs] def get_resolution(self) -> Tuple[int, int]:
return get_video_capture_resolution(self.video_capture)
[docs] def capture_frame(self) -> ndarray:
# Hack: Double capture frames to fill buffer.
self.video_capture.read()
return super().capture_frame()
[docs] def close(self) -> None:
super().close()
self.video_capture.release()
[docs] @classmethod
def discover(cls, **kwargs: Any) -> Generator["Camera", None, None]:
for camera_id in find_camera_ids():
yield cls(camera_id, **kwargs)
[docs]class SnapshotCamera(VideoCaptureMixin, BaseCamera):
"""
A modified version of Camera optimised for single use.
- Doesn't keep the camera open between captures
"""
[docs] def __init__(
self,
camera_id: int,
*,
marker_size: Optional[int] = None,
marker_type: MarkerType,
calibration_file: Optional[Path] = None,
resolution: Optional[Tuple[int, int]] = None,
) -> None:
super().__init__(
marker_size=marker_size,
marker_type=marker_type,
calibration_file=calibration_file,
)
self.camera_id = camera_id
self._resolution = resolution
def __repr__(self) -> str:
return f"<{self.__class__.__name__}: {self.camera_id}>"
[docs] def get_video_capture(self, camera_id: int) -> VideoCapture:
video_capture = VideoCapture(camera_id)
if self._resolution is not None:
set_video_capture_resolution(video_capture, self._resolution)
else:
self._resolution = get_video_capture_resolution(video_capture)
if self.calibration_params is not None:
validate_calibrated_video_capture_resolution(
video_capture, self.calibration_params, override=False
)
return video_capture
[docs] def get_resolution(self) -> Tuple[int, int]:
if self._resolution is None:
raise ValueError(
"Cannot find resolution of camera until at least 1 frame has been captured."
)
return self._resolution
[docs] def capture_frame(self) -> ndarray:
self.video_capture = self.get_video_capture(self.camera_id)
frame = super().capture_frame()
self.video_capture.release()
return frame
[docs] @classmethod
def discover(cls, **kwargs: Any) -> Generator["SnapshotCamera", None, None]:
for camera_id in find_camera_ids():
yield cls(camera_id, **kwargs)