Files
Alfredv2/alfred/app/services/camera.py
T
2026-10-31 11:04:45 +01:00

96 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Accès simple à la caméra d'Alfred, avec aperçu MJPEG activé à la demande.
Une seule lecture par instance Alfred. La vidéo ne démarre que lorsque
l'utilisateur clique sur Activer, pour éviter de monopoliser la caméra.
"""
import os
import threading
import time
_CAMERA_LOCK = threading.Lock()
# DONA_VISION_V95 : partage une image récente avec la détection YOLOv8.
_FRAME_LOCK = threading.Lock()
_LAST_FRAME = None
_FRAME_TIMESTAMP = 0.0
def _cache_frame(frame):
global _LAST_FRAME, _FRAME_TIMESTAMP
with _FRAME_LOCK:
_LAST_FRAME = frame.copy()
_FRAME_TIMESTAMP = time.monotonic()
def read_frame(index=None, max_age=1.4):
"""Retourne une image de l'aperçu actif ou capture sans collision de caméra."""
import cv2
with _FRAME_LOCK:
if _LAST_FRAME is not None and time.monotonic() - _FRAME_TIMESTAMP <= max_age:
return _LAST_FRAME.copy()
if not _CAMERA_LOCK.acquire(blocking=False):
raise RuntimeError('Caméra occupée : active l’aperçu et réessaie, ou ferme l’autre application.')
cap = None
try:
cam_index = int(os.getenv('CAMERA_INDEX', '0')) if index is None else index
cap = cv2.VideoCapture(cam_index)
if not cap.isOpened():
raise RuntimeError('Caméra indisponible sur Dona.')
ok, frame = cap.read()
if not ok: raise RuntimeError('Capture caméra impossible.')
_cache_frame(frame)
return frame
finally:
if cap is not None: cap.release()
_CAMERA_LOCK.release()
def snapshot(path, index=0):
import cv2
frame = read_frame(index=index)
if not cv2.imwrite(path,frame): raise RuntimeError('Impossible d’enregistrer la capture.')
return path
def video_stream(index=0):
"""Ouvre la caméra, retourne un générateur de trames JPEG multipart.
L'appelant doit retourner ce générateur dans une réponse Flask en streaming.
L'absence de cv2 ou de webcam déclenche une exception avant HTTP 200.
"""
import cv2
if not _CAMERA_LOCK.acquire(blocking=False):
raise RuntimeError('Prévisualisation vidéo déjà active')
cap = None
try:
cap = cv2.VideoCapture(index)
if not cap.isOpened():
cap.release()
raise RuntimeError(f'Caméra {index} indisponible')
cap.set(cv2.CAP_PROP_FRAME_WIDTH, int(os.getenv('CAMERA_WIDTH', '640')))
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, int(os.getenv('CAMERA_HEIGHT', '480')))
fps = min(15.0, max(1.0, float(os.getenv('CAMERA_FPS', '8'))))
quality = min(95, max(35, int(os.getenv('CAMERA_JPEG_QUALITY', '75'))))
except Exception:
if cap is not None:
cap.release()
_CAMERA_LOCK.release()
raise
def frames():
try:
while True:
ok, frame = cap.read()
if not ok:
break
_cache_frame(frame) # DONA_VISION_V95 : vidéo et YOLO partagent la capture.
ok_jpeg, encoded = cv2.imencode('.jpg', frame, [cv2.IMWRITE_JPEG_QUALITY, quality])
if ok_jpeg:
yield b'--frame\r\nContent-Type: image/jpeg\r\n\r\n' + encoded.tobytes() + b'\r\n'
time.sleep(1.0 / fps)
finally:
cap.release()
_CAMERA_LOCK.release()
return frames()