jarondon82/ComputerVisionProject
1
1import streamlit as st2 3# Set page config with custom title and layout - DEBE SER EL PRIMER COMANDO STREAMLIT4st.set_page_config(5 page_title="Advanced Face & Feature Detection",6 page_icon="馃懁",7 layout="wide",8 initial_sidebar_state="expanded"9)10 11# Importaciones despu茅s de set_page_config12import cv213import numpy as np14from PIL import Image15from io import BytesIO16import base6417import tempfile18import os19import time20import urllib.request21import pandas as pd22import json23import matplotlib.pyplot as plt24import pickle25from sklearn.metrics.pairwise import cosine_similarity # type: ignore26 27# Importar m贸dulos opcionales que pueden no estar disponibles en todos los entornos28try:29 import av30 from streamlit_webrtc import webrtc_streamer, VideoProcessorBase, RTCConfiguration, WebRtcMode31 WEBRTC_AVAILABLE = True32except ImportError:33 WEBRTC_AVAILABLE = False34 st.warning("WebRTC components are not available. Real-time camera features will be disabled.")35 36# Importar las utilidades para la base de datos de rostros37try:38 from face_database_utils import save_face_database, load_face_database, export_database_json, import_database_json, print_database_info39 DATABASE_UTILS_AVAILABLE = True40except ImportError:41 DATABASE_UTILS_AVAILABLE = False42 st.warning("Database utilities are not available. Face recognition data will not be persistent between sessions.")43 44# Importar DeepFace para reconocimiento facial avanzado45try:46 from deepface import DeepFace47 DEEPFACE_AVAILABLE = True48except ImportError:49 DEEPFACE_AVAILABLE = False50 51# Import functions for face comparison52try:53 from face_comparison import compare_faces, compare_faces_embeddings, generate_comparison_report_english, draw_face_matches, extract_face_embeddings, extract_face_embeddings_all_models54 FACE_COMPARISON_AVAILABLE = True55except ImportError:56 FACE_COMPARISON_AVAILABLE = False57 st.warning("Face comparison functions are not available. Please check your installation.")58 59# Funci贸n principal que encapsula toda la aplicaci贸n60def main():61 # La configuraci贸n de la p谩gina ya se ha hecho al inicio del script, eliminar de aqu铆62 63 # Sidebar for navigation and controls64 st.sidebar.title("Controls & Settings")65 66 # Initialize session_state to store original image and camera state67 if 'original_image' not in st.session_state:68 st.session_state.original_image = None69 if 'camera_running' not in st.session_state:70 st.session_state.camera_running = False71 if 'feature_camera_running' not in st.session_state:72 st.session_state.feature_camera_running = False73 74 # Navigation menu75 app_mode = st.sidebar.selectbox(76 "Choose the app mode",77 ["About", "Face Detection", "Feature Detection", "Comparison Mode", "Face Recognition", "Diagn贸stico"]78 )79 80 # A帽adir mensaje destacado para guiar al usuario a la detecci贸n en tiempo real81 if app_mode != "Face Recognition":82 st.sidebar.warning("鈿狅笍 Para usar la detecci贸n facial en tiempo real, selecciona 'Face Recognition' y luego la pesta帽a 'Real-time Recognition'")83 84 # Function to load DNN models with caching and auto-download85 @st.cache_resource86 def load_face_model():87 # No need to create directory as we're using the root directory88 #89 #90 91 # Correct model file names92 modelFile = "res10_300x300_ssd_iter_140000.caffemodel"93 configFile = "deploy.prototxt.txt"94 95 # Check if files exist96 missing_files = []97 if not os.path.exists(modelFile):98 missing_files.append(modelFile)99 if not os.path.exists(configFile):100 missing_files.append(configFile)101 102 if missing_files:103 st.error("Missing model files: " + ", ".join(missing_files))104 st.error("Please manually download the following files:")105 st.code("""106 1. Download the model file:107 URL: https://raw.githubusercontent.com/sr6033/face-detection-with-OpenCV-and-DNN/master/res10_300x300_ssd_iter_140000.caffemodel108 Save as: res10_300x300_ssd_iter_140000.caffemodel109 110 2. Download the configuration file:111 URL: https://raw.githubusercontent.com/sr6033/face-detection-with-OpenCV-and-DNN/master/deploy.prototxt.txt112 Save as: deploy.prototxt.txt113 """)114 st.stop()115 116 # Load model117 try:118 net = cv2.dnn.readNetFromCaffe(configFile, modelFile)119 return net120 except Exception as e:121 st.error(f"Error loading model: {e}")122 st.stop()123 124 @st.cache_resource125 def load_feature_models():126 # Load pre-trained models for eye and smile detection127 eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')128 smile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml')129 return eye_cascade, smile_cascade130 131 # Function for detecting faces in an image132 def detect_face_dnn(net, frame, conf_threshold=0.3):133 """134 Detecta rostros en una imagen utilizando un modelo DNN pre-entrenado.135 Si no se detectan rostros, usa autom谩ticamente Haar Cascades como respaldo.136 137 Args:138 net: Modelo DNN cargado139 frame: Imagen en formato BGR140 conf_threshold: Umbral de confianza para la detecci贸n (0.0-1.0)141 142 Returns:143 Lista de bounding boxes con formato [x1, y1, x2, y2, confidence]144 o None si no se detectan rostros145 """146 # Crear un diagn贸stico m谩s detallado147 log_info = f"===== DIAGN脫STICO DE DETECCI脫N FACIAL =====\n"148 log_info += f"Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S')}\n"149 log_info += f"Tipo de modelo: {type(net)}\n"150 log_info += f"Forma de la imagen: {frame.shape}\n"151 152 # Forzar un umbral muy bajo para aumentar la sensibilidad153 internal_threshold = 0.05 # Usar este umbral internamente para mayor sensibilidad154 155 # A帽adir impresi贸n de depuraci贸n para el umbral usado156 print(f"Detecting faces with original threshold: {conf_threshold}, using internal threshold: {internal_threshold}")157 log_info += f"Umbral original: {conf_threshold}, umbral interno: {internal_threshold}\n"158 159 # Obtener dimensiones de la imagen160 h, w = frame.shape[:2]161 log_info += f"Dimensiones de imagen: {w}x{h}\n"162 163 # Crear un blob de la imagen (redimensionada a 300x300 y normalizada)164 # IMPORTANTE: Los valores de media (104.0, 177.0, 123.0) son espec铆ficos 165 # para el modelo res10_300x300_ssd_iter_140000.caffemodel entrenado en Caffe166 try:167 blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0,168 (300, 300), (104.0, 177.0, 123.0))169 log_info += f"Blob creado correctamente. Forma: {blob.shape}\n"170 except Exception as e:171 log_info += f"ERROR al crear blob: {str(e)}\n"172 with open("diagnostico_deteccion.txt", "a") as f:173 f.write(log_info)174 print(log_info)175 return detect_face_haar(frame, conf_threshold)176 177 # Pasar el blob a trav茅s de la red178 try:179 net.setInput(blob)180 log_info += "Input establecido correctamente en la red\n"181 except Exception as e:182 log_info += f"ERROR al establecer input: {str(e)}\n"183 with open("diagnostico_deteccion.txt", "a") as f:184 f.write(log_info)185 print(log_info)186 return detect_face_haar(frame, conf_threshold)187 188 # Realizar la detecci贸n (forward pass)189 try:190 detections = net.forward()191 log_info += f"Forward pass exitoso. Forma de las detecciones: {detections.shape}\n"192 except Exception as e:193 log_info += f"ERROR en forward pass: {str(e)}\n"194 with open("diagnostico_deteccion.txt", "a") as f:195 f.write(log_info)196 print(log_info)197 # Intentar con Haar cascade como respaldo198 print("Intentando detecci贸n con Haar cascade como respaldo...")199 return detect_face_haar(frame, conf_threshold)200 201 # Variable para almacenar las cajas delimitadoras202 bboxes = []203 204 # Procesar cada detecci贸n205 detection_count = 0206 detection_info = []207 208 for i in range(detections.shape[2]):209 # Extraer la confianza (probabilidad) de la detecci贸n210 confidence = detections[0, 0, i, 2]211 detection_info.append(f" {i}: confianza={confidence:.3f}")212 213 # Filtrar detecciones d茅biles por confianza (usando el umbral interno m谩s bajo)214 if confidence > internal_threshold:215 detection_count += 1216 # La red da las coordenadas de la caja normalizadas entre 0 y 1217 # Multiplicamos por ancho y alto para obtener coordenadas en p铆xeles218 box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])219 220 # Convertir a enteros221 x1, y1, x2, y2 = box.astype("int")222 223 # Garantizar que las coordenadas est茅n dentro de los l铆mites de la imagen224 x1, y1 = max(0, x1), max(0, y1)225 x2, y2 = min(w, x2), min(h, y2)226 227 # Imprimir informaci贸n de depuraci贸n228 print(f"Detecci贸n #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")229 detection_info[i] += f", bbox=[{x1},{y1},{x2},{y2}]"230 231 # Saltar cajas inv谩lidas (por ejemplo, con ancho o alto negativo)232 width, height = x2 - x1, y2 - y1233 if width <= 0 or height <= 0:234 print(f"Saltando caja inv谩lida con dimensiones: {width}x{height}")235 detection_info[i] += f" - INV脕LIDA: dimensiones {width}x{height}"236 continue237 238 # A帽adir la caja y la confianza a la lista de resultados239 bboxes.append([x1, y1, x2, y2, confidence])240 detection_info[i] += " - A脩ADIDA"241 242 # A帽adir informaci贸n de detecciones al log243 log_info += f"Detecciones totales analizadas: {detections.shape[2]}\n"244 log_info += "Detalles de detecciones:\n"245 for info in detection_info:246 log_info += f"{info}\n"247 248 # Dar feedback sobre el n煤mero de detecciones249 log_info += f"Total de detecciones con confianza > {internal_threshold}: {detection_count}\n"250 log_info += f"Total de cajas v谩lidas: {len(bboxes)}\n"251 252 # Si no se encontraron rostros, intentar con Haar cascade253 if not bboxes:254 log_info += "NO SE DETECTARON ROSTROS CON DNN\n"255 log_info += "Intentando detecci贸n con Haar cascade como respaldo...\n"256 257 # Verificar si hay detecciones con umbral m谩s bajo para depuraci贸n258 for i in range(detections.shape[2]):259 confidence = detections[0, 0, i, 2]260 if confidence > 0.01: # Umbral extremadamente bajo para depuraci贸n261 box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])262 x1, y1, x2, y2 = box.astype("int")263 log_info += f"Detecci贸n de baja confianza: {confidence:.3f} en [{x1},{y1},{x2},{y2}]\n"264 265 # Intentar detecci贸n Haar266 haar_bboxes = detect_face_haar(frame, conf_threshold)267 if haar_bboxes and len(haar_bboxes) > 0:268 log_info += f"Haar cascade encontr贸 {len(haar_bboxes)} rostro(s)\n"269 log_info += f"Haar bboxes: {haar_bboxes}\n"270 271 # Guardar diagn贸stico en archivo272 with open("diagnostico_deteccion.txt", "a") as f:273 f.write(log_info)274 print(log_info)275 return haar_bboxes276 277 log_info += "Haar cascade NO detect贸 rostros\n"278 279 # Guardar diagn贸stico en archivo cuando no hay detecciones280 with open("diagnostico_deteccion.txt", "a") as f:281 f.write(log_info)282 print(log_info)283 return []284 285 # Si llegamos aqu铆, hay detecciones exitosas286 log_info += f"Detecci贸n exitosa. Retornando {len(bboxes)} bounding boxes.\n"287 log_info += f"Bounding boxes: {bboxes}\n"288 289 # Guardar diagn贸stico en archivo290 with open("diagnostico_deteccion.txt", "a") as f:291 f.write(log_info)292 print(log_info)293 294 # Devolver las cajas detectadas295 return bboxes296 297 # Funci贸n alternativa para detectar rostros usando Haar Cascades298 def detect_face_haar(frame, conf_threshold=0.3):299 """Detecta rostros usando Haar Cascades como m茅todo de respaldo"""300 try:301 # Carga el clasificador Haar Cascade para rostros (deber铆a estar cargado globalmente,302 # pero lo hacemos aqu铆 para asegurar que est茅 disponible)303 if 'haar_face_cascade' not in st.session_state:304 cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'305 st.session_state.haar_face_cascade = cv2.CascadeClassifier(cascade_path)306 print(f"Haar cascade loaded from {cascade_path}")307 308 # Convertir a escala de grises309 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)310 311 # Ecualizar el histograma para mejorar contraste312 gray = cv2.equalizeHist(gray)313 314 # Par谩metros m谩s sensibles para la detecci贸n con Haar315 scale_factor = 1.05 # M谩s lento pero m谩s preciso (era 1.1)316 min_neighbors = 3 # Valor m谩s bajo, m谩s detecciones pero m谩s falsos positivos (era 5)317 min_size = (20, 20) # Tama帽o m铆nimo m谩s peque帽o (era 30, 30)318 319 # Detectar rostros con clasificador Haar320 faces = st.session_state.haar_face_cascade.detectMultiScale(321 gray,322 scaleFactor=scale_factor,323 minNeighbors=min_neighbors,324 minSize=min_size,325 flags=cv2.CASCADE_SCALE_IMAGE326 )327 328 # Convertir a formato bounding box [x1, y1, x2, y2, confianza]329 bboxes = []330 for (x, y, w, h) in faces:331 # Usar un valor de confianza fijo para las detecciones Haar332 confidence = 0.8 # Valor arbitrario alto333 bboxes.append([x, y, x + w, y + h, confidence])334 335 return bboxes336 337 except Exception as e:338 print(f"Error en detecci贸n Haar: {e}")339 return []340 341 # Function for processing face detections342 def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):343 # Create a copy for drawing on344 result_frame = frame.copy()345 346 # Asegurar que bbox_color sea una tupla de 3 elementos para BGR347 if isinstance(bbox_color, tuple) and len(bbox_color) == 3:348 bbox_color_bgr = bbox_color349 else:350 # Usar verde como color predeterminado351 bbox_color_bgr = (0, 255, 0)352 353 # Definir grosor para los rect谩ngulos (m谩s grueso para mejor visibilidad)354 thickness = 3355 356 # Procesar detecciones si son del formato original357 if isinstance(detections, np.ndarray) and len(detections.shape) == 4:358 bboxes = []359 frame_h = frame.shape[0]360 frame_w = frame.shape[1]361 362 for i in range(detections.shape[2]):363 confidence = detections[0, 0, i, 2]364 print(f"Confidence: {confidence}, Threshold: {conf_threshold}")365 366 # Usar un umbral muy bajo para mejorar la detecci贸n367 effective_threshold = max(0.05, conf_threshold)368 369 if confidence > effective_threshold:370 x1 = int(detections[0, 0, i, 3] * frame_w)371 y1 = int(detections[0, 0, i, 4] * frame_h)372 x2 = int(detections[0, 0, i, 5] * frame_w)373 y2 = int(detections[0, 0, i, 6] * frame_h)374 375 # Asegurarse de que las coordenadas est茅n dentro de los l铆mites376 x1 = max(0, min(x1, frame_w - 1))377 y1 = max(0, min(y1, frame_h - 1))378 x2 = max(0, min(x2, frame_w - 1))379 y2 = max(0, min(y2, frame_h - 1))380 381 # Verificar que el rect谩ngulo es v谩lido382 if x2 <= x1 or y2 <= y1:383 continue384 385 # Dibujar el bounding box con l铆nea m谩s gruesa386 cv2.rectangle(result_frame, (x1, y1), (x2, y2), bbox_color_bgr, thickness)387 388 # A帽adir texto con la confianza389 label = f"{confidence:.2f}"390 cv2.putText(result_frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, bbox_color_bgr, 2)391 392 # A帽adir a la lista de bounding boxes393 bboxes.append([x1, y1, x2, y2, confidence])394 else:395 # Si ya es una lista de bounding boxes (formato nuevo)396 bboxes = detections if detections is not None else []397 398 # Dibujar bounding boxes399 for bbox in bboxes:400 if len(bbox) == 5: # Asegurarse de que el bounding box tiene el formato correcto401 x1, y1, x2, y2, confidence = bbox402 403 # Usar un umbral bajo para la visualizaci贸n404 effective_threshold = max(0.05, conf_threshold)405 406 if confidence >= effective_threshold:407 # Verificar que las coordenadas son v谩lidas408 if x1 >= 0 and y1 >= 0 and x2 > x1 and y2 > y1:409 # Dibujar el bounding box con l铆nea m谩s gruesa410 cv2.rectangle(result_frame, (x1, y1), (x2, y2), bbox_color_bgr, thickness)411 412 # A帽adir texto con la confianza413 label = f"{confidence:.2f}"414 cv2.putText(result_frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, bbox_color_bgr, 2)415 416 return result_frame, bboxes417 418 # Function to detect facial features (eyes, smile) with improved profile face handling419 def detect_facial_features(frame, bboxes, eye_cascade, smile_cascade, detect_eyes=True, detect_smile=True, smile_sensitivity=15, eye_sensitivity=5):420 result_frame = frame.copy()421 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)422 423 # Counters for detection summary424 eye_count = 0425 smile_count = 0426 427 for bbox in bboxes:428 x1, y1, x2, y2, _ = bbox429 roi_gray = gray[y1:y2, x1:x2]430 roi_color = result_frame[y1:y2, x1:x2]431 face_width = x2 - x1432 face_height = y2 - y1433 434 # Detect eyes if enabled435 if detect_eyes:436 # Adjust region of interest to focus on the upper part of the face437 upper_face_y1 = y1438 upper_face_y2 = y1 + int(face_height * 0.55) # Slightly reduced to focus more on the eye area439 440 # For profile faces, we need to search the entire upper region441 # as well as the left and right sides separately442 443 # Full upper region for profile faces444 upper_face_roi_gray = gray[upper_face_y1:upper_face_y2, x1:x2]445 upper_face_roi_color = result_frame[upper_face_y1:upper_face_y2, x1:x2]446 447 # Split the upper region into two halves (left and right) to search for eyes individually448 mid_x = x1 + face_width // 2449 left_eye_roi_gray = gray[upper_face_y1:upper_face_y2, x1:mid_x]450 right_eye_roi_gray = gray[upper_face_y1:upper_face_y2, mid_x:x2]451 452 left_eye_roi_color = result_frame[upper_face_y1:upper_face_y2, x1:mid_x]453 right_eye_roi_color = result_frame[upper_face_y1:upper_face_y2, mid_x:x2]454 455 # Apply histogram equalization and contrast enhancement for all regions456 if upper_face_roi_gray.size > 0:457 upper_face_roi_gray = cv2.equalizeHist(upper_face_roi_gray)458 459 # Enhance contrast460 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))461 upper_face_roi_gray = clahe.apply(upper_face_roi_gray)462 463 # First try to detect eyes in the full upper region (for profile faces)464 full_eyes = eye_cascade.detectMultiScale(465 upper_face_roi_gray, 466 scaleFactor=1.02, # More sensitive for profile faces467 minNeighbors=max(1, eye_sensitivity-3), # Even more sensitive468 minSize=(int(face_width * 0.07), int(face_width * 0.07)),469 maxSize=(int(face_width * 0.3), int(face_width * 0.3))470 )471 472 # If we found eyes in the full region, use those473 if len(full_eyes) > 0:474 # Sort by size (area) and take up to 2 largest475 full_eyes = sorted(full_eyes, key=lambda e: e[2] * e[3], reverse=True)476 full_eyes = full_eyes[:2] # Take at most 2 eyes477 478 for ex, ey, ew, eh in full_eyes:479 eye_count += 1480 cv2.rectangle(upper_face_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)481 cv2.putText(upper_face_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)482 else:483 # If no eyes found in full region, try left and right separately484 if left_eye_roi_gray.size > 0:485 left_eye_roi_gray = cv2.equalizeHist(left_eye_roi_gray)486 left_eye_roi_gray = clahe.apply(left_eye_roi_gray)487 488 left_eyes = eye_cascade.detectMultiScale(489 left_eye_roi_gray, 490 scaleFactor=1.03,491 minNeighbors=max(1, eye_sensitivity-2),492 minSize=(int(face_width * 0.08), int(face_width * 0.08)),493 maxSize=(int(face_width * 0.25), int(face_width * 0.25))494 )495 496 if len(left_eyes) > 0:497 # Sort by size and take the largest498 left_eyes = sorted(left_eyes, key=lambda e: e[2] * e[3], reverse=True)499 left_eye = left_eyes[0]500 eye_count += 1501 502 # Draw rectangle for the left eye503 ex, ey, ew, eh = left_eye504 cv2.rectangle(left_eye_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)505 cv2.putText(left_eye_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)506 507 if right_eye_roi_gray.size > 0:508 right_eye_roi_gray = cv2.equalizeHist(right_eye_roi_gray)509 right_eye_roi_gray = clahe.apply(right_eye_roi_gray)510 511 right_eyes = eye_cascade.detectMultiScale(512 right_eye_roi_gray, 513 scaleFactor=1.03,514 minNeighbors=max(1, eye_sensitivity-2),515 minSize=(int(face_width * 0.08), int(face_width * 0.08)),516 maxSize=(int(face_width * 0.25), int(face_width * 0.25))517 )518 519 if len(right_eyes) > 0:520 # Sort by size and take the largest521 right_eyes = sorted(right_eyes, key=lambda e: e[2] * e[3], reverse=True)522 right_eye = right_eyes[0]523 eye_count += 1524 525 # Draw rectangle for the right eye526 ex, ey, ew, eh = right_eye527 cv2.rectangle(right_eye_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)528 cv2.putText(right_eye_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)529 530 # Detect smile if enabled531 if detect_smile:532 # For profile faces, we need to adjust the region of interest533 # Try multiple regions to improve detection534 535 # Standard region (middle to bottom)536 lower_face_y1 = y1 + int(face_height * 0.5)537 lower_face_roi_gray = gray[lower_face_y1:y2, x1:x2]538 lower_face_roi_color = result_frame[lower_face_y1:y2, x1:x2]539 540 # Alternative region (lower third)541 alt_lower_face_y1 = y1 + int(face_height * 0.65)542 alt_lower_face_roi_gray = gray[alt_lower_face_y1:y2, x1:x2]543 544 # Apply histogram equalization and enhance contrast545 smile_detected = False546 547 if lower_face_roi_gray.size > 0:548 lower_face_roi_gray = cv2.equalizeHist(lower_face_roi_gray)549 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))550 lower_face_roi_gray = clahe.apply(lower_face_roi_gray)551 552 # Try with standard parameters553 smiles = smile_cascade.detectMultiScale(554 lower_face_roi_gray, 555 scaleFactor=1.2,556 minNeighbors=smile_sensitivity,557 minSize=(int(face_width * 0.25), int(face_width * 0.15)),558 maxSize=(int(face_width * 0.7), int(face_width * 0.4))559 )560 561 if len(smiles) > 0:562 # Sort by size and take the largest563 smiles = sorted(smiles, key=lambda s: s[2] * s[3], reverse=True)564 sx, sy, sw, sh = smiles[0]565 566 # Increment smile counter567 smile_count += 1568 smile_detected = True569 570 # Draw rectangle for the smile571 cv2.rectangle(lower_face_roi_color, (sx, sy), (sx+sw, sy+sh), (0, 0, 255), 2)572 cv2.putText(lower_face_roi_color, "Smile", (sx, sy-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)573 574 # If no smile detected in standard region, try alternative region575 if not smile_detected and alt_lower_face_roi_gray.size > 0:576 alt_lower_face_roi_gray = cv2.equalizeHist(alt_lower_face_roi_gray)577 alt_lower_face_roi_gray = clahe.apply(alt_lower_face_roi_gray)578 579 # Try with more sensitive parameters580 alt_smiles = smile_cascade.detectMultiScale(581 alt_lower_face_roi_gray, 582 scaleFactor=1.1,583 minNeighbors=max(1, smile_sensitivity-5), # More sensitive584 minSize=(int(face_width * 0.2), int(face_width * 0.1)),585 maxSize=(int(face_width * 0.6), int(face_width * 0.3))586 )587 588 if len(alt_smiles) > 0:589 # Sort by size and take the largest590 alt_smiles = sorted(alt_smiles, key=lambda s: s[2] * s[3], reverse=True)591 sx, sy, sw, sh = alt_smiles[0]592 593 # Adjust coordinates for the alternative region594 adjusted_sy = sy + (alt_lower_face_y1 - lower_face_y1)595 596 # Increment smile counter597 smile_count += 1598 599 # Draw rectangle for the smile (in the original lower face ROI)600 cv2.rectangle(lower_face_roi_color, (sx, adjusted_sy), (sx+sw, adjusted_sy+sh), (0, 0, 255), 2)601 cv2.putText(lower_face_roi_color, "Smile", (sx, adjusted_sy-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)602 603 return result_frame, eye_count, smile_count604 605 # Funci贸n para detectar atributos faciales (edad, g茅nero, emoci贸n)606 def detect_face_attributes(image, bbox):607 """608 Detecta atributos faciales como edad, g茅nero y emoci贸n usando DeepFace.609 610 Args:611 image: Imagen en formato OpenCV (BGR)612 bbox: Bounding box de la cara [x1, y1, x2, y2, conf]613 614 Returns:615 Diccionario con los atributos detectados616 """617 if not DEEPFACE_AVAILABLE:618 return None619 620 try:621 x1, y1, x2, y2, _ = bbox622 face_img = image[y1:y2, x1:x2]623 624 # Convertir de BGR a RGB para DeepFace625 face_img_rgb = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)626 627 # Analyze atributos faciales628 attributes = DeepFace.analyze(629 img_path=face_img_rgb,630 actions=['age', 'gender', 'emotion'],631 enforce_detection=False,632 detector_backend="opencv"633 )634 635 return attributes[0]636 637 except Exception as e:638 st.error(f"Error detecting facial attributes: {str(e)}")639 return None640 641 # Function to apply age and gender detection (placeholder - would need additional models)642 def detect_age_gender(frame, bboxes):643 # Versi贸n mejorada que usa DeepFace si est谩 disponible644 result_frame = frame.copy()645 646 for i, bbox in enumerate(bboxes):647 x1, y1, x2, y2, _ = bbox648 649 if DEEPFACE_AVAILABLE:650 # Intentar usar DeepFace para an谩lisis facial651 attributes = detect_face_attributes(frame, bbox)652 653 if attributes:654 # Extraer informaci贸n de atributos655 age = attributes.get('age', 'Unknown')656 gender = attributes.get('gender', 'Unknown')657 emotion = attributes.get('dominant_emotion', 'Unknown').capitalize()658 gender_prob = attributes.get('gender', {}).get('Woman', 0)659 660 # Determinar color basado en confianza661 if gender == 'Woman':662 gender_color = (255, 0, 255) # Magenta para mujer663 else:664 gender_color = (255, 0, 0) # Azul para hombre665 666 # A帽adir texto con informaci贸n667 cv2.putText(result_frame, f"Age: {age}", (x1, y2+20), 668 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)669 cv2.putText(result_frame, f"Gender: {gender}", (x1, y2+40), 670 cv2.FONT_HERSHEY_SIMPLEX, 0.5, gender_color, 2)671 cv2.putText(result_frame, f"Emotion: {emotion}", (x1, y2+60), 672 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2)673 else:674 # Fallback si DeepFace falla675 cv2.putText(result_frame, "Age: Unknown", (x1, y2+20), 676 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)677 cv2.putText(result_frame, "Gender: Unknown", (x1, y2+40), 678 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)679 else:680 # Usar texto placeholder si DeepFace no est谩 disponible681 cv2.putText(result_frame, "Age: 25-35", (x1, y2+20), 682 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)683 cv2.putText(result_frame, "Gender: Unknown", (x1, y2+40), 684 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)685 686 return result_frame687 688 # Function to generate download link for processed image689 def get_image_download_link(img, filename, text):690 buffered = BytesIO()691 img.save(buffered, format="JPEG")692 img_str = base64.b64encode(buffered.getvalue()).decode()693 href = f'<a href="data:file/txt;base64,{img_str}" download="{filename}">{text}</a>'694 return href695 696 # Function to process video frames697 def process_video(video_path, face_net, eye_cascade, smile_cascade, conf_threshold=0.5, detect_eyes=True, detect_smile=True, bbox_color=(0, 255, 0), smile_sensitivity=15, eye_sensitivity=5):698 cap = cv2.VideoCapture(video_path)699 700 # Get video properties701 frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))702 frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))703 fps = int(cap.get(cv2.CAP_PROP_FPS))704 705 # Create temporary output file706 temp_dir = tempfile.mkdtemp()707 temp_output_path = os.path.join(temp_dir, "processed_video.mp4")708 709 # Initialize video writer710 fourcc = cv2.VideoWriter_fourcc(*'mp4v')711 out = cv2.VideoWriter(temp_output_path, fourcc, fps, (frame_width, frame_height))712 713 # Create a progress bar714 frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))715 progress_bar = st.progress(0)716 status_text = st.empty()717 718 # Process video frames719 current_frame = 0720 processing_times = []721 722 # Total counters for statistics723 total_faces = 0724 total_eyes = 0725 total_smiles = 0726 727 while cap.isOpened():728 ret, frame = cap.read()729 if not ret:730 break731 732 # Start timing for performance metrics733 start_time = time.time()734 735 # Detect faces736 detections = detect_face_dnn(face_net, frame, conf_threshold)737 processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color)738 739 # Update face counter740 total_faces += len(bboxes)741 742 # Detect facial features if enabled743 if detect_eyes or detect_smile:744 processed_frame, eye_count, smile_count = detect_facial_features(745 processed_frame, 746 bboxes, 747 eye_cascade, 748 smile_cascade,749 detect_eyes,750 detect_smile,751 smile_sensitivity,752 eye_sensitivity753 )754 # Update counters755 total_eyes += eye_count756 total_smiles += smile_count757 758 # End timing759 processing_times.append(time.time() - start_time)760 761 # Write the processed frame762 out.write(processed_frame)763 764 # Update progress765 current_frame += 1766 progress_bar.progress(current_frame / frame_count)767 status_text.text(f"Processing frame {current_frame}/{frame_count}")768 769 # Release resources770 cap.release()771 out.release()772 773 # Calculate and display performance metrics774 if processing_times:775 avg_time = sum(processing_times) / len(processing_times)776 status_text.text(f"Processing complete! Average processing time: {avg_time:.4f}s per frame")777 778 # Return detection statistics779 detection_stats = {780 "faces": total_faces // max(1, current_frame), # Average per frame781 "eyes": total_eyes // max(1, current_frame), # Average per frame782 "smiles": total_smiles // max(1, current_frame) # Average per frame783 }784 785 return temp_output_path, temp_dir, detection_stats786 787 # Camera control functions788 def start_camera():789 st.session_state.camera_running = True790 791 def stop_camera():792 st.session_state.camera_running = False793 st.session_state.camera_stopped = True794 795 def start_feature_camera():796 st.session_state.feature_camera_running = True797 798 def stop_feature_camera():799 st.session_state.feature_camera_running = False800 st.session_state.feature_camera_stopped = True801 802 # Funci贸n auxiliar para verificar si una imagen es v谩lida antes de redimensionar803 def is_valid_image(img):804 if img is None:805 return False806 try:807 # Verificar que la imagen tenga dimensiones v谩lidas y datos808 return img.size > 0 and len(img.shape) >= 2 and img.shape[0] > 0 and img.shape[1] > 0809 except Exception:810 return False811 812 # Funci贸n auxiliar para redimensionar de forma segura813 def safe_resize(img, target_size):814 if is_valid_image(img):815 try:816 return cv2.resize(img, target_size)817 except Exception as e:818 print(f"Error al redimensionar: {str(e)}")819 return None820 return None821 822 if app_mode == "About":823 st.markdown("""824 ## About This App825 826 This application uses OpenCV's Deep Neural Network (DNN) module and Haar Cascade classifiers to detect faces and facial features in images and videos.827 828 ### Features:829 - Face detection using OpenCV DNN830 - Eye and smile detection using Haar Cascades831 - Support for both image and video processing832 - Adjustable confidence threshold833 - Download options for processed media834 - Performance metrics835 836 ### How to use:837 1. Select a mode from the sidebar838 2. Upload an image or video839 3. Adjust settings as needed840 4. View and download the results841 842 ### Technologies Used:843 - Streamlit for the web interface844 - OpenCV for computer vision operations845 - Python for backend processing846 847 ### Models:848 - SSD MobileNet for face detection849 - Haar Cascades for facial features850 """)851 852 # Display a sample image or GIF853 st.image("https://opencv.org/wp-content/uploads/2019/07/detection.gif", caption="Sample face detection", use_container_width=True)854 855 elif app_mode == "Face Detection":856 # Load the face detection model857 face_net = load_face_model()858 859 # Input type selection (Image or Video)860 input_type = st.sidebar.radio("Select Input Type", ["Image", "Video"])861 862 # Confidence threshold slider863 conf_threshold = st.sidebar.slider(864 "Confidence Threshold", 865 min_value=0.0, 866 max_value=1.0, 867 value=0.5, 868 step=0.05,869 help="Adjust the threshold for face detection confidence (higher = fewer detections but more accurate)"870 )871 872 # Style options873 bbox_color = st.sidebar.color_picker("Bounding Box Color", "#00FF00")874 # Convert hex color to BGR for OpenCV875 bbox_color_rgb = tuple(int(bbox_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))876 bbox_color_bgr = (bbox_color_rgb[2], bbox_color_rgb[1], bbox_color_rgb[0]) # Convert RGB to BGR877 878 # Display processing metrics879 show_metrics = st.sidebar.checkbox("Show Processing Metrics", True)880 881 if input_type == "Image":882 # File uploader for images883 file_buffer = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png'])884 885 # Umbral de confianza ajustable886 conf_threshold = st.slider(887 "Umbral de confianza",888 min_value=0.05,889 max_value=0.95,890 value=0.2, # Valor por defecto m谩s bajo (era 0.5)891 step=0.05,892 help="Ajusta este valor para controlar la sensibilidad de la detecci贸n facial. Un valor m谩s bajo detecta m谩s rostros pero puede tener falsos positivos."893 )894 895 # Color del bounding box896 bbox_color_bgr = (0, 255, 0) # Verde brillante para mejor visibilidad897 898 if file_buffer is not None:899 # Read the file and convert it to OpenCV format900 raw_bytes = np.asarray(bytearray(file_buffer.read()), dtype=np.uint8)901 image = cv2.imdecode(raw_bytes, cv2.IMREAD_COLOR)902 903 # Save la imagen original en session_state para reprocesarla cuando cambie el umbral904 # Usar un identificador 煤nico para cada archivo para detectar cambios905 file_id = file_buffer.name + str(file_buffer.size)906 907 if 'file_id' not in st.session_state or st.session_state.file_id != file_id:908 st.session_state.file_id = file_id909 st.session_state.original_image = image.copy()910 911 # Display original image912 col1, col2 = st.columns(2)913 with col1:914 st.subheader("Original Image")915 st.image(st.session_state.original_image, channels='BGR', use_container_width=True)916 917 # Start timing for performance metrics918 start_time = time.time()919 920 # Detect faces921 detections = detect_face_dnn(face_net, st.session_state.original_image, conf_threshold)922 processed_image, bboxes = process_face_detections(st.session_state.original_image, detections, conf_threshold, bbox_color_bgr)923 924 # Calculate processing time925 processing_time = time.time() - start_time926 927 # Display the processed image928 with col2:929 st.subheader("Processed Image")930 st.image(processed_image, channels='BGR', use_container_width=True)931 932 # Mostrar mensaje sobre lo que se est谩 viendo933 if len(bboxes) > 0:934 st.success(f"Se detectaron {len(bboxes)} rostros en la imagen.")935 else:936 st.warning("No se detectaron rostros. Prueba ajustar el umbral de confianza o usar otra imagen.")937 938 # Convert OpenCV image to PIL for download939 pil_img = Image.fromarray(processed_image[:, :, ::-1])940 st.markdown(941 get_image_download_link(pil_img, "face_detection_result.jpg", "馃摜 Download Processed Image"),942 unsafe_allow_html=True943 )944 945 else: # Video mode946 # Video mode options947 video_source = st.radio("Select video source", ["Upload video", "Use webcam"])948 949 if video_source == "Upload video":950 # File uploader for videos951 file_buffer = st.file_uploader("Upload a video", type=['mp4', 'avi', 'mov'])952 953 if file_buffer is not None:954 # Save uploaded video to temporary file955 temp_dir = tempfile.mkdtemp()956 temp_path = os.path.join(temp_dir, "input_video.mp4")957 958 with open(temp_path, "wb") as f:959 f.write(file_buffer.read())960 961 # Display original video962 st.subheader("Original Video")963 st.video(temp_path)964 965 # Load models for feature detection (will be used in the processing)966 eye_cascade, smile_cascade = load_feature_models()967 968 # Process video button969 if st.button("Process Video"):970 with st.spinner("Processing video... This may take a while depending on the video length."):971 # Process the video972 output_path, output_dir, detection_stats = process_video(973 temp_path, 974 face_net, 975 eye_cascade,976 smile_cascade,977 conf_threshold,978 detect_eyes=True,979 detect_smile=True,980 bbox_color=bbox_color_bgr,981 eye_sensitivity=5982 )983 984 # Display processed video985 st.subheader("Processed Video")986 st.video(output_path)987 988 # Mostrar estad铆sticas de detecci贸n989 st.subheader("Detection Summary")990 summary_col1, summary_col2, summary_col3 = st.columns(3)991 summary_col1.metric("Avg. Faces per Frame", detection_stats["faces"])992 993 if detect_eyes: # type: ignore994 summary_col2.metric("Avg. Eyes per Frame", detection_stats["eyes"])995 else:996 summary_col2.metric("Avg. Eyes Detected", "N/A")997 998 if detect_smile: # type: ignore999 summary_col3.metric("Avg. Smiles per Frame", detection_stats["smiles"])1000 else:1001 summary_col3.metric("Avg. Smiles Detected", "N/A")1002 1003 # Provide download link1004 with open(output_path, 'rb') as f:1005 video_bytes = f.read()1006 1007 st.download_button(1008 label="馃摜 Download Processed Video",1009 data=video_bytes,1010 file_name="processed_video.mp4",1011 mime="video/mp4"1012 )1013 1014 # Clean up temporary files1015 try:1016 os.remove(temp_path)1017 os.remove(output_path)1018 os.rmdir(temp_dir)1019 os.rmdir(output_dir)1020 except:1021 pass1022 else: # Use webcam1023 st.subheader("Real-time face detection")1024 st.write("Click 'Start Camera' to begin real-time face detection.")1025 1026 # Verificar si WebRTC est谩 disponible1027 if not WEBRTC_AVAILABLE:1028 st.error("WebRTC components are not available. Real-time camera features are disabled.")1029 st.warning("鈿狅笍 Note: If you're using this app on Hugging Face Spaces without WebRTC support, try using the image upload or video upload features instead.")1030 else:1031 # Placeholder for webcam video1032 camera_placeholder = st.empty()1033 1034 # Buttons to control the camera1035 col1, col2 = st.columns(2)1036 start_button = col1.button("Start Camera", on_click=start_camera)1037 stop_button = col2.button("Stop Camera", on_click=stop_camera)1038 1039 # Show message when camera is stopped1040 if 'camera_stopped' in st.session_state and st.session_state.camera_stopped:1041 st.info("Camera stopped. Click 'Start Camera' to activate it again.")1042 st.session_state.camera_stopped = False1043 1044 if st.session_state.camera_running:1045 st.info("Camera activated. Processing real-time video...")1046 # Initialize webcam1047 cap = cv2.VideoCapture(0) # 0 is typically the main webcam1048 1049 if not cap.isOpened():1050 st.error("Could not access webcam. Make sure it's connected and not being used by another application.")1051 st.warning("鈿狅笍 Note: If you're using this app on Hugging Face Spaces, webcam access is not supported. Try running this app locally for webcam features.")1052 st.session_state.camera_running = False1053 else:1054 # Display real-time video with face detection1055 try:1056 while st.session_state.camera_running:1057 ret, frame = cap.read()1058 if not ret:1059 st.error("Error reading frame from camera.")1060 break1061 1062 # Detect faces1063 detections = detect_face_dnn(face_net, frame, conf_threshold)1064 processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color_bgr)1065 1066 # Display the processed frame1067 camera_placeholder.image(processed_frame, channels="BGR", use_container_width=True)1068 1069 # Small pause to avoid overloading the CPU1070 time.sleep(0.01)1071 finally:1072 # Release the camera when stopped1073 cap.release()1074 1075 elif app_mode == "Feature Detection":1076 # Load all required models1077 face_net = load_face_model()1078 eye_cascade, smile_cascade = load_feature_models()1079 1080 # Feature selection checkboxes1081 st.sidebar.subheader("Feature Detection Options")1082 detect_eyes = st.sidebar.checkbox("Detect Eyes", True)1083 1084 # Add controls for eye detection sensitivity1085 eye_sensitivity = 5 # Default value1086 if detect_eyes:1087 eye_sensitivity = st.sidebar.slider(1088 "Eye Detection Sensitivity", 1089 min_value=1, 1090 max_value=10, 1091 value=5, 1092 step=1,1093 help="Adjust the sensitivity of eye detection (lower value = more detections)"1094 )1095 1096 detect_smile = st.sidebar.checkbox("Detect Smile", True)1097 1098 # Add controls for smile detection sensitivity1099 smile_sensitivity = 15 # Default value1100 if detect_smile:1101 smile_sensitivity = st.sidebar.slider(1102 "Smile Detection Sensitivity", 1103 min_value=5, 1104 max_value=30, 1105 value=15, 1106 step=1,1107 help="Adjust the sensitivity of smile detection (lower value = more detections)"1108 )1109 1110 detect_age_gender_option = st.sidebar.checkbox("Detect Age/Gender (Demo)", False)1111 1112 # Confidence threshold slider1113 conf_threshold = st.sidebar.slider(1114 "Face Detection Confidence", 1115 min_value=0.0, 1116 max_value=1.0, 1117 value=0.5, 1118 step=0.051119 )1120 1121 # Style options1122 bbox_color = st.sidebar.color_picker("Bounding Box Color", "#00FF00")1123 # Convert hex color to BGR for OpenCV1124 bbox_color_rgb = tuple(int(bbox_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))1125 bbox_color_bgr = (bbox_color_rgb[2], bbox_color_rgb[1], bbox_color_rgb[0]) # Convert RGB to BGR1126 1127 # Input type selection1128 input_type = st.sidebar.radio("Select Input Type", ["Image", "Video"])1129 1130 if input_type == "Image":1131 # File uploader for images1132 file_buffer = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png'])1133 1134 # Umbral de confianza ajustable1135 conf_threshold = st.slider(1136 "Umbral de confianza",1137 min_value=0.05,1138 max_value=0.95,1139 value=0.2, # Valor por defecto m谩s bajo (era 0.5)1140 step=0.05,1141 help="Ajusta este valor para controlar la sensibilidad de la detecci贸n facial. Un valor m谩s bajo detecta m谩s rostros pero puede tener falsos positivos."1142 )1143 1144 # Color del bounding box1145 bbox_color_bgr = (0, 255, 0) # Verde brillante para mejor visibilidad1146 1147 if file_buffer is not None:1148 # Read the file and convert it to OpenCV format1149 raw_bytes = np.asarray(bytearray(file_buffer.read()), dtype=np.uint8)1150 image = cv2.imdecode(raw_bytes, cv2.IMREAD_COLOR)1151 1152 # Save la imagen original en session_state para reprocesarla cuando cambie el umbral1153 # Usar un identificador 煤nico para cada archivo para detectar cambios1154 file_id = file_buffer.name + str(file_buffer.size)1155 1156 if 'feature_file_id' not in st.session_state or st.session_state.feature_file_id != file_id:1157 st.session_state.feature_file_id = file_id1158 st.session_state.feature_original_image = image.copy()1159 1160 # Display original image1161 col1, col2 = st.columns(2)1162 with col1:1163 st.subheader("Original Image")1164 st.image(st.session_state.feature_original_image, channels='BGR', use_container_width=True)1165 1166 # Start processing with face detection1167 detections = detect_face_dnn(face_net, st.session_state.feature_original_image, conf_threshold)1168 processed_image, bboxes = process_face_detections(st.session_state.feature_original_image, detections, conf_threshold, bbox_color_bgr)1169 1170 # Inicializar contadores1171 eye_count = 01172 smile_count = 01173 1174 # Detect facial features if any options are enabled1175 if detect_eyes or detect_smile:1176 processed_image, eye_count, smile_count = detect_facial_features(1177 processed_image, 1178 bboxes,1179 eye_cascade,1180 smile_cascade,1181 detect_eyes,1182 detect_smile,1183 smile_sensitivity,1184 eye_sensitivity1185 )1186 1187 # Apply age/gender detection if enabled (demo purpose)1188 if detect_age_gender_option:1189 processed_image = detect_age_gender(processed_image, bboxes)1190 1191 # Display the processed image1192 with col2:1193 st.subheader("Processed Image")1194 st.image(processed_image, channels='BGR', use_container_width=True)1195 1196 # Mostrar mensaje sobre lo que se est谩 viendo1197 if len(bboxes) > 0:1198 st.success(f"Se detectaron {len(bboxes)} rostros en la imagen.")1199 else:1200 st.warning("No se detectaron rostros. Prueba ajustar el umbral de confianza o usar otra imagen.")