"""Analyse de luminosité sur les visages détectés dans le preview DSLR. Produit une note d'éclairage (0-100) et une recommandation (allumer/ok/sombre). Échantillonne toutes les ~3 secondes pour ne pas charger le CPU. """ import logging import time from pathlib import Path import cv2 import numpy as np log = logging.getLogger("photobooth.eclairage") _CASCADE_CANDIDATES = [ "/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml", "/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml", "/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_default.xml", ] try: _CASCADE_PATH = cv2.data.haarcascades + "haarcascade_frontalface_default.xml" except AttributeError: _CASCADE_PATH = next((p for p in _CASCADE_CANDIDATES if Path(p).exists()), _CASCADE_CANDIDATES[0]) _face_cascade = cv2.CascadeClassifier(_CASCADE_PATH) SEUIL_SOMBRE = 45 SEUIL_CORRECT = 65 _dernier_analyse: float = 0 _INTERVALLE = 3.0 _dernier_resultat: dict | None = None def analyser_frame(jpeg_data: bytes) -> dict | None: """Analyse une frame JPEG du preview. Retourne None si l'intervalle n'est pas écoulé (throttle). Sinon retourne: { "score": int 0-100, "action": "allumer" | "attention" | "ok", "visages": int, "detail": str, } """ global _dernier_analyse, _dernier_resultat now = time.monotonic() if now - _dernier_analyse < _INTERVALLE: return None _dernier_analyse = now try: arr = np.frombuffer(jpeg_data, dtype=np.uint8) img = cv2.imdecode(arr, cv2.IMREAD_COLOR) if img is None: return None gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = _face_cascade.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=4, minSize=(60, 60) ) if len(faces) == 0: _dernier_resultat = { "score": -1, "action": "no_face", "visages": 0, "detail": "Aucun visage détecté", } return _dernier_resultat hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) scores = [] for (x, y, w, h) in faces: pad_x = int(w * 0.1) pad_y = int(h * 0.1) x1 = max(0, x + pad_x) y1 = max(0, y + pad_y) x2 = min(img.shape[1], x + w - pad_x) y2 = min(img.shape[0], y + h - pad_y) roi_v = hsv[y1:y2, x1:x2, 2] score = int(np.mean(roi_v) / 2.55) scores.append(score) score_moyen = int(np.mean(scores)) if score_moyen < SEUIL_SOMBRE: action = "allumer" detail = f"Visages sombres ({score_moyen}/100)" elif score_moyen < SEUIL_CORRECT: action = "attention" detail = f"Éclairage limite ({score_moyen}/100)" else: action = "ok" detail = f"Éclairage correct ({score_moyen}/100)" _dernier_resultat = { "score": score_moyen, "action": action, "visages": len(faces), "detail": detail, } return _dernier_resultat except Exception as e: log.debug(f"Erreur analyse éclairage : {e}") return None def dernier_resultat() -> dict | None: return _dernier_resultat