""" Face Classifier Module Valida caras y detecta género usando DeepFace para filtrar falsos positivos y asignar nombres automáticos según el género detectado. """ import logging from typing import Optional, Dict, Any logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Configuración de thresholds # FACE_CONFIDENCE_THRESHOLD: Confianza mínima para aceptar una cara # Valores: 0.3 = permisivo (acepta muchos falsos positivos) # 0.6 = balanceado # 0.8 = estricto (elimina falsos positivos pero puede perder caras reales) # 0.85 = MUY estricto (solo caras muy claras) FACE_CONFIDENCE_THRESHOLD = 0.85 # MUY ESTRICTO: eliminar camisetas, letreros, etc. GENDER_NEUTRAL_THRESHOLD = 0.2 # Diferencia mínima para género neutro def validate_and_classify_face(image_path: str) -> Optional[Dict[str, Any]]: """ Valida si és una cara real i detecta el gènere usant DeepFace. Args: image_path: Ruta a la imagen de la cara Returns: Dict amb: { 'is_valid_face': bool, # True si és una cara amb confiança alta 'face_confidence': float, # Score de detecció de cara (0-1) 'gender': 'Man' | 'Woman' | 'Neutral', 'gender_confidence': float, # Score de confiança del gènere (0-1) 'man_prob': float, 'woman_prob': float } o None si falla completament """ try: import cv2 import numpy as np from deepface import DeepFace print(f"[DeepFace] Analitzant: {image_path}") # PREPROCESAMIENTO: Normalizar iluminación y mejorar contraste # Esto reduce el impacto de luz/oscuridad en la detección img = cv2.imread(str(image_path)) if img is None: print(f"[DeepFace] No se pudo cargar la imagen: {image_path}") return None # Convertir a escala de grises (más robusto para detección) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # CLAHE: Adaptive Histogram Equalization # Normaliza el contraste de forma local, reduciendo efectos de luz clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) normalized = clahe.apply(gray) # Volver a BGR para DeepFace normalized_bgr = cv2.cvtColor(normalized, cv2.COLOR_GRAY2BGR) # Guardar imagen preprocesada temporalmente import tempfile import os temp_dir = tempfile.gettempdir() temp_path = os.path.join(temp_dir, f"normalized_{os.path.basename(image_path)}") cv2.imwrite(temp_path, normalized_bgr) print(f"[DeepFace] Imagen preprocesada con CLAHE: {temp_path}") # Analitzar gènere amb detecció de cara (usando imagen normalizada) result = DeepFace.analyze( img_path=temp_path, actions=['gender'], enforce_detection=True, # Intentar detectar cara detector_backend='opencv', silent=True ) # Limpiar archivo temporal try: os.remove(temp_path) except: pass # DeepFace pot retornar llista si detecta múltiples cares if isinstance(result, list): print(f"[DeepFace] Resultado es lista con {len(result)} elementos") result = result[0] if result else None if not result: print(f"[DeepFace] No s'ha detectat cap cara") return { 'is_valid_face': False, 'face_confidence': 0.0, 'gender': 'Neutral', 'gender_confidence': 0.0, 'man_prob': 0.0, 'woman_prob': 0.0 } # LOG: Ver estructura completa del resultado print(f"[DeepFace] Resultado completo de analyze: {result}") # Extreure informació de gènere gender_info = result.get('gender', {}) print(f"[DeepFace] gender_info type: {type(gender_info)}, value: {gender_info}") if isinstance(gender_info, dict): # DeepFace retorna percentatges, convertir a 0-1 man_prob = gender_info.get('Man', 0) / 100.0 woman_prob = gender_info.get('Woman', 0) / 100.0 print(f"[DeepFace] Extraído de dict - Man: {man_prob:.3f}, Woman: {woman_prob:.3f}") else: # Fallback si el format és diferent print(f"[DeepFace] gender_info NO es dict, usando fallback 0.5/0.5") man_prob = 0.5 woman_prob = 0.5 # Determinar gènere basat en les probabilitats gender_diff = abs(man_prob - woman_prob) print(f"[DeepFace] Diferencia Man-Woman: {gender_diff:.3f} (threshold neutral={GENDER_NEUTRAL_THRESHOLD})") # Si la diferència és petita (< threshold), considerar neutre if gender_diff < GENDER_NEUTRAL_THRESHOLD: gender = 'Neutral' gender_confidence = 0.5 print(f"[DeepFace] → Asignado NEUTRAL (diferencia {gender_diff:.3f} < {GENDER_NEUTRAL_THRESHOLD})") else: gender = 'Man' if man_prob > woman_prob else 'Woman' gender_confidence = max(man_prob, woman_prob) print(f"[DeepFace] → Asignado {gender.upper()} (man_prob={man_prob:.3f}, woman_prob={woman_prob:.3f})") # Confiança de detecció de cara # DeepFace no proporciona score directamente en analyze(), pero si retornó resultado # asumimos que es cara válida con confianza alta face_confidence = result.get('face_confidence', 0.9) # Default alto si detecta # Si DeepFace va retornar resultat, assumir que és cara vàlida is_valid_face = True print(f"[DeepFace] ===== RESUMEN FINAL =====") print(f"[DeepFace] is_valid_face: {is_valid_face}") print(f"[DeepFace] face_confidence: {face_confidence:.3f}") print(f"[DeepFace] gender: {gender}") print(f"[DeepFace] gender_confidence: {gender_confidence:.3f}") print(f"[DeepFace] man_prob: {man_prob:.3f}") print(f"[DeepFace] woman_prob: {woman_prob:.3f}") print(f"[DeepFace] ==========================") return { 'is_valid_face': is_valid_face, 'face_confidence': face_confidence, 'gender': gender, 'gender_confidence': gender_confidence, 'man_prob': man_prob, 'woman_prob': woman_prob } except ValueError as e: # ValueError significa que no es va detectar cara print(f"[DeepFace] No s'ha detectat cara (ValueError): {e}") return { 'is_valid_face': False, 'face_confidence': 0.0, 'gender': 'Neutral', 'gender_confidence': 0.0, 'man_prob': 0.0, 'woman_prob': 0.0 } except Exception as e: print(f"[DeepFace] Error validant cara: {e}") return None def get_random_catalan_name_by_gender(gender: str, seed_value: str = "") -> str: """ Genera un nom català aleatori basat en el gènere. Args: gender: 'Man', 'Woman', o 'Neutral' seed_value: Valor per fer el random determinista (opcional) Returns: Nom català """ noms_home = [ "Jordi", "Marc", "Pau", "Pere", "Joan", "Josep", "David", "Guillem", "Albert", "Arnau", "Martí", "Bernat", "Oriol", "Roger", "Pol", "Lluís", "Sergi", "Carles", "Xavier" ] noms_dona = [ "Maria", "Anna", "Laura", "Marta", "Cristina", "Núria", "Montserrat", "Júlia", "Sara", "Carla", "Alba", "Elisabet", "Rosa", "Gemma", "Sílvia", "Teresa", "Irene", "Laia", "Marina", "Bet" ] noms_neutre = ["Àlex", "Andrea", "Francis", "Cris", "Noa"] # Seleccionar llista segons gènere if gender == 'Woman': noms = noms_dona elif gender == 'Man': noms = noms_home else: # Neutral noms = noms_neutre # Usar hash del seed per seleccionar nom de forma determinista if seed_value: hash_val = hash(seed_value) return noms[abs(hash_val) % len(noms)] else: import random return random.choice(noms)