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