| """ |
| 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__) |
|
|
| |
| |
| |
| |
| |
| |
| FACE_CONFIDENCE_THRESHOLD = 0.85 |
| GENDER_NEUTRAL_THRESHOLD = 0.2 |
|
|
|
|
| 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}") |
| |
| |
| |
| img = cv2.imread(str(image_path)) |
| if img is None: |
| print(f"[DeepFace] No se pudo cargar la imagen: {image_path}") |
| return None |
| |
| |
| gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) |
| |
| |
| |
| clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) |
| normalized = clahe.apply(gray) |
| |
| |
| normalized_bgr = cv2.cvtColor(normalized, cv2.COLOR_GRAY2BGR) |
| |
| |
| 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}") |
| |
| |
| result = DeepFace.analyze( |
| img_path=temp_path, |
| actions=['gender'], |
| enforce_detection=True, |
| detector_backend='opencv', |
| silent=True |
| ) |
| |
| |
| try: |
| os.remove(temp_path) |
| except: |
| pass |
| |
| |
| 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 |
| } |
| |
| |
| print(f"[DeepFace] Resultado completo de analyze: {result}") |
| |
| |
| gender_info = result.get('gender', {}) |
| print(f"[DeepFace] gender_info type: {type(gender_info)}, value: {gender_info}") |
| |
| if isinstance(gender_info, dict): |
| |
| 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: |
| |
| print(f"[DeepFace] gender_info NO es dict, usando fallback 0.5/0.5") |
| man_prob = 0.5 |
| woman_prob = 0.5 |
| |
| |
| gender_diff = abs(man_prob - woman_prob) |
| |
| print(f"[DeepFace] Diferencia Man-Woman: {gender_diff:.3f} (threshold neutral={GENDER_NEUTRAL_THRESHOLD})") |
| |
| |
| 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})") |
| |
| |
| |
| |
| face_confidence = result.get('face_confidence', 0.9) |
| |
| |
| 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: |
| |
| 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"] |
| |
| |
| if gender == 'Woman': |
| noms = noms_dona |
| elif gender == 'Man': |
| noms = noms_home |
| else: |
| noms = noms_neutre |
| |
| |
| if seed_value: |
| hash_val = hash(seed_value) |
| return noms[abs(hash_val) % len(noms)] |
| else: |
| import random |
| return random.choice(noms) |
|
|