Download face_classifier.py from VeuReu/engine: direct link, hf CLI and curl.
- Browser
- Download file 8.41 kB
-
https://huggingface.co/spaces/VeuReu/engine/resolve/cbee2ade83ef3fb1e5a8d2e3ef0c2f634da2fd5a/face_classifier.py
- Command line
-
hf download hf://spaces/VeuReu/engine@cbee2ade83ef3fb1e5a8d2e3ef0c2f634da2fd5a/face_classifier.py
-
curl -L -o face_classifier.py https://huggingface.co/spaces/VeuReu/engine/resolve/cbee2ade83ef3fb1e5a8d2e3ef0c2f634da2fd5a/face_classifier.py
8.41 kB
| """ | |
| 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) | |