ayzeksalimli's picture
Push project (code, README, Docker/compose, models) — no .github/workflows
9f85448 verified
Raw
History Blame Contribute Delete
2.75 kB
from __future__ import annotations
import sys
import threading
import time
import cv2
import numpy as np
class CameraStream:
"""Threaded camera frame grabber."""
def __init__(self, index: int = 0, width: int = 1280, height: int = 720,
fps: int = 60) -> None:
backend = cv2.CAP_AVFOUNDATION if sys.platform == "darwin" else cv2.CAP_ANY
cap = cv2.VideoCapture(index, backend)
if not cap.isOpened():
cap = cv2.VideoCapture(index)
if not cap.isOpened():
raise RuntimeError(
f"Could not open camera {index}. "
"On macOS grant camera access in System Settings, Privacy and "
"Security, Camera, then restart."
)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, width)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height)
cap.set(cv2.CAP_PROP_FPS, fps)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
self.cap = cap
ok, frame = cap.read()
if not ok or frame is None:
cap.release()
raise RuntimeError("Camera opened but returns no frames.")
self._frame: np.ndarray = frame
self._seq = 0
self._lock = threading.Lock()
self._stop = threading.Event()
self.failed = False
self._thread = threading.Thread(target=self._loop, daemon=True)
self._thread.start()
@property
def shape(self) -> tuple[int, int]:
"""Frame height and width."""
return self._frame.shape[0], self._frame.shape[1]
@property
def source_fps(self) -> float:
"""Camera reported FPS."""
v = self.cap.get(cv2.CAP_PROP_FPS)
return float(v) if v and v > 0 else 0.0
def _loop(self) -> None:
"""Background capture loop."""
while not self._stop.is_set():
ok, frame = self.cap.read()
if not ok or frame is None:
self.failed = True
break
with self._lock:
self._frame = frame
self._seq += 1
def read(self) -> tuple[np.ndarray, int]:
"""Latest frame and sequence."""
with self._lock:
return self._frame, self._seq
def wait_next(self, last_seq: int, timeout: float = 1.0) -> tuple[np.ndarray, int]:
"""Wait for fresh frame."""
deadline = time.perf_counter() + timeout
while True:
frame, seq = self.read()
if seq != last_seq or self.failed or time.perf_counter() > deadline:
return frame, seq
time.sleep(0.001)
def release(self) -> None:
"""Stop thread and release."""
self._stop.set()
self._thread.join(timeout=1.0)
self.cap.release()