"""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()