jarondon82/ComputerVisionProject
1
1import streamlit as st2import cv23import numpy as np4from PIL import Image5from io import BytesIO6import base647import tempfile8import os9import time10import urllib.request11import matplotlib.pyplot as plt12import pickle13from sklearn.metrics.pairwise import cosine_similarity # type: ignore14import pandas as pd15import av16from streamlit_webrtc import webrtc_streamer, VideoProcessorBase, RTCConfiguration, WebRtcMode17 18# Importar las utilidades para la base de datos de rostros19try:20 from face_database_utils import save_face_database, load_face_database, export_database_json, import_database_json, print_database_info21 DATABASE_UTILS_AVAILABLE = True22except ImportError:23 DATABASE_UTILS_AVAILABLE = False24 st.warning("Database utilities are not available. Face recognition data will not be persistent between sessions.")25 26# Importar DeepFace para reconocimiento facial avanzado27try:28 from deepface import DeepFace29 DEEPFACE_AVAILABLE = True30except ImportError:31 DEEPFACE_AVAILABLE = False32 33# Import functions for face comparison34try:35 from face_comparison import compare_faces, compare_faces_embeddings, generate_comparison_report_english, draw_face_matches, extract_face_embeddings, extract_face_embeddings_all_models36 FACE_COMPARISON_AVAILABLE = True37except ImportError:38 FACE_COMPARISON_AVAILABLE = False39 st.warning("Face comparison functions are not available. Please check your installation.")40 41# Función principal que encapsula toda la aplicación42def main():43 # Set page config with custom title and layout44 st.set_page_config(45 page_title="Advanced Face & Feature Detection",46 page_icon="👤",47 layout="wide",48 initial_sidebar_state="expanded"49 )50 51 # Sidebar for navigation and controls52 st.sidebar.title("Controls & Settings")53 54 # Initialize session_state to store original image and camera state55 if 'original_image' not in st.session_state:56 st.session_state.original_image = None57 if 'camera_running' not in st.session_state:58 st.session_state.camera_running = False59 if 'feature_camera_running' not in st.session_state:60 st.session_state.feature_camera_running = False61 62 # Navigation menu63 app_mode = st.sidebar.selectbox(64 "Choose the app mode",65 ["About", "Face Detection", "Feature Detection", "Comparison Mode", "Face Recognition"]66 )67 68 # Function to load DNN models with caching and auto-download69 @st.cache_resource70 def load_face_model():71 # No need to create directory as we're using the root directory72 #73 #74 75 # Correct model file names76 modelFile = "res10_300x300_ssd_iter_140000.caffemodel"77 configFile = "deploy.prototxt.txt"78 79 # Check if files exist80 missing_files = []81 if not os.path.exists(modelFile):82 missing_files.append(modelFile)83 if not os.path.exists(configFile):84 missing_files.append(configFile)85 86 if missing_files:87 st.error("Missing model files: " + ", ".join(missing_files))88 st.error("Please manually download the following files:")89 st.code("""90 1. Download the model file:91 URL: https://raw.githubusercontent.com/sr6033/face-detection-with-OpenCV-and-DNN/master/res10_300x300_ssd_iter_140000.caffemodel92 Save as: res10_300x300_ssd_iter_140000.caffemodel93 94 2. Download the configuration file:95 URL: https://raw.githubusercontent.com/sr6033/face-detection-with-OpenCV-and-DNN/master/deploy.prototxt.txt96 Save as: deploy.prototxt.txt97 """)98 st.stop()99 100 # Load model101 try:102 net = cv2.dnn.readNetFromCaffe(configFile, modelFile)103 return net104 except Exception as e:105 st.error(f"Error loading model: {e}")106 st.stop()107 108 @st.cache_resource109 def load_feature_models():110 # Load pre-trained models for eye and smile detection111 eye_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')112 smile_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml')113 return eye_cascade, smile_cascade114 115 # Function for detecting faces in an image116 def detect_face_dnn(net, frame, conf_threshold=0.5):117 """118 Detecta rostros usando el modelo DNN y devuelve las detecciones.119 """120 try:121 # Verificar que el frame sea válido122 if frame is None or frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0:123 return []124 125 # Crear blob a partir del frame (redimensionar a 300x300, escalar, etc.)126 blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), [104, 117, 123], False, False)127 128 # Establecer la entrada para la red neuronal129 net.setInput(blob)130 131 # Realizar la detección132 detections = net.forward()133 134 # Procesar las detecciones para devolver una lista de bounding boxes135 bboxes = []136 frame_h = frame.shape[0]137 frame_w = frame.shape[1]138 139 for i in range(detections.shape[2]):140 confidence = detections[0, 0, i, 2]141 if confidence > conf_threshold:142 x1 = int(detections[0, 0, i, 3] * frame_w)143 y1 = int(detections[0, 0, i, 4] * frame_h)144 x2 = int(detections[0, 0, i, 5] * frame_w)145 y2 = int(detections[0, 0, i, 6] * frame_h)146 147 # Asegurarse de que las coordenadas estén dentro de los límites148 x1 = max(0, min(x1, frame_w - 1))149 y1 = max(0, min(y1, frame_h - 1))150 x2 = max(0, min(x2, frame_w - 1))151 y2 = max(0, min(y2, frame_h - 1))152 153 # Añadir el bounding box y la confianza154 bboxes.append([x1, y1, x2, y2, confidence])155 156 return bboxes157 except Exception as e:158 st.error(f"Error en la detección de rostros: {e}")159 return []160 161 # Function for processing face detections162 def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):163 # Create a copy for drawing on164 result_frame = frame.copy()165 166 # Procesar detecciones si son del formato original167 if isinstance(detections, np.ndarray) and len(detections.shape) == 4:168 bboxes = []169 frame_h = frame.shape[0]170 frame_w = frame.shape[1]171 172 for i in range(detections.shape[2]):173 confidence = detections[0, 0, i, 2]174 if confidence > conf_threshold:175 x1 = int(detections[0, 0, i, 3] * frame_w)176 y1 = int(detections[0, 0, i, 4] * frame_h)177 x2 = int(detections[0, 0, i, 5] * frame_w)178 y2 = int(detections[0, 0, i, 6] * frame_h)179 180 # Asegurarse de que las coordenadas estén dentro de los límites181 x1 = max(0, min(x1, frame_w - 1))182 y1 = max(0, min(y1, frame_h - 1))183 x2 = max(0, min(x2, frame_w - 1))184 y2 = max(0, min(y2, frame_h - 1))185 186 # Dibujar el bounding box187 cv2.rectangle(result_frame, (x1, y1), (x2, y2), bbox_color, 2)188 189 # Añadir texto con la confianza190 label = f"{confidence:.2f}"191 cv2.putText(result_frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, bbox_color, 2)192 193 # Añadir a la lista de bounding boxes194 bboxes.append([x1, y1, x2, y2, confidence])195 else:196 # Si ya es una lista de bounding boxes (formato nuevo)197 bboxes = detections198 # Dibujar bounding boxes199 for bbox in bboxes:200 if len(bbox) == 5: # Asegurarse de que el bounding box tiene el formato correcto201 x1, y1, x2, y2, confidence = bbox202 if confidence >= conf_threshold:203 # Dibujar el bounding box204 cv2.rectangle(result_frame, (x1, y1), (x2, y2), bbox_color, 2)205 206 # Añadir texto con la confianza207 label = f"{confidence:.2f}"208 cv2.putText(result_frame, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, bbox_color, 2)209 210 return result_frame, bboxes211 212 # Function to detect facial features (eyes, smile) with improved profile face handling213 def detect_facial_features(frame, bboxes, eye_cascade, smile_cascade, detect_eyes=True, detect_smile=True, smile_sensitivity=15, eye_sensitivity=5):214 result_frame = frame.copy()215 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)216 217 # Counters for detection summary218 eye_count = 0219 smile_count = 0220 221 for bbox in bboxes:222 x1, y1, x2, y2, _ = bbox223 roi_gray = gray[y1:y2, x1:x2]224 roi_color = result_frame[y1:y2, x1:x2]225 face_width = x2 - x1226 face_height = y2 - y1227 228 # Detect eyes if enabled229 if detect_eyes:230 # Adjust region of interest to focus on the upper part of the face231 upper_face_y1 = y1232 upper_face_y2 = y1 + int(face_height * 0.55) # Slightly reduced to focus more on the eye area233 234 # For profile faces, we need to search the entire upper region235 # as well as the left and right sides separately236 237 # Full upper region for profile faces238 upper_face_roi_gray = gray[upper_face_y1:upper_face_y2, x1:x2]239 upper_face_roi_color = result_frame[upper_face_y1:upper_face_y2, x1:x2]240 241 # Split the upper region into two halves (left and right) to search for eyes individually242 mid_x = x1 + face_width // 2243 left_eye_roi_gray = gray[upper_face_y1:upper_face_y2, x1:mid_x]244 right_eye_roi_gray = gray[upper_face_y1:upper_face_y2, mid_x:x2]245 246 left_eye_roi_color = result_frame[upper_face_y1:upper_face_y2, x1:mid_x]247 right_eye_roi_color = result_frame[upper_face_y1:upper_face_y2, mid_x:x2]248 249 # Apply histogram equalization and contrast enhancement for all regions250 if upper_face_roi_gray.size > 0:251 upper_face_roi_gray = cv2.equalizeHist(upper_face_roi_gray)252 253 # Enhance contrast254 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))255 upper_face_roi_gray = clahe.apply(upper_face_roi_gray)256 257 # First try to detect eyes in the full upper region (for profile faces)258 full_eyes = eye_cascade.detectMultiScale(259 upper_face_roi_gray, 260 scaleFactor=1.02, # More sensitive for profile faces261 minNeighbors=max(1, eye_sensitivity-3), # Even more sensitive262 minSize=(int(face_width * 0.07), int(face_width * 0.07)),263 maxSize=(int(face_width * 0.3), int(face_width * 0.3))264 )265 266 # If we found eyes in the full region, use those267 if len(full_eyes) > 0:268 # Sort by size (area) and take up to 2 largest269 full_eyes = sorted(full_eyes, key=lambda e: e[2] * e[3], reverse=True)270 full_eyes = full_eyes[:2] # Take at most 2 eyes271 272 for ex, ey, ew, eh in full_eyes:273 eye_count += 1274 cv2.rectangle(upper_face_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)275 cv2.putText(upper_face_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)276 else:277 # If no eyes found in full region, try left and right separately278 if left_eye_roi_gray.size > 0:279 left_eye_roi_gray = cv2.equalizeHist(left_eye_roi_gray)280 left_eye_roi_gray = clahe.apply(left_eye_roi_gray)281 282 left_eyes = eye_cascade.detectMultiScale(283 left_eye_roi_gray, 284 scaleFactor=1.03,285 minNeighbors=max(1, eye_sensitivity-2),286 minSize=(int(face_width * 0.08), int(face_width * 0.08)),287 maxSize=(int(face_width * 0.25), int(face_width * 0.25))288 )289 290 if len(left_eyes) > 0:291 # Sort by size and take the largest292 left_eyes = sorted(left_eyes, key=lambda e: e[2] * e[3], reverse=True)293 left_eye = left_eyes[0]294 eye_count += 1295 296 # Draw rectangle for the left eye297 ex, ey, ew, eh = left_eye298 cv2.rectangle(left_eye_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)299 cv2.putText(left_eye_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)300 301 if right_eye_roi_gray.size > 0:302 right_eye_roi_gray = cv2.equalizeHist(right_eye_roi_gray)303 right_eye_roi_gray = clahe.apply(right_eye_roi_gray)304 305 right_eyes = eye_cascade.detectMultiScale(306 right_eye_roi_gray, 307 scaleFactor=1.03,308 minNeighbors=max(1, eye_sensitivity-2),309 minSize=(int(face_width * 0.08), int(face_width * 0.08)),310 maxSize=(int(face_width * 0.25), int(face_width * 0.25))311 )312 313 if len(right_eyes) > 0:314 # Sort by size and take the largest315 right_eyes = sorted(right_eyes, key=lambda e: e[2] * e[3], reverse=True)316 right_eye = right_eyes[0]317 eye_count += 1318 319 # Draw rectangle for the right eye320 ex, ey, ew, eh = right_eye321 cv2.rectangle(right_eye_roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)322 cv2.putText(right_eye_roi_color, "Eye", (ex, ey-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 2)323 324 # Detect smile if enabled325 if detect_smile:326 # For profile faces, we need to adjust the region of interest327 # Try multiple regions to improve detection328 329 # Standard region (middle to bottom)330 lower_face_y1 = y1 + int(face_height * 0.5)331 lower_face_roi_gray = gray[lower_face_y1:y2, x1:x2]332 lower_face_roi_color = result_frame[lower_face_y1:y2, x1:x2]333 334 # Alternative region (lower third)335 alt_lower_face_y1 = y1 + int(face_height * 0.65)336 alt_lower_face_roi_gray = gray[alt_lower_face_y1:y2, x1:x2]337 338 # Apply histogram equalization and enhance contrast339 smile_detected = False340 341 if lower_face_roi_gray.size > 0:342 lower_face_roi_gray = cv2.equalizeHist(lower_face_roi_gray)343 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))344 lower_face_roi_gray = clahe.apply(lower_face_roi_gray)345 346 # Try with standard parameters347 smiles = smile_cascade.detectMultiScale(348 lower_face_roi_gray, 349 scaleFactor=1.2,350 minNeighbors=smile_sensitivity,351 minSize=(int(face_width * 0.25), int(face_width * 0.15)),352 maxSize=(int(face_width * 0.7), int(face_width * 0.4))353 )354 355 if len(smiles) > 0:356 # Sort by size and take the largest357 smiles = sorted(smiles, key=lambda s: s[2] * s[3], reverse=True)358 sx, sy, sw, sh = smiles[0]359 360 # Increment smile counter361 smile_count += 1362 smile_detected = True363 364 # Draw rectangle for the smile365 cv2.rectangle(lower_face_roi_color, (sx, sy), (sx+sw, sy+sh), (0, 0, 255), 2)366 cv2.putText(lower_face_roi_color, "Smile", (sx, sy-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)367 368 # If no smile detected in standard region, try alternative region369 if not smile_detected and alt_lower_face_roi_gray.size > 0:370 alt_lower_face_roi_gray = cv2.equalizeHist(alt_lower_face_roi_gray)371 alt_lower_face_roi_gray = clahe.apply(alt_lower_face_roi_gray)372 373 # Try with more sensitive parameters374 alt_smiles = smile_cascade.detectMultiScale(375 alt_lower_face_roi_gray, 376 scaleFactor=1.1,377 minNeighbors=max(1, smile_sensitivity-5), # More sensitive378 minSize=(int(face_width * 0.2), int(face_width * 0.1)),379 maxSize=(int(face_width * 0.6), int(face_width * 0.3))380 )381 382 if len(alt_smiles) > 0:383 # Sort by size and take the largest384 alt_smiles = sorted(alt_smiles, key=lambda s: s[2] * s[3], reverse=True)385 sx, sy, sw, sh = alt_smiles[0]386 387 # Adjust coordinates for the alternative region388 adjusted_sy = sy + (alt_lower_face_y1 - lower_face_y1)389 390 # Increment smile counter391 smile_count += 1392 393 # Draw rectangle for the smile (in the original lower face ROI)394 cv2.rectangle(lower_face_roi_color, (sx, adjusted_sy), (sx+sw, adjusted_sy+sh), (0, 0, 255), 2)395 cv2.putText(lower_face_roi_color, "Smile", (sx, adjusted_sy-5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)396 397 return result_frame, eye_count, smile_count398 399 # Función para detectar atributos faciales (edad, género, emoción)400 def detect_face_attributes(image, bbox):401 """402 Detecta atributos faciales como edad, género y emoción usando DeepFace.403 404 Args:405 image: Imagen en formato OpenCV (BGR)406 bbox: Bounding box de la cara [x1, y1, x2, y2, conf]407 408 Returns:409 Diccionario con los atributos detectados410 """411 if not DEEPFACE_AVAILABLE:412 return None413 414 try:415 x1, y1, x2, y2, _ = bbox416 face_img = image[y1:y2, x1:x2]417 418 # Convertir de BGR a RGB para DeepFace419 face_img_rgb = cv2.cvtColor(face_img, cv2.COLOR_BGR2RGB)420 421 # Analyze atributos faciales422 attributes = DeepFace.analyze(423 img_path=face_img_rgb,424 actions=['age', 'gender', 'emotion'],425 enforce_detection=False,426 detector_backend="opencv"427 )428 429 return attributes[0]430 431 except Exception as e:432 st.error(f"Error detecting facial attributes: {str(e)}")433 return None434 435 # Function to apply age and gender detection (placeholder - would need additional models)436 def detect_age_gender(frame, bboxes):437 # Versión mejorada que usa DeepFace si está disponible438 result_frame = frame.copy()439 440 for i, bbox in enumerate(bboxes):441 x1, y1, x2, y2, _ = bbox442 443 if DEEPFACE_AVAILABLE:444 # Intentar usar DeepFace para análisis facial445 attributes = detect_face_attributes(frame, bbox)446 447 if attributes:448 # Extraer información de atributos449 age = attributes.get('age', 'Unknown')450 gender = attributes.get('gender', 'Unknown')451 emotion = attributes.get('dominant_emotion', 'Unknown').capitalize()452 gender_prob = attributes.get('gender', {}).get('Woman', 0)453 454 # Determinar color basado en confianza455 if gender == 'Woman':456 gender_color = (255, 0, 255) # Magenta para mujer457 else:458 gender_color = (255, 0, 0) # Azul para hombre459 460 # Añadir texto con información461 cv2.putText(result_frame, f"Age: {age}", (x1, y2+20), 462 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 0), 2)463 cv2.putText(result_frame, f"Gender: {gender}", (x1, y2+40), 464 cv2.FONT_HERSHEY_SIMPLEX, 0.5, gender_color, 2)465 cv2.putText(result_frame, f"Emotion: {emotion}", (x1, y2+60), 466 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 2)467 else:468 # Fallback si DeepFace falla469 cv2.putText(result_frame, "Age: Unknown", (x1, y2+20), 470 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)471 cv2.putText(result_frame, "Gender: Unknown", (x1, y2+40), 472 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)473 else:474 # Usar texto placeholder si DeepFace no está disponible475 cv2.putText(result_frame, "Age: 25-35", (x1, y2+20), 476 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)477 cv2.putText(result_frame, "Gender: Unknown", (x1, y2+40), 478 cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 255), 2)479 480 return result_frame481 482 # Function to generate download link for processed image483 def get_image_download_link(img, filename, text):484 buffered = BytesIO()485 img.save(buffered, format="JPEG")486 img_str = base64.b64encode(buffered.getvalue()).decode()487 href = f'<a href="data:file/txt;base64,{img_str}" download="{filename}">{text}</a>'488 return href489 490 # Function to process video frames491 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):492 cap = cv2.VideoCapture(video_path)493 494 # Get video properties495 frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))496 frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))497 fps = int(cap.get(cv2.CAP_PROP_FPS))498 499 # Create temporary output file500 temp_dir = tempfile.mkdtemp()501 temp_output_path = os.path.join(temp_dir, "processed_video.mp4")502 503 # Initialize video writer504 fourcc = cv2.VideoWriter_fourcc(*'mp4v')505 out = cv2.VideoWriter(temp_output_path, fourcc, fps, (frame_width, frame_height))506 507 # Create a progress bar508 frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))509 progress_bar = st.progress(0)510 status_text = st.empty()511 512 # Process video frames513 current_frame = 0514 processing_times = []515 516 # Total counters for statistics517 total_faces = 0518 total_eyes = 0519 total_smiles = 0520 521 while cap.isOpened():522 ret, frame = cap.read()523 if not ret:524 break525 526 # Start timing for performance metrics527 start_time = time.time()528 529 # Detect faces530 detections = detect_face_dnn(face_net, frame, conf_threshold)531 processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color)532 533 # Update face counter534 total_faces += len(bboxes)535 536 # Detect facial features if enabled537 if detect_eyes or detect_smile:538 processed_frame, eye_count, smile_count = detect_facial_features(539 processed_frame, 540 bboxes, 541 eye_cascade, 542 smile_cascade,543 detect_eyes,544 detect_smile,545 smile_sensitivity,546 eye_sensitivity547 )548 # Update counters549 total_eyes += eye_count550 total_smiles += smile_count551 552 # End timing553 processing_times.append(time.time() - start_time)554 555 # Write the processed frame556 out.write(processed_frame)557 558 # Update progress559 current_frame += 1560 progress_bar.progress(current_frame / frame_count)561 status_text.text(f"Processing frame {current_frame}/{frame_count}")562 563 # Release resources564 cap.release()565 out.release()566 567 # Calculate and display performance metrics568 if processing_times:569 avg_time = sum(processing_times) / len(processing_times)570 status_text.text(f"Processing complete! Average processing time: {avg_time:.4f}s per frame")571 572 # Return detection statistics573 detection_stats = {574 "faces": total_faces // max(1, current_frame), # Average per frame575 "eyes": total_eyes // max(1, current_frame), # Average per frame576 "smiles": total_smiles // max(1, current_frame) # Average per frame577 }578 579 return temp_output_path, temp_dir, detection_stats580 581 # Camera control functions582 def start_camera():583 st.session_state.camera_running = True584 585 def stop_camera():586 st.session_state.camera_running = False587 st.session_state.camera_stopped = True588 589 def start_feature_camera():590 st.session_state.feature_camera_running = True591 592 def stop_feature_camera():593 st.session_state.feature_camera_running = False594 st.session_state.feature_camera_stopped = True595 596 # Función auxiliar para verificar si una imagen es válida antes de redimensionar597 def is_valid_image(img):598 if img is None:599 return False600 try:601 # Verificar que la imagen tenga dimensiones válidas y datos602 return img.size > 0 and len(img.shape) >= 2 and img.shape[0] > 0 and img.shape[1] > 0603 except:604 return False605 606 # Función auxiliar para redimensionar de forma segura607 def safe_resize(img, target_size):608 if is_valid_image(img):609 try:610 return cv2.resize(img, target_size)611 except Exception as e:612 st.error(f"Error al redimensionar: {str(e)}")613 return None614 return None615 616 if app_mode == "About":617 st.markdown("""618 ## About This App619 620 This application uses OpenCV's Deep Neural Network (DNN) module and Haar Cascade classifiers to detect faces and facial features in images and videos.621 622 ### Features:623 - Face detection using OpenCV DNN624 - Eye and smile detection using Haar Cascades625 - Support for both image and video processing626 - Adjustable confidence threshold627 - Download options for processed media628 - Performance metrics629 630 ### How to use:631 1. Select a mode from the sidebar632 2. Upload an image or video633 3. Adjust settings as needed634 4. View and download the results635 636 ### Technologies Used:637 - Streamlit for the web interface638 - OpenCV for computer vision operations639 - Python for backend processing640 641 ### Models:642 - SSD MobileNet for face detection643 - Haar Cascades for facial features644 """)645 646 # Display a sample image or GIF647 st.image("https://opencv.org/wp-content/uploads/2019/07/detection.gif", caption="Sample face detection", use_container_width=True)648 649 elif app_mode == "Face Detection":650 # Load the face detection model651 face_net = load_face_model()652 653 # Input type selection (Image or Video)654 input_type = st.sidebar.radio("Select Input Type", ["Image", "Video"])655 656 # Confidence threshold slider657 conf_threshold = st.sidebar.slider(658 "Confidence Threshold", 659 min_value=0.0, 660 max_value=1.0, 661 value=0.5, 662 step=0.05,663 help="Adjust the threshold for face detection confidence (higher = fewer detections but more accurate)"664 )665 666 # Style options667 bbox_color = st.sidebar.color_picker("Bounding Box Color", "#00FF00")668 # Convert hex color to BGR for OpenCV669 bbox_color_rgb = tuple(int(bbox_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))670 bbox_color_bgr = (bbox_color_rgb[2], bbox_color_rgb[1], bbox_color_rgb[0]) # Convert RGB to BGR671 672 # Display processing metrics673 show_metrics = st.sidebar.checkbox("Show Processing Metrics", True)674 675 if input_type == "Image":676 # File uploader for images677 file_buffer = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png'])678 679 if file_buffer is not None:680 # Read the file and convert it to OpenCV format681 raw_bytes = np.asarray(bytearray(file_buffer.read()), dtype=np.uint8)682 image = cv2.imdecode(raw_bytes, cv2.IMREAD_COLOR)683 684 # Save la imagen original en session_state para reprocesarla cuando cambie el umbral685 # Usar un identificador único para cada archivo para detectar cambios686 file_id = file_buffer.name + str(file_buffer.size)687 688 if 'file_id' not in st.session_state or st.session_state.file_id != file_id:689 st.session_state.file_id = file_id690 st.session_state.original_image = image.copy()691 692 # Display original image693 col1, col2 = st.columns(2)694 with col1:695 st.subheader("Original Image")696 st.image(st.session_state.original_image, channels='BGR', use_container_width=True)697 698 # Start timing for performance metrics699 start_time = time.time()700 701 # Detect faces702 detections = detect_face_dnn(face_net, st.session_state.original_image, conf_threshold)703 processed_image, bboxes = process_face_detections(st.session_state.original_image, detections, conf_threshold, bbox_color_bgr)704 705 # Calculate processing time706 processing_time = time.time() - start_time707 708 # Display the processed image709 with col2:710 st.subheader("Processed Image")711 st.image(processed_image, channels='BGR', use_container_width=True)712 713 # Convert OpenCV image to PIL for download714 pil_img = Image.fromarray(processed_image[:, :, ::-1])715 st.markdown(716 get_image_download_link(pil_img, "face_detection_result.jpg", "📥 Download Processed Image"),717 unsafe_allow_html=True718 )719 720 # Show metrics if enabled721 if show_metrics:722 st.subheader("Processing Metrics")723 col1, col2, col3 = st.columns(3)724 col1.metric("Processing Time", f"{processing_time:.4f} seconds")725 col2.metric("Faces Detected", len(bboxes))726 col3.metric("Confidence Threshold", f"{conf_threshold:.2f}")727 728 # Display detailed metrics in an expandable section729 with st.expander("Detailed Detection Information"):730 if bboxes:731 st.write("Detected faces with confidence scores:")732 for i, bbox in enumerate(bboxes):733 st.write(f"Face #{i+1}: Confidence = {bbox[4]:.4f}")734 else:735 st.write("No faces detected in the image.")736 737 else: # Video mode738 # Video mode options739 video_source = st.radio("Select video source", ["Upload video", "Use webcam"])740 741 if video_source == "Upload video":742 # File uploader for videos743 file_buffer = st.file_uploader("Upload a video", type=['mp4', 'avi', 'mov'])744 745 if file_buffer is not None:746 # Save uploaded video to temporary file747 temp_dir = tempfile.mkdtemp()748 temp_path = os.path.join(temp_dir, "input_video.mp4")749 750 with open(temp_path, "wb") as f:751 f.write(file_buffer.read())752 753 # Display original video754 st.subheader("Original Video")755 st.video(temp_path)756 757 # Load models for feature detection (will be used in the processing)758 eye_cascade, smile_cascade = load_feature_models()759 760 # Process video button761 if st.button("Process Video"):762 with st.spinner("Processing video... This may take a while depending on the video length."):763 # Process the video764 output_path, output_dir, detection_stats = process_video(765 temp_path, 766 face_net, 767 eye_cascade,768 smile_cascade,769 conf_threshold,770 detect_eyes=True,771 detect_smile=True,772 bbox_color=bbox_color_bgr,773 eye_sensitivity=5774 )775 776 # Display processed video777 st.subheader("Processed Video")778 st.video(output_path)779 780 # Mostrar estadísticas de detección781 st.subheader("Detection Summary")782 summary_col1, summary_col2, summary_col3 = st.columns(3)783 summary_col1.metric("Avg. Faces per Frame", detection_stats["faces"])784 785 if detect_eyes: # type: ignore786 summary_col2.metric("Avg. Eyes per Frame", detection_stats["eyes"])787 else:788 summary_col2.metric("Eyes Detected", "N/A")789 790 if detect_smile: # type: ignore791 summary_col3.metric("Avg. Smiles per Frame", detection_stats["smiles"])792 else:793 summary_col3.metric("Smiles Detected", "N/A")794 795 # Provide download link796 with open(output_path, 'rb') as f:797 video_bytes = f.read()798 799 st.download_button(800 label="📥 Download Processed Video",801 data=video_bytes,802 file_name="processed_video.mp4",803 mime="video/mp4"804 )805 806 # Clean up temporary files807 try:808 os.remove(temp_path)809 os.remove(output_path)810 os.rmdir(temp_dir)811 os.rmdir(output_dir)812 except:813 pass814 else: # Use webcam815 st.subheader("Real-time face detection")816 st.write("Click 'Start Camera' to begin real-time face detection.")817 818 # Placeholder for webcam video819 camera_placeholder = st.empty()820 821 # Buttons to control the camera822 col1, col2 = st.columns(2)823 start_button = col1.button("Start Camera", on_click=start_camera)824 stop_button = col2.button("Stop Camera", on_click=stop_camera)825 826 # Show message when camera is stopped827 if 'camera_stopped' in st.session_state and st.session_state.camera_stopped:828 st.info("Camera stopped. Click 'Start Camera' to activate it again.")829 st.session_state.camera_stopped = False830 831 if st.session_state.camera_running:832 st.info("Camera activated. Processing real-time video...")833 # Initialize webcam834 cap = cv2.VideoCapture(0) # 0 is typically the main webcam835 836 if not cap.isOpened():837 st.error("Could not access webcam. Make sure it's connected and not being used by another application.")838 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.")839 st.session_state.camera_running = False840 else:841 # Display real-time video with face detection842 try:843 while st.session_state.camera_running:844 ret, frame = cap.read()845 if not ret:846 st.error("Error reading frame from camera.")847 break848 849 # Detect faces850 detections = detect_face_dnn(face_net, frame, conf_threshold)851 processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color_bgr)852 853 # Display the processed frame854 camera_placeholder.image(processed_frame, channels="BGR", use_container_width=True)855 856 # Small pause to avoid overloading the CPU857 time.sleep(0.01)858 finally:859 # Release the camera when stopped860 cap.release()861 862 elif app_mode == "Feature Detection":863 # Load all required models864 face_net = load_face_model()865 eye_cascade, smile_cascade = load_feature_models()866 867 # Feature selection checkboxes868 st.sidebar.subheader("Feature Detection Options")869 detect_eyes = st.sidebar.checkbox("Detect Eyes", True)870 871 # Add controls for eye detection sensitivity872 eye_sensitivity = 5 # Default value873 if detect_eyes:874 eye_sensitivity = st.sidebar.slider(875 "Eye Detection Sensitivity", 876 min_value=1, 877 max_value=10, 878 value=5, 879 step=1,880 help="Adjust the sensitivity of eye detection (lower value = more detections)"881 )882 883 detect_smile = st.sidebar.checkbox("Detect Smile", True)884 885 # Add controls for smile detection sensitivity886 smile_sensitivity = 15 # Default value887 if detect_smile:888 smile_sensitivity = st.sidebar.slider(889 "Smile Detection Sensitivity", 890 min_value=5, 891 max_value=30, 892 value=15, 893 step=1,894 help="Adjust the sensitivity of smile detection (lower value = more detections)"895 )896 897 detect_age_gender_option = st.sidebar.checkbox("Detect Age/Gender (Demo)", False)898 899 # Confidence threshold slider900 conf_threshold = st.sidebar.slider(901 "Face Detection Confidence", 902 min_value=0.0, 903 max_value=1.0, 904 value=0.5, 905 step=0.05906 )907 908 # Style options909 bbox_color = st.sidebar.color_picker("Bounding Box Color", "#00FF00")910 # Convert hex color to BGR for OpenCV911 bbox_color_rgb = tuple(int(bbox_color.lstrip('#')[i:i+2], 16) for i in (0, 2, 4))912 bbox_color_bgr = (bbox_color_rgb[2], bbox_color_rgb[1], bbox_color_rgb[0]) # Convert RGB to BGR913 914 # Input type selection915 input_type = st.sidebar.radio("Select Input Type", ["Image", "Video"])916 917 if input_type == "Image":918 # File uploader for images919 file_buffer = st.file_uploader("Upload an image", type=['jpg', 'jpeg', 'png'])920 921 if file_buffer is not None:922 # Read the file and convert it to OpenCV format923 raw_bytes = np.asarray(bytearray(file_buffer.read()), dtype=np.uint8)924 image = cv2.imdecode(raw_bytes, cv2.IMREAD_COLOR)925 926 # Save la imagen original en session_state para reprocesarla cuando cambie el umbral927 # Usar un identificador único para cada archivo para detectar cambios928 file_id = file_buffer.name + str(file_buffer.size)929 930 if 'feature_file_id' not in st.session_state or st.session_state.feature_file_id != file_id:931 st.session_state.feature_file_id = file_id932 st.session_state.feature_original_image = image.copy()933 934 # Display original image935 col1, col2 = st.columns(2)936 with col1:937 st.subheader("Original Image")938 st.image(st.session_state.feature_original_image, channels='BGR', use_container_width=True)939 940 # Start processing with face detection941 detections = detect_face_dnn(face_net, st.session_state.feature_original_image, conf_threshold)942 processed_image, bboxes = process_face_detections(st.session_state.feature_original_image, detections, conf_threshold, bbox_color_bgr)943 944 # Inicializar contadores945 eye_count = 0946 smile_count = 0947 948 # Detect facial features if any options are enabled949 if detect_eyes or detect_smile:950 processed_image, eye_count, smile_count = detect_facial_features(951 processed_image, 952 bboxes,953 eye_cascade,954 smile_cascade,955 detect_eyes,956 detect_smile,957 smile_sensitivity,958 eye_sensitivity959 )960 961 # Apply age/gender detection if enabled (demo purpose)962 if detect_age_gender_option:963 processed_image = detect_age_gender(processed_image, bboxes)964 965 # Display the processed image966 with col2:967 st.subheader("Processed Image")968 st.image(processed_image, channels='BGR', use_container_width=True)969 970 # Convert OpenCV image to PIL for download971 pil_img = Image.fromarray(processed_image[:, :, ::-1])972 st.markdown(973 get_image_download_link(pil_img, "feature_detection_result.jpg", "📥 Download Processed Image"),974 unsafe_allow_html=True975 )976 977 # Display detection summary978 st.subheader("Detection Summary")979 summary_col1, summary_col2, summary_col3 = st.columns(3)980 summary_col1.metric("Faces Detected", len(bboxes))981 982 if detect_eyes:983 summary_col2.metric("Eyes Detected", eye_count)984 else:985 summary_col2.metric("Eyes Detected", "N/A")986 987 if detect_smile:988 summary_col3.metric("Smiles Detected", smile_count)989 else:990 summary_col3.metric("Smiles Detected", "N/A")991 992 else: # Video mode993 st.write("Facial feature detection in video")994 995 # Video mode options996 video_source = st.radio("Select video source", ["Upload video", "Use webcam"])997 998 if video_source == "Upload video":999 st.write("Upload a video to process with facial feature detection.")1000 # Similar implementation to Face Detection mode for uploaded videos1001 file_buffer = st.file_uploader("Upload a video", type=['mp4', 'avi', 'mov'])1002 1003 if file_buffer is not None:1004 # Save uploaded video to temporary file1005 temp_dir = tempfile.mkdtemp()1006 temp_path = os.path.join(temp_dir, "input_video.mp4")1007 1008 with open(temp_path, "wb") as f:1009 f.write(file_buffer.read())1010 1011 # Display original video1012 st.subheader("Original Video")1013 st.video(temp_path)1014 1015 # Process video button1016 if st.button("Process Video"):1017 with st.spinner("Processing video... This may take a while depending on the video length."):1018 # Process the video with feature detection1019 output_path, output_dir, detection_stats = process_video(1020 temp_path, 1021 face_net, 1022 eye_cascade,1023 smile_cascade,1024 conf_threshold,1025 detect_eyes=True,1026 detect_smile=True,1027 bbox_color=bbox_color_bgr,1028 smile_sensitivity=smile_sensitivity,1029 eye_sensitivity=eye_sensitivity1030 )1031 1032 # Display processed video1033 st.subheader("Processed Video")1034 st.video(output_path)1035 1036 # Mostrar estadísticas de detección1037 st.subheader("Detection Summary")1038 summary_col1, summary_col2, summary_col3 = st.columns(3)1039 summary_col1.metric("Avg. Faces per Frame", detection_stats["faces"])1040 1041 if detect_eyes:1042 summary_col2.metric("Avg. Eyes per Frame", detection_stats["eyes"])1043 else:1044 summary_col2.metric("Eyes Detected", "N/A")1045 1046 if detect_smile:1047 summary_col3.metric("Avg. Smiles per Frame", detection_stats["smiles"])1048 else:1049 summary_col3.metric("Smiles Detected", "N/A")1050 1051 # Provide download link1052 with open(output_path, 'rb') as f:1053 video_bytes = f.read()1054 1055 st.download_button(1056 label="📥 Download Processed Video",1057 data=video_bytes,1058 file_name="feature_detection_video.mp4",1059 mime="video/mp4"1060 )1061 1062 # Clean up temporary files1063 try:1064 os.remove(temp_path)1065 os.remove(output_path)1066 os.rmdir(temp_dir)1067 os.rmdir(output_dir)1068 except:1069 pass1070 else: # Usar cámara web1071 st.subheader("Real-time facial feature detection")1072 st.write("Click 'Start Camera' to begin real-time detection.")1073 1074 # Placeholder for webcam video1075 camera_placeholder = st.empty()1076 1077 # Buttons to control the camera1078 col1, col2 = st.columns(2)1079 start_button = col1.button("Start Camera", on_click=start_feature_camera)1080 stop_button = col2.button("Stop Camera", on_click=stop_feature_camera)1081 1082 # Show message when camera is stopped1083 if 'feature_camera_stopped' in st.session_state and st.session_state.feature_camera_stopped:1084 st.info("Camera stopped. Click 'Start Camera' to activate it again.")1085 st.session_state.feature_camera_stopped = False1086 1087 if st.session_state.feature_camera_running:1088 st.info("Camera activated. Processing real-time video with feature detection...")1089 # Initialize webcam1090 cap = cv2.VideoCapture(0) # 0 is typically the main webcam1091 1092 if not cap.isOpened():1093 st.error("Could not access webcam. Make sure it's connected and not being used by another application.")1094 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.")1095 st.session_state.feature_camera_running = False1096 else:1097 # Display real-time video with face and feature detection1098 try:1099 # Create placeholders for metrics1100 metrics_placeholder = st.empty()1101 metrics_col1, metrics_col2, metrics_col3 = metrics_placeholder.columns(3)1102 1103 # Initialize counters1104 face_count_total = 01105 eye_count_total = 01106 smile_count_total = 01107 frame_count = 01108 1109 while st.session_state.feature_camera_running:1110 ret, frame = cap.read()1111 if not ret:1112 st.error("Error reading frame from camera.")1113 break1114 1115 # Detect faces1116 detections = detect_face_dnn(face_net, frame, conf_threshold)1117 processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color_bgr)1118 1119 # Update face counter1120 face_count = len(bboxes)1121 face_count_total += face_count1122 1123 # Initialize counters for this frame1124 eye_count = 01125 smile_count = 01126 1127 # Detect facial features if enabled1128 if detect_eyes or detect_smile:1129 processed_frame, eye_count, smile_count = detect_facial_features(1130 processed_frame, 1131 bboxes,1132 eye_cascade,1133 smile_cascade,1134 detect_eyes,1135 detect_smile,1136 smile_sensitivity,1137 eye_sensitivity1138 )1139 1140 # Update total counters1141 eye_count_total += eye_count1142 smile_count_total += smile_count1143 1144 # Apply age/gender detection if enabled1145 if detect_age_gender_option:1146 processed_frame = detect_age_gender(processed_frame, bboxes)1147 1148 # Display the processed frame1149 camera_placeholder.image(processed_frame, channels="BGR", use_container_width=True)1150 1151 # Update frame counter1152 frame_count += 11153 1154 # Update metrics every 5 frames to avoid overloading the interface1155 if frame_count % 5 == 0:1156 metrics_col1.metric("Faces Detected", face_count)1157 1158 if detect_eyes:1159 metrics_col2.metric("Eyes Detected", eye_count)1160 else:1161 metrics_col2.metric("Eyes Detected", "N/A")1162 1163 if detect_smile:1164 metrics_col3.metric("Smiles Detected", smile_count)1165 else:1166 metrics_col3.metric("Smiles Detected", "N/A")1167 1168 # Small pause to avoid overloading the CPU1169 time.sleep(0.01)1170 finally:1171 # Release the camera when stopped1172 cap.release()1173 1174 elif app_mode == "Comparison Mode":1175 st.subheader("Face Comparison")1176 st.write("Upload two images to compare faces between them.")1177 1178 # Añadir explicación sobre la interpretación de resultados1179 with st.expander("📌 How to interpret similarity results"):1180 st.markdown("""1181 ### Facial Similarity Interpretation Guide1182 1183 The system calculates similarity between faces based on multiple facial features and characteristics.1184 1185 **Similarity Ranges:**1186 - **70-100%**: HIGH Similarity - Very likely to be the same person or identical twins1187 - **50-70%**: MEDIUM Similarity - Possible match, requires verification1188 - **30-50%**: LOW Similarity - Different people with some similar features1189 - **0-30%**: VERY LOW Similarity - Completely different people1190 1191 **Enhanced Comparison System:**1192 The system uses a sophisticated approach that:1193 1. Analyzes multiple facial characteristics with advanced precision1194 2. Evaluates hair style/color, facial structure, texture patterns, and expressions with improved accuracy1195 3. Applies a balanced differentiation between similar and different individuals1196 4. Creates a clear gap between similar and different people's scores1197 5. Reduces scores for people with different facial structures1198 6. Applies penalty factors for critical differences in facial features1199 1200 **Features Analyzed:**