jarondon82 commited on
Commit
1f91d26
·
1 Parent(s): 6629b55

Restaurar implementación completa de detección facial en tiempo real con WebRTC

Browse files
Files changed (1) hide show
  1. streamlit_app.py +291 -1011
streamlit_app.py CHANGED
@@ -74,7 +74,7 @@ def main():
74
  # Navigation menu
75
  app_mode = st.sidebar.selectbox(
76
  "Choose the app mode",
77
- ["About", "Face Detection", "Feature Detection", "Comparison Mode", "Face Recognition"]
78
  )
79
 
80
  # Function to load DNN models with caching and auto-download
@@ -989,12 +989,12 @@ def main():
989
  if detect_eyes: # type: ignore
990
  summary_col2.metric("Avg. Eyes per Frame", detection_stats["eyes"])
991
  else:
992
- summary_col2.metric("Eyes Detected", "N/A")
993
 
994
  if detect_smile: # type: ignore
995
  summary_col3.metric("Avg. Smiles per Frame", detection_stats["smiles"])
996
  else:
997
- summary_col3.metric("Smiles Detected", "N/A")
998
 
999
  # Provide download link
1000
  with open(output_path, 'rb') as f:
@@ -1019,49 +1019,54 @@ def main():
1019
  st.subheader("Real-time face detection")
1020
  st.write("Click 'Start Camera' to begin real-time face detection.")
1021
 
1022
- # Placeholder for webcam video
1023
- camera_placeholder = st.empty()
1024
-
1025
- # Buttons to control the camera
1026
- col1, col2 = st.columns(2)
1027
- start_button = col1.button("Start Camera", on_click=start_camera)
1028
- stop_button = col2.button("Stop Camera", on_click=stop_camera)
1029
-
1030
- # Show message when camera is stopped
1031
- if 'camera_stopped' in st.session_state and st.session_state.camera_stopped:
1032
- st.info("Camera stopped. Click 'Start Camera' to activate it again.")
1033
- st.session_state.camera_stopped = False
1034
-
1035
- if st.session_state.camera_running:
1036
- st.info("Camera activated. Processing real-time video...")
1037
- # Initialize webcam
1038
- cap = cv2.VideoCapture(0) # 0 is typically the main webcam
1039
 
1040
- if not cap.isOpened():
1041
- st.error("Could not access webcam. Make sure it's connected and not being used by another application.")
1042
- 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.")
1043
- st.session_state.camera_running = False
1044
- else:
1045
- # Display real-time video with face detection
1046
- try:
1047
- while st.session_state.camera_running:
1048
- ret, frame = cap.read()
1049
- if not ret:
1050
- st.error("Error reading frame from camera.")
1051
- break
1052
-
1053
- # Detect faces
1054
- detections = detect_face_dnn(face_net, frame, conf_threshold)
1055
- processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color_bgr)
1056
-
1057
- # Display the processed frame
1058
- camera_placeholder.image(processed_frame, channels="BGR", use_container_width=True)
1059
-
1060
- # Small pause to avoid overloading the CPU
1061
- time.sleep(0.01)
1062
- finally:
1063
- # Release the camera when stopped
1064
- cap.release()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1065
 
1066
  elif app_mode == "Feature Detection":
1067
  # Load all required models
@@ -2358,996 +2363,271 @@ def main():
2358
  if not st.session_state.face_database:
2359
  st.warning("No faces registered. Please register at least one face first.")
2360
  else:
2361
- # Configuración avanzada
2362
- with st.expander("Advanced Configuration", expanded=False):
2363
- # Configuración de umbral de similitud
2364
- similarity_threshold = st.slider(
2365
- "Similarity threshold (%)",
2366
- min_value=35.0,
2367
- max_value=95.0,
2368
- value=45.0,
2369
- step=5.0,
2370
- key="realtime_threshold",
2371
- help="Minimum similarity percentage to consider a match"
2372
- )
2373
-
2374
- confidence_threshold = st.slider(
2375
- "Detection Confidence",
2376
- min_value=0.3,
2377
- max_value=0.9,
2378
- value=0.5,
2379
- step=0.05,
2380
- key="realtime_confidence",
2381
- help="Higher value is more restrictive but more accurate"
2382
- )
2383
-
2384
- model_choice = st.selectbox(
2385
- "Embedding model",
2386
- ["VGG-Face", "Facenet", "OpenFace", "ArcFace"],
2387
- key="realtime_model",
2388
- help="Different models can give different results depending on facial features"
2389
- )
2390
-
2391
- voting_method = st.radio(
2392
- "Voting method for multiple embeddings",
2393
- ["Average", "Best match", "Weighted voting"],
2394
- key="realtime_voting",
2395
- help="How to combine results when there are multiple images of a person"
2396
- )
2397
-
2398
- show_confidence = st.checkbox(
2399
- "Show confidence percentage",
2400
- value=True,
2401
- help="Show similarity percentage next to the name"
2402
- )
2403
-
2404
- stabilize_results = st.checkbox(
2405
- "Stabilize results",
2406
- value=True,
2407
- help="Reduce identification fluctuations using temporal averaging"
2408
- )
2409
-
2410
- # Placeholder para métricas
2411
  metrics_cols = st.columns(3)
2412
- with metrics_cols[0]:
2413
- faces_metric = st.empty()
2414
- with metrics_cols[1]:
2415
- fps_metric = st.empty()
2416
- with metrics_cols[2]:
2417
- time_metric = st.empty()
2418
 
2419
- # WebRTC configuration
2420
- rtc_configuration = RTCConfiguration(
2421
- {"iceServers": [
2422
- {"urls": ["stun:stun.l.google.com:19302"]},
2423
- {"urls": ["stun:stun1.l.google.com:19302"]},
2424
- {"urls": ["stun:stun2.l.google.com:19302"]}
2425
- ]}
2426
- )
2427
-
2428
- # Initialize session state variables if they don't exist
2429
  if 'faces_detected' not in st.session_state:
2430
  st.session_state.faces_detected = 0
2431
  if 'fps' not in st.session_state:
2432
  st.session_state.fps = 0
2433
 
2434
- # Define callback to update session state with frames processed
2435
- class VideoProcessor(VideoProcessorBase):
2436
- def __init__(self):
2437
- self.frame_count = 0
2438
- self.face_count = 0
2439
- self.start_time = time.time()
2440
- self.processing = True
2441
- self.frame_skip = 2 # Process every other frame to reduce load
2442
- self.frames_processed = 0
2443
- self.last_log_time = time.time()
2444
-
2445
- def recv(self, frame):
2446
- try:
2447
- img = frame.to_ndarray(format="bgr24")
2448
- self.frame_count += 1
2449
-
2450
- # Solo procesar algunos frames para reducir carga
2451
- if self.frame_count % self.frame_skip != 0:
2452
- return av.VideoFrame.from_ndarray(img, format="bgr24")
2453
-
2454
- self.frames_processed += 1
2455
- now = time.time()
2456
-
2457
- # Registro de diagnóstico cada 5 segundos
2458
- if now - self.last_log_time > 5:
2459
- print(f"Frames procesados: {self.frames_processed}, " +
2460
- f"Tiempo transcurrido: {now - self.start_time:.1f}s, " +
2461
- f"FPS: {self.frames_processed/(now - self.start_time):.1f}")
2462
- self.last_log_time = now
2463
-
2464
- # Verificar que la imagen no sea nula
2465
- if img is None or img.size == 0 or img.shape[0] == 0 or img.shape[1] == 0:
2466
- # Si la imagen es inválida, devolver un frame en blanco
2467
- blank_frame = np.ones((480, 640, 3), dtype=np.uint8) * 255
2468
- cv2.putText(blank_frame, "Error: Invalid frame", (50, 240),
2469
- cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
2470
- return av.VideoFrame.from_ndarray(blank_frame, format="bgr24")
2471
-
2472
- # Reducir tamaño del frame para procesamiento más rápido
2473
- scale_factor = 0.5
2474
- h, w = img.shape[:2]
2475
-
2476
- small_img = safe_resize(img, (int(w * scale_factor), int(h * scale_factor)))
2477
- if small_img is None:
2478
- # Si no se puede redimensionar, usar el frame original (solo para diagnóstico)
2479
- print("No se pudo redimensionar la imagen para procesamiento")
2480
- return av.VideoFrame.from_ndarray(img, format="bgr24")
2481
-
2482
- # Detect faces - la función ahora devuelve directamente los bboxes
2483
- try:
2484
- bboxes = detect_face_dnn(face_net, small_img, confidence_threshold)
2485
- except Exception as e:
2486
- print(f"Error al detectar rostros: {e}")
2487
- bboxes = []
2488
-
2489
- # Ajustar bounding boxes al tamaño original
2490
- original_bboxes = []
2491
- for x1, y1, x2, y2, conf in bboxes:
2492
- original_bboxes.append((
2493
- int(x1 / scale_factor),
2494
- int(y1 / scale_factor),
2495
- int(x2 / scale_factor),
2496
- int(y2 / scale_factor),
2497
- conf
2498
- ))
2499
-
2500
- # Actualizar contadores
2501
- self.face_count = len(original_bboxes)
2502
- current_time = time.time()
2503
- elapsed_time = current_time - self.start_time
2504
- fps = self.frames_processed / elapsed_time if elapsed_time > 0 else 0
2505
-
2506
- # Actualizar métricas en session_state para que sean accesibles fuera
2507
- st.session_state.faces_detected = self.face_count
2508
- st.session_state.fps = fps
2509
-
2510
- # Dibujar cajas de los rostros
2511
- result_img = img.copy()
2512
- for i, (x1, y1, x2, y2, conf) in enumerate(original_bboxes):
2513
- cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
2514
- cv2.putText(result_img, f"Face {i+1}: {conf:.2f}", (x1, y1-10),
2515
- cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
2516
-
2517
- # Añadir información FPS y rostros
2518
- cv2.putText(result_img, f"FPS: {fps:.1f}", (10, 30),
2519
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
2520
- cv2.putText(result_img, f"Faces: {self.face_count}", (10, 60),
2521
- cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
2522
-
2523
- return av.VideoFrame.from_ndarray(result_img, format="bgr24")
2524
- except Exception as e:
2525
- print(f"Error general en procesamiento de video: {str(e)}")
2526
- try:
2527
- # Intentar devolver el frame original
2528
- return av.VideoFrame.from_ndarray(img, format="bgr24")
2529
- except:
2530
- # Si eso falla, devolver un frame en blanco como último recurso
2531
- blank = np.ones((480, 640, 3), dtype=np.uint8) * 255
2532
- return av.VideoFrame.from_ndarray(blank, format="bgr24")
2533
-
2534
- # Display WebRTC streamer con opciones simplificadas para mejorar compatibilidad
2535
- st.info("⚠️ If the video doesn't load: Try using Chrome, reload the page, or use the alternative options below.")
2536
- webrtc_ctx = webrtc_streamer(
2537
- key="face-recognition",
2538
- mode=WebRtcMode.SENDRECV,
2539
- rtc_configuration=rtc_configuration,
2540
- media_stream_constraints={"video": {"width": 640, "height": 480}, "audio": False},
2541
- video_processor_factory=VideoProcessor,
2542
- async_processing=True,
2543
  )
2544
 
2545
- # Establecer y actualizar métricas
2546
- if webrtc_ctx.state.playing:
2547
- faces_metric.metric("Faces detected", st.session_state.get('faces_detected', 0))
2548
- fps_metric.metric("FPS", f"{st.session_state.get('fps', 0):.1f}")
2549
- time_metric.metric("Status", "Running")
2550
-
2551
- # Mostrar instrucciones de uso
2552
- st.success("Webcam activated. Detected faces will be identified in real-time.")
2553
- else:
2554
- faces_metric.metric("Faces detected", 0)
2555
- fps_metric.metric("FPS", "0")
2556
- time_metric.metric("Status", "Stopped")
2557
-
2558
- # Mostrar instrucciones de activación
2559
- st.warning("Click START to activate the webcam. This feature may not be available in environments like Hugging Face Spaces due to security restrictions.")
2560
-
2561
- # WebRTC troubleshooting
2562
- with st.expander("Help: WebRTC Issues"):
2563
- st.markdown("""
2564
- ### WebRTC Troubleshooting
2565
-
2566
- If real-time recognition is not working, it may be due to the following reasons:
2567
-
2568
- 1. **Security restrictions in Hugging Face Spaces**: Some browsers restrict camera access in environments like this.
2569
- 2. **Connection problems**: WebRTC requires establishing a connection that may be blocked by firewalls or proxies.
2570
- 3. **Camera permissions**: You may need to grant explicit permissions to the browser to access your camera.
2571
-
2572
- ### What to do:
2573
-
2574
- 1. Try using another browser (Chrome usually works better)
2575
- 2. Make sure you have granted camera permissions when the browser requests them
2576
- 3. If it still doesn't work, use the alternative options shown below
2577
- """)
2578
-
2579
- # Añadir modo de captura continua (funciona mejor en Hugging Face)
2580
- st.markdown("---")
2581
- st.markdown("### Continuous Capture Mode")
2582
- st.info("⚠️ Recommended mode for Hugging Face: Captures frames continuously with reliable camera access.")
2583
-
2584
- # Configuración del modo de captura continua
2585
- with st.expander("Configuration", expanded=False):
2586
- continuous_model_choice = st.selectbox(
2587
- "Embedding model for recognition",
2588
- ["VGG-Face", "Facenet", "OpenFace", "ArcFace"],
2589
- key="continuous_model_choice",
2590
- index=0 if "continuous_model_choice" not in st.session_state else ["VGG-Face", "Facenet", "OpenFace", "ArcFace"].index(st.session_state.continuous_model_choice)
2591
- )
2592
-
2593
- continuous_similarity_threshold = st.slider(
2594
- "Similarity threshold (%)",
2595
- min_value=35.0,
2596
- max_value=95.0,
2597
- value=45.0,
2598
- step=5.0,
2599
- key="continuous_similarity_threshold"
2600
- )
2601
-
2602
- continuous_confidence_threshold = st.slider(
2603
- "Detection confidence",
2604
- min_value=0.1,
2605
- max_value=0.9,
2606
- value=0.3,
2607
- step=0.05,
2608
- key="continuous_confidence_threshold"
2609
- )
2610
-
2611
- capture_fps = st.slider(
2612
- "Capture frames per second",
2613
- min_value=0.5,
2614
- max_value=5.0,
2615
- value=1.0,
2616
- step=0.5,
2617
- key="capture_fps",
2618
- help="Higher values capture more frames but may overload the system"
2619
- )
2620
-
2621
- col1, col2 = st.columns(2)
2622
- start_continuous = col1.button("Start Continuous Capture", key="start_continuous_button", use_container_width=True)
2623
- stop_continuous = col2.button("Stop Continuous Capture", key="stop_continuous_button", use_container_width=True)
2624
-
2625
- if start_continuous:
2626
  st.session_state.continuous_capture = True
2627
- st.session_state.frame_count = 0
2628
- st.session_state.frames_processed = 0
2629
- st.session_state.start_time = time.time()
2630
- st.session_state.last_fps_update = time.time()
2631
- # Desactivar otros modos
2632
- st.session_state.demo_running = False
2633
- st.session_state.upload_mode = False
2634
- st.session_state.simple_camera = False
2635
-
2636
- if stop_continuous:
2637
- st.session_state.continuous_capture = False
2638
-
2639
- if st.session_state.get('continuous_capture', False):
2640
- # Área para mostrar resultados
2641
- result_container = st.container()
2642
- camera_container = st.container()
2643
-
2644
- # Configurar métricas
2645
- faces_metric.metric("Faces detected", 0)
2646
- fps_metric.metric("FPS", "Processing...")
2647
- time_metric.metric("Status", "Running")
2648
-
2649
- # Usar un componente personalizado con JavaScript para captura automática
2650
- with camera_container:
2651
- st.info("Auto-capture enabled. Camera should start automatically.")
2652
-
2653
- # Construir el componente de cámara con JavaScript sin formateo complejo
2654
- fps_value = str(st.session_state.get('capture_fps', 1.0))
2655
-
2656
- # Primera parte del HTML (antes del valor de FPS)
2657
- camera_html_part1 = """
2658
- <div style="margin-bottom: 20px;">
2659
- <video id="webcam" autoplay playsinline width="640" height="480" style="border-radius: 5px; display: none;"></video>
2660
- <canvas id="canvas" width="640" height="480" style="display: none;"></canvas>
2661
- <canvas id="display" width="640" height="480" style="border-radius: 5px; display: block; margin: 0 auto; border: 4px solid #ff5500; box-shadow: 0 0 15px rgba(255, 85, 0, 0.5);"></canvas>
2662
- </div>
2663
- <script>
2664
- const video = document.getElementById('webcam');
2665
- const canvas = document.getElementById('canvas');
2666
- const display = document.getElementById('display');
2667
- const ctx = canvas.getContext('2d');
2668
- const displayCtx = display.getContext('2d');
2669
- let captureInterval;
2670
-
2671
- // Para depuración
2672
- console.log('Componente de cámara inicializado');
2673
-
2674
- // Dibujar un rectángulo de prueba inmediatamente
2675
- displayCtx.fillStyle = 'rgba(255, 255, 255, 0.5)';
2676
- displayCtx.fillRect(0, 0, display.width, display.height);
2677
- displayCtx.strokeStyle = '#FF0000';
2678
- displayCtx.lineWidth = 8;
2679
- displayCtx.strokeRect(200, 100, 240, 280);
2680
- displayCtx.fillStyle = '#FF0000';
2681
- displayCtx.font = '28px Arial';
2682
- displayCtx.fillText('Rectángulo de Prueba', 210, 90);
2683
- console.log('Rectángulo inicial de prueba dibujado');
2684
-
2685
- // Arreglo para almacenar las últimas cajas recibidas
2686
- let lastBoxes = [];
2687
-
2688
- // Configuración dinámica del FPS (desde Streamlit)
2689
- const captureDelay = 1000 / """
2690
-
2691
- # Valor de FPS (como string)
2692
- camera_html_part2 = fps_value
2693
-
2694
- # Resto del HTML (después del valor de FPS)
2695
- camera_html_part3 = """;
2696
-
2697
- // Iniciar la cámara
2698
- async function setupCamera() {
2699
- try {
2700
- console.log('Intentando acceder a la cámara...');
2701
- const stream = await navigator.mediaDevices.getUserMedia({
2702
- 'video': { width: 640, height: 480 },
2703
- 'audio': false
2704
- });
2705
- video.srcObject = stream;
2706
- console.log('Cámara iniciada correctamente');
2707
-
2708
- // Esperar a que la cámara esté lista
2709
- return new Promise((resolve) => {
2710
- video.onloadedmetadata = () => {
2711
- video.play();
2712
- console.log('Video iniciado');
2713
- resolve(video);
2714
- };
2715
- });
2716
- } catch (error) {
2717
- console.error('Error accessing camera:', error);
2718
- window.parent.postMessage({
2719
- type: 'streamlit:setComponentValue',
2720
- value: { error: 'Camera access denied or not available' }
2721
- }, '*');
2722
- }
2723
- }
2724
-
2725
- // Capturar frame y enviar a Streamlit
2726
- function captureFrame() {
2727
- if (video.readyState === video.HAVE_ENOUGH_DATA) {
2728
- // Dibujar el video en ambos canvas
2729
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
2730
- displayCtx.drawImage(video, 0, 0, display.width, display.height);
2731
-
2732
- // Dibujar cajas almacenadas (para mantener las cajas entre frames)
2733
- drawStoredBoxes();
2734
-
2735
- // Convertir a base64
2736
- const imageData = canvas.toDataURL('image/jpeg', 0.8);
2737
-
2738
- // Enviar los datos a Streamlit
2739
- window.parent.postMessage({
2740
- type: 'streamlit:setComponentValue',
2741
- value: { image: imageData, timestamp: Date.now() }
2742
- }, '*');
2743
- }
2744
- }
2745
-
2746
- // Función para dibujar las cajas almacenadas
2747
- function drawStoredBoxes() {
2748
- if (lastBoxes && lastBoxes.length > 0) {
2749
- console.log(`Redibujando ${lastBoxes.length} cajas almacenadas`);
2750
- lastBoxes.forEach(box => {
2751
- if (box && box.length >= 5) {
2752
- const [x1, y1, x2, y2, confidence] = box;
2753
-
2754
- // Dibujar rectángulo
2755
- displayCtx.strokeStyle = '#00FF00'; // Verde brillante
2756
- displayCtx.lineWidth = 6; // Línea más gruesa
2757
- displayCtx.strokeRect(x1, y1, x2-x1, y2-y1);
2758
-
2759
- // Añadir un relleno semitransparente para mayor visibilidad
2760
- displayCtx.fillStyle = 'rgba(0, 255, 0, 0.2)';
2761
- displayCtx.fillRect(x1, y1, x2-x1, y2-y1);
2762
-
2763
- // Añadir un fondo para el texto
2764
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2765
- displayCtx.fillRect(x1, y1-25, 140, 25);
2766
-
2767
- // Dibujar etiqueta con fuente más grande
2768
- displayCtx.fillStyle = '#FFFF00'; // Amarillo brillante
2769
- displayCtx.font = 'bold 18px Arial';
2770
- displayCtx.fillText(`Rostro: ${confidence.toFixed(2)}`, x1+5, y1-5);
2771
- }
2772
- });
2773
-
2774
- // Añadir contador de rostros
2775
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2776
- displayCtx.fillRect(10, 10, 200, 30);
2777
- displayCtx.fillStyle = '#FFFFFF';
2778
- displayCtx.font = 'bold 18px Arial';
2779
- displayCtx.fillText(`Rostros: ${lastBoxes.length}`, 20, 30);
2780
- } else if (video.readyState === video.HAVE_ENOUGH_DATA) {
2781
- // Si no hay cajas almacenadas pero el video está activo,
2782
- // simplemente mostrar el mensaje "No se detectan rostros"
2783
-
2784
- // Mensaje de estado
2785
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2786
- displayCtx.fillRect(10, 10, 250, 30);
2787
- displayCtx.fillStyle = '#FF9900';
2788
- displayCtx.font = 'bold 18px Arial';
2789
- displayCtx.fillText('No se detectan rostros', 20, 30);
2790
- }
2791
- }
2792
 
2793
- // Debug para todos los mensajes recibidos
2794
- window.addEventListener('message', function(event) {
2795
- console.log('>>> Mensaje recibido:', event.data);
 
2796
 
2797
- // Verificar si es un mensaje con cajas de rostros
2798
- if (event.data && event.data.type === 'faceBoxes') {
2799
- const message = event.data;
2800
- console.log('🔴 Recibido mensaje de cajas faciales:', message);
2801
-
2802
- const boxes = message.boxes;
2803
- if (boxes && Array.isArray(boxes)) {
2804
- // Guardar cajas para redibujar en cada frame
2805
- lastBoxes = boxes;
2806
-
2807
- // Limpiar el canvas y dibujar el frame actual
2808
- displayCtx.drawImage(video, 0, 0, display.width, display.height);
2809
-
2810
- // Dibujar cada caja
2811
- if (boxes.length > 0) {
2812
- console.log(`Dibujando ${boxes.length} cajas de rostros`);
2813
-
2814
- // Añadir texto grande para diagnóstico
2815
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2816
- displayCtx.fillRect(10, 50, 300, 40);
2817
- displayCtx.fillStyle = '#00FF00';
2818
- displayCtx.font = 'bold 20px Arial';
2819
- displayCtx.fillText(`DETECTADAS: ${boxes.length} CAJAS`, 20, 80);
2820
-
2821
- boxes.forEach(box => {
2822
- if (box && box.length >= 5) {
2823
- const [x1, y1, x2, y2, confidence] = box;
2824
- console.log(`Dibujando caja: (${x1}, ${y1}, ${x2}, ${y2}, ${confidence})`);
2825
-
2826
- // Dibujar rectángulo con colores más brillantes y líneas más gruesas
2827
- displayCtx.strokeStyle = '#00FF00'; // Verde brillante
2828
- displayCtx.lineWidth = 6; // Línea más gruesa
2829
- displayCtx.strokeRect(x1, y1, x2-x1, y2-y1);
2830
-
2831
- // Añadir un relleno semitransparente para mayor visibilidad
2832
- displayCtx.fillStyle = 'rgba(0, 255, 0, 0.2)';
2833
- displayCtx.fillRect(x1, y1, x2-x1, y2-y1);
2834
-
2835
- // Añadir un fondo para el texto
2836
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2837
- displayCtx.fillRect(x1, y1-25, 160, 25);
2838
-
2839
- // Dibujar etiqueta con fuente más grande
2840
- displayCtx.fillStyle = '#FFFF00'; // Amarillo brillante
2841
- displayCtx.font = 'bold 18px Arial';
2842
- displayCtx.fillText(`ROSTRO: ${confidence.toFixed(2)}`, x1+5, y1-5);
2843
- } else {
2844
- console.warn('Formato de caja inválido:', box);
2845
- }
2846
- });
2847
- } else {
2848
- // Mensaje cuando no se detectan rostros
2849
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2850
- displayCtx.fillRect(10, 10, 350, 30);
2851
- displayCtx.fillStyle = '#FF9900';
2852
- displayCtx.font = 'bold 18px Arial';
2853
- displayCtx.fillText('No se detectan rostros (Usa conf. < 0.2)', 20, 30);
2854
-
2855
- // Añadir texto de diagnóstico adicional
2856
- displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
2857
- displayCtx.fillRect(10, 50, 400, 90);
2858
- displayCtx.fillStyle = '#FF9900';
2859
- displayCtx.font = 'bold 16px Arial';
2860
- displayCtx.fillText('DIAGNÓSTICO:', 20, 70);
2861
- displayCtx.fillStyle = '#FFFFFF';
2862
- displayCtx.font = '14px Arial';
2863
- displayCtx.fillText('1. Reduce el umbral de confianza a 0.1-0.2', 20, 90);
2864
- displayCtx.fillText('2. Mejora la iluminación (luz frontal)', 20, 110);
2865
- displayCtx.fillText('3. Mira directo a la cámara', 20, 130);
2866
-
2867
- console.log('No hay cajas para dibujar o formato inválido');
2868
-
2869
- // Limpiar cajas almacenadas si se recibe explícitamente un array vacío
2870
- if (!message.forceDisplay) {
2871
- lastBoxes = [];
2872
- }
2873
- }
2874
- }
2875
- }
2876
- });
2877
-
2878
- // Arrancar todo
2879
- async function initCapture() {
2880
- await setupCamera();
2881
- // Empezar a capturar frames periódicamente
2882
- captureInterval = setInterval(captureFrame, captureDelay);
2883
- console.log(`Captura iniciada con intervalo de ${captureDelay}ms`);
2884
- }
2885
-
2886
- // Limpiar al salir
2887
- function stopCapture() {
2888
- clearInterval(captureInterval);
2889
- if (video.srcObject) {
2890
- video.srcObject.getTracks().forEach(track => track.stop());
2891
- }
2892
- console.log('Captura detenida');
2893
- }
2894
-
2895
- // Iniciar captura automáticamente
2896
- initCapture();
2897
-
2898
- // Limpiar cuando se desmonte el componente
2899
- window.addEventListener('beforeunload', stopCapture);
2900
- </script>
2901
- """
2902
-
2903
- # Unir todas las partes para formar el HTML completo
2904
- camera_component_html = camera_html_part1 + camera_html_part2 + camera_html_part3
2905
-
2906
- # Renderizar el componente
2907
- camera_component = st.components.v1.html(camera_component_html, height=520)
2908
-
2909
- # Procesar la imagen si está disponible (desde JavaScript)
2910
- if camera_component is not None and isinstance(camera_component, dict):
2911
- if 'error' in camera_component:
2912
- st.error(f"Camera error: {camera_component['error']}")
2913
- elif 'image' in camera_component:
2914
  try:
2915
- # Convertir image base64 a imagen OpenCV
2916
- encoded_data = camera_component['image'].split(',')[1]
2917
- nparr = np.frombuffer(base64.b64decode(encoded_data), np.uint8)
2918
- image = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
2919
-
2920
- if image is not None and image.size > 0:
2921
- # Usar la configuración local
2922
- local_confidence = st.session_state.get('continuous_confidence_threshold', 0.5)
2923
-
2924
- # Detectar rostros
2925
- bboxes = detect_face_dnn(face_net, image, local_confidence)
2926
-
2927
- # Actualizar métricas
2928
- faces_metric.metric("Faces detected", len(bboxes))
2929
-
2930
- # Incrementar contador de frames procesados
2931
- st.session_state.frames_processed += 1
2932
-
2933
- # Calcular FPS real (actualizar cada segundo)
2934
- current_time = time.time()
2935
- elapsed = current_time - st.session_state.start_time
2936
- if current_time - st.session_state.last_fps_update >= 1.0:
2937
- fps = st.session_state.frames_processed / elapsed
2938
- fps_metric.metric("FPS", f"{fps:.1f}")
2939
- st.session_state.last_fps_update = current_time
2940
-
2941
- # Enviar las cajas detectadas al componente JavaScript para dibujarlas en tiempo real
2942
- if bboxes is not None and len(bboxes) > 0:
2943
- # Convertir a lista si es un array numpy
2944
- bbox_list = bboxes.tolist() if isinstance(bboxes, np.ndarray) else bboxes
2945
-
2946
- # Imprimir en el servidor para depuración
2947
- print(f"Cajas de rostros detectadas: {bbox_list}")
2948
-
2949
- # Crear script para enviar datos al componente JavaScript - Versión mejorada con múltiples métodos
2950
- face_boxes_js = f"""
2951
- <script>
2952
- // Versión mejorada para envío garantizado de detecciones faciales
2953
- (function() {{
2954
- console.log("DETECCIÓN FACIAL: Enviando datos de {len(bbox_list)} rostros");
2955
-
2956
- const message = {{
2957
- type: 'faceBoxes',
2958
- boxes: {json.dumps(bbox_list)},
2959
- timestamp: Date.now()
2960
- }};
2961
-
2962
- // Función para enviar mensaje a todos los destinos posibles
2963
- function broadcastMessage() {{
2964
- console.log("Intentando enviar mensaje a todos los destinos posibles");
2965
-
2966
- // 1. Enviar directo al window
2967
- try {{
2968
- window.postMessage(message, '*');
2969
- console.log("Mensaje enviado a window");
2970
- }} catch(e) {{
2971
- console.error("Error enviando a window:", e);
2972
- }}
2973
-
2974
- // 2. Enviar al parent (desde iframe)
2975
- try {{
2976
- window.parent.postMessage(message, '*');
2977
- console.log("Mensaje enviado a parent");
2978
- }} catch(e) {{
2979
- console.error("Error enviando a parent:", e);
2980
- }}
2981
-
2982
- // 3. Enviar a todos los iframes en la página
2983
- try {{
2984
- const frames = document.getElementsByTagName('iframe');
2985
- console.log(`Encontrados ${{frames.length}} iframes`);
2986
- for(let i = 0; i < frames.length; i++) {{
2987
- try {{
2988
- frames[i].contentWindow.postMessage(message, '*');
2989
- console.log(`Mensaje enviado a iframe[${{i}}]`);
2990
- }} catch(e) {{
2991
- console.error(`Error enviando a iframe[${{i}}]:`, e);
2992
- }}
2993
- }}
2994
- }} catch(e) {{
2995
- console.error("Error accediendo a iframes:", e);
2996
- }}
2997
-
2998
- // 4. Enviar a todos los iframes en el parent
2999
- try {{
3000
- const parentFrames = window.parent.document.getElementsByTagName('iframe');
3001
- console.log(`Encontrados ${{parentFrames.length}} iframes en parent`);
3002
- for(let i = 0; i < parentFrames.length; i++) {{
3003
- try {{
3004
- parentFrames[i].contentWindow.postMessage(message, '*');
3005
- console.log(`Mensaje enviado a parent.iframe[${{i}}]`);
3006
- }} catch(e) {{
3007
- console.error(`Error enviando a parent.iframe[${{i}}]:`, e);
3008
- }}
3009
- }}
3010
- }} catch(e) {{
3011
- console.error("Error accediendo a iframes de parent:", e);
3012
- }}
3013
- }}
3014
-
3015
- // Llamar inmediatamente
3016
- broadcastMessage();
3017
-
3018
- // Reintentar varias veces para asegurar la entrega
3019
- setTimeout(broadcastMessage, 100);
3020
- setTimeout(broadcastMessage, 500);
3021
- setTimeout(broadcastMessage, 1000);
3022
- }})();
3023
- </script>
3024
- """
3025
-
3026
- # Inyectar el script en la página
3027
- components.html(face_boxes_js, height=0, width=0)
3028
-
3029
- # Agregar un script para forzar la visualización de los rectángulos
3030
- # y actualizar el estado del canvas incluso si no hay detecciones
3031
- force_display_js = """
3032
- <script>
3033
- (function() {
3034
- console.log("Forzando actualización del display");
3035
-
3036
- const noDetectionMessage = {
3037
- type: 'faceBoxes',
3038
- boxes: [],
3039
- timestamp: Date.now(),
3040
- forceDisplay: false
3041
- };
3042
-
3043
- // Enviar a todos los destinos posibles
3044
- try { window.parent.postMessage(noDetectionMessage, '*'); } catch(e) {}
3045
- try { window.postMessage(noDetectionMessage, '*'); } catch(e) {}
3046
-
3047
- try {
3048
- const frames = document.getElementsByTagName('iframe');
3049
- for(let i = 0; i < frames.length; i++) {
3050
- try { frames[i].contentWindow.postMessage(noDetectionMessage, '*'); } catch(e) {}
3051
- }
3052
- } catch(e) {}
3053
- })();
3054
- </script>
3055
- """
3056
- components.html(force_display_js, height=0, width=0)
3057
  except Exception as e:
3058
- st.error(f"Error processing camera frame: {str(e)}")
3059
- st.info("Camera continues to run. Processing will be attempted on next frame.")
3060
-
3061
- # Añadir opción de cámara alternativa para entornos donde WebRTC no funciona bien
3062
- st.markdown("---")
3063
- st.markdown("### Alternative Camera Mode")
3064
-
3065
- col1, col2 = st.columns(2)
3066
- simple_camera = col1.button("Use Simple Camera", key="simple_camera_button1", use_container_width=True)
3067
- stop_simple_camera = col2.button("Stop Camera", key="stop_camera_button1", use_container_width=True)
3068
-
3069
- if simple_camera:
3070
- st.session_state.simple_camera = True
3071
- st.session_state.demo_running = False
3072
- st.session_state.upload_mode = False
3073
-
3074
- if stop_simple_camera:
3075
- st.session_state.simple_camera = False
3076
-
3077
- if st.session_state.get('simple_camera', False):
3078
- # Contenedor para la cámara
3079
- camera_container = st.container()
3080
-
3081
- # Configurar métricas
3082
- faces_metric.metric("Faces detected", 0)
3083
- fps_metric.metric("FPS", "N/A")
3084
- time_metric.metric("Status", "Running")
3085
-
3086
- # Cámara simple que toma una imagen a la vez
3087
- with camera_container:
3088
- st.info("Simple camera activated. Each image is processed individually. Take a photo with your camera to detect faces.")
3089
-
3090
- # Usar imagen de la cámara
3091
- captured_image = st.camera_input("Take photo for recognition", key="camera_simple_input")
3092
-
3093
- # Procesar la imagen si está disponible
3094
- if captured_image is not None:
3095
- try:
3096
- # Leer imagen
3097
- image_bytes = captured_image.getvalue()
3098
- image = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR)
3099
 
3100
- if image is not None and image.size > 0:
3101
- # Detectar rostros
3102
- bboxes = detect_face_dnn(face_net, image, confidence_threshold)
3103
-
3104
- # Actualizar métricas
3105
- faces_metric.metric("Faces detected", len(bboxes))
3106
-
3107
- # Dibujar resultados
3108
- result_img = image.copy()
3109
- for i, bbox in enumerate(bboxes):
3110
- x1, y1, x2, y2, conf = bbox
3111
- cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
3112
- cv2.putText(result_img, f"Face {i+1}: {conf:.2f}", (x1, y1-10),
3113
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
3114
-
3115
- # Mostrar resultado
3116
- st.image(result_img, channels="BGR", caption="Detected faces", use_container_width=True)
3117
-
3118
- if len(bboxes) > 0:
3119
- # Si hay rostros registrados, intentar reconocerlos
3120
- if st.session_state.face_database and len(st.session_state.face_database) > 0:
3121
- st.subheader("Face Recognition:")
3122
-
3123
- recognition_results = []
3124
- for i, bbox in enumerate(bboxes):
3125
- x1, y1, x2, y2, _ = bbox
3126
- face_img = image[y1:y2, x1:x2]
3127
-
3128
- # Extraer el embedding del rostro
3129
- if model_choice == "VGG-Face":
3130
- embedding = vggface_model(face_img)
3131
- elif model_choice == "Facenet":
3132
- embedding = facenet_model(face_img)
3133
- elif model_choice == "OpenFace":
3134
- embedding = openface_model(face_img)
3135
- elif model_choice == "ArcFace":
3136
- embedding = arcface_model(face_img)
3137
- else: # Default to VGG-Face
3138
- embedding = vggface_model(face_img)
3139
-
3140
- # Comparar con rostros registrados
3141
- best_match = None
3142
- best_similarity = -1
3143
-
3144
- for name, info in st.session_state.face_database.items():
3145
- if 'embeddings' in info and info['embeddings']:
3146
- for emb_info in info['embeddings']:
3147
- if emb_info['model'] == model_choice:
3148
- stored_emb = emb_info['embedding']
3149
- similarity = cosine_similarity(embedding, stored_emb)
3150
-
3151
- if similarity > similarity_threshold and similarity > best_similarity:
3152
- best_similarity = similarity
3153
- best_match = name
3154
-
3155
- if best_match is not None:
3156
- recognition_results.append({
3157
- 'bbox': bbox,
3158
- 'name': best_match,
3159
- 'similarity': best_similarity
3160
- })
3161
-
3162
- # Mostrar resultados de reconocimiento
3163
- if recognition_results:
3164
- result_with_names = result_img.copy()
3165
- for result in recognition_results:
3166
- x1, y1, x2, y2, _ = result['bbox']
3167
- name = result['name']
3168
- similarity = result['similarity']
3169
-
3170
- # Dibujar nombre y similitud
3171
- cv2.rectangle(result_with_names, (x1, y1), (x2, y2), (0, 255, 0), 2)
3172
- label = f"{name}: {similarity:.2f}"
3173
- cv2.putText(result_with_names, label, (x1, y1-10),
3174
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
3175
-
3176
- with result_container:
3177
- st.image(result_with_names, channels="BGR", caption="Real-time capture", use_container_width=True)
3178
-
3179
- # Mostrar tabla de resultados
3180
- results_df = pd.DataFrame([
3181
- {"Name": r['name'], "Confidence": f"{r['similarity']:.2f}"}
3182
- for r in recognition_results
3183
- ])
3184
- st.table(results_df)
3185
- else:
3186
- st.warning("Could not recognize any of the detected faces.")
3187
- else:
3188
- st.info("No registered faces to compare. Please register faces in the 'Face Registration' section.")
3189
-
3190
- st.success(f"{len(bboxes)} faces detected")
3191
- else:
3192
- st.warning("No faces detected. Try with better lighting or a different position.")
3193
- else:
3194
- st.error("Could not process the image. Try taking another photo.")
3195
  except Exception as e:
3196
- st.error(f"Error processing image: {str(e)}")
3197
- st.info("Try taking another photo or use another option.")
3198
-
3199
- # Opción alternativa en caso de problemas con WebRTC (mantenemos esta opción también)
3200
- st.markdown("---")
3201
- st.markdown("### Other Options")
3202
- col1, col2 = st.columns(2)
3203
- demo_mode = col1.button("Use Demo Mode", key="demo_button1")
3204
- upload_mode = col2.button("Upload Image for Recognition", key="upload_button1")
3205
-
3206
- if demo_mode:
3207
- st.session_state.demo_running = True
3208
- st.session_state.upload_mode = False
3209
- st.session_state.simple_camera = False
3210
- elif upload_mode:
3211
- st.session_state.upload_mode = True
3212
- st.session_state.demo_running = False
3213
- st.session_state.simple_camera = False
3214
-
3215
- # Modo de demostración con imágenes simuladas
3216
- if st.session_state.get('demo_running', False):
3217
- # Cargar algunas imágenes de ejemplo (usar tus propias imágenes si es posible)
3218
- demo_img = None
3219
-
3220
- # Intentar usar una imagen de la base de datos
3221
- if st.session_state.face_database:
3222
- for name, info in st.session_state.face_database.items():
3223
- if 'image' in info:
3224
  try:
3225
- demo_img = info['image']
3226
- break
3227
  except:
3228
- pass
3229
-
3230
- # Si no hay imagen disponible, crear una imagen en blanco
3231
- if demo_img is None:
3232
- demo_img = np.ones((480, 640, 3), dtype=np.uint8) * 255
3233
- # Dibujar un círculo como "cara" simulada
3234
- cv2.circle(demo_img, (320, 240), 100, (0, 0, 255), -1)
3235
- cv2.circle(demo_img, (280, 200), 15, (255, 255, 255), -1)
3236
- cv2.circle(demo_img, (360, 200), 15, (255, 255, 255), -1)
3237
- cv2.ellipse(demo_img, (320, 260), (50, 30), 0, 0, 180, (255, 255, 255), -1)
3238
-
3239
- # Mostrar la imagen
3240
- st.image(demo_img, channels="BGR", caption="Demo Mode", use_container_width=True)
3241
-
3242
- # Simular métricas
3243
- faces_metric.metric("Faces detected", 1)
3244
- fps_metric.metric("FPS", "15.5")
3245
- time_metric.metric("Status", "Demo")
3246
-
3247
- st.success("Demo mode activated. In a local environment, real-time facial recognition would work correctly.")
3248
 
3249
- # Modo de carga de imagen
3250
- if st.session_state.get('upload_mode', False):
3251
- uploaded_file = st.file_uploader("Upload an image with faces", type=["jpg", "jpeg", "png"], key="upload_image_input")
3252
-
3253
- if uploaded_file is not None:
3254
- # Leer imagen
3255
- image_bytes = uploaded_file.read()
3256
- image = cv2.imdecode(np.frombuffer(image_bytes, np.uint8), cv2.IMREAD_COLOR)
 
 
 
 
 
3257
 
3258
- # Detectar rostros
3259
- bboxes = detect_face_dnn(face_net, image, confidence_threshold)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3260
 
3261
- # Dibujar rostros detectados
3262
- result_img = image.copy()
3263
- for i, bbox in enumerate(bboxes):
3264
- x1, y1, x2, y2, _ = bbox
3265
- cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
3266
- cv2.putText(result_img, f"Face {i+1}", (x1, y1-10),
3267
- cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
3268
 
3269
- # Mostrar resultado
3270
- st.image(result_img, channels="BGR", caption="Detected faces", use_container_width=True)
3271
 
3272
- # Actualizar métricas
3273
- faces_metric.metric("Faces detected", len(bboxes))
3274
- time_metric.metric("Status", "Processed")
3275
-
3276
- # Add a note about privacy
3277
- st.markdown("---")
3278
- st.markdown("**Privacy Note**: Video is processed in your browser and on the server. No video data is stored permanently.")
3279
-
3280
- # Agregar opción para cargar imagen desde URL
3281
- use_url = st.checkbox("Load image from URL")
3282
-
3283
- # Diagnóstico para verificar modelos
3284
- with st.expander("🔍 Diagnóstico de detección", expanded=False):
3285
- st.warning("Si los rectángulos no aparecen, usa esta herramienta para verificar que los modelos de detección están funcionando correctamente.")
3286
- col1, col2 = st.columns(2)
3287
-
3288
- if col1.button("Ver diagnóstico de detección"):
3289
- try:
3290
- with open("diagnostico_deteccion.txt", "r") as f:
3291
- diagnostico = f.read()
3292
- st.code(diagnostico, language="text")
3293
- except FileNotFoundError:
3294
- st.info("Aún no hay información de diagnóstico disponible. Procesa una imagen primero.")
3295
-
3296
- if col2.button("Verificar modelo"):
3297
- st.info("Verificando modelo de detección facial...")
3298
 
3299
- # Verificar si el modelo está cargado correctamente
3300
- try:
3301
- if 'face_net' in locals():
3302
- st.success(f"Modelo cargado: {type(face_net)}")
3303
- st.json({"Modelo": str(type(face_net)),
3304
- "Estado": "Cargado correctamente",
3305
- "Archivo del modelo": "res10_300x300_ssd_iter_140000.caffemodel",
3306
- "Archivo de configuración": "deploy.prototxt.txt"})
3307
- else:
3308
- face_net = load_face_model()
3309
- st.success(f"Modelo cargado durante verificación: {type(face_net)}")
3310
- except Exception as e:
3311
- st.error(f"Error al verificar modelo: {str(e)}")
3312
-
3313
- if use_url:
3314
- # Agregar campo de URL
3315
- url = st.text_input("Enter image URL")
3316
-
3317
- if st.button("Load Image from URL"):
3318
- try:
3319
- # Cargar imagen desde URL
3320
- response = urllib.request.urlopen(url)
3321
- image_data = response.read()
3322
- image = cv2.imdecode(np.frombuffer(image_data, np.uint8), cv2.IMREAD_COLOR)
3323
-
3324
- if image is not None and image.size > 0:
3325
- # Detectar rostros
3326
- bboxes = detect_face_dnn(face_net, image, conf_threshold)
3327
-
3328
- if bboxes:
3329
- # Mostrar imagen con rostros detectados
3330
- st.image(image, channels='BGR', caption="Detected faces")
3331
-
3332
- # Mostrar estadísticas
3333
- st.subheader("Detection Summary")
3334
- summary_col1, summary_col2, summary_col3 = st.columns(3)
3335
- summary_col1.metric("Faces Detected", len(bboxes))
3336
- summary_col2.metric("Eyes Detected", 0)
3337
- summary_col3.metric("Smiles Detected", 0)
3338
-
3339
- # Mostrar cajas detectadas
3340
- st.subheader("Detected Faces")
3341
- for i, bbox in enumerate(bboxes):
3342
- x1, y1, x2, y2, _ = bbox
3343
- cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
3344
- cv2.putText(image, f"Face {i+1}", (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
3345
- else:
3346
- st.warning("No faces detected. Please try a different URL.")
3347
- else:
3348
- st.warning("Failed to load image. Please check the URL.")
3349
- except Exception as e:
3350
- st.error(f"Error loading image from URL: {str(e)}")
3351
 
3352
  # Si se ejecuta este archivo directamente, llamar a la función main
3353
  if __name__ == "__main__":
 
74
  # Navigation menu
75
  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
  # Function to load DNN models with caching and auto-download
 
989
  if detect_eyes: # type: ignore
990
  summary_col2.metric("Avg. Eyes per Frame", detection_stats["eyes"])
991
  else:
992
+ summary_col2.metric("Avg. Eyes Detected", "N/A")
993
 
994
  if detect_smile: # type: ignore
995
  summary_col3.metric("Avg. Smiles per Frame", detection_stats["smiles"])
996
  else:
997
+ summary_col3.metric("Avg. Smiles Detected", "N/A")
998
 
999
  # Provide download link
1000
  with open(output_path, 'rb') as f:
 
1019
  st.subheader("Real-time face detection")
1020
  st.write("Click 'Start Camera' to begin real-time face detection.")
1021
 
1022
+ # Verificar si WebRTC está disponible
1023
+ if not WEBRTC_AVAILABLE:
1024
+ st.error("WebRTC components are not available. Real-time camera features are disabled.")
1025
+ 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.")
1026
+ else:
1027
+ # Placeholder for webcam video
1028
+ camera_placeholder = st.empty()
 
 
 
 
 
 
 
 
 
 
1029
 
1030
+ # Buttons to control the camera
1031
+ col1, col2 = st.columns(2)
1032
+ start_button = col1.button("Start Camera", on_click=start_camera)
1033
+ stop_button = col2.button("Stop Camera", on_click=stop_camera)
1034
+
1035
+ # Show message when camera is stopped
1036
+ if 'camera_stopped' in st.session_state and st.session_state.camera_stopped:
1037
+ st.info("Camera stopped. Click 'Start Camera' to activate it again.")
1038
+ st.session_state.camera_stopped = False
1039
+
1040
+ if st.session_state.camera_running:
1041
+ st.info("Camera activated. Processing real-time video...")
1042
+ # Initialize webcam
1043
+ cap = cv2.VideoCapture(0) # 0 is typically the main webcam
1044
+
1045
+ if not cap.isOpened():
1046
+ st.error("Could not access webcam. Make sure it's connected and not being used by another application.")
1047
+ 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.")
1048
+ st.session_state.camera_running = False
1049
+ else:
1050
+ # Display real-time video with face detection
1051
+ try:
1052
+ while st.session_state.camera_running:
1053
+ ret, frame = cap.read()
1054
+ if not ret:
1055
+ st.error("Error reading frame from camera.")
1056
+ break
1057
+
1058
+ # Detect faces
1059
+ detections = detect_face_dnn(face_net, frame, conf_threshold)
1060
+ processed_frame, bboxes = process_face_detections(frame, detections, conf_threshold, bbox_color_bgr)
1061
+
1062
+ # Display the processed frame
1063
+ camera_placeholder.image(processed_frame, channels="BGR", use_container_width=True)
1064
+
1065
+ # Small pause to avoid overloading the CPU
1066
+ time.sleep(0.01)
1067
+ finally:
1068
+ # Release the camera when stopped
1069
+ cap.release()
1070
 
1071
  elif app_mode == "Feature Detection":
1072
  # Load all required models
 
2363
  if not st.session_state.face_database:
2364
  st.warning("No faces registered. Please register at least one face first.")
2365
  else:
2366
+ # Preparar layout para métricas
2367
+ st.markdown("### Recognition Metrics")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2368
  metrics_cols = st.columns(3)
2369
+ faces_metric = metrics_cols[0]
2370
+ fps_metric = metrics_cols[1]
2371
+ time_metric = metrics_cols[2]
 
 
 
2372
 
2373
+ # Inicializar métricas
 
 
 
 
 
 
 
 
 
2374
  if 'faces_detected' not in st.session_state:
2375
  st.session_state.faces_detected = 0
2376
  if 'fps' not in st.session_state:
2377
  st.session_state.fps = 0
2378
 
2379
+ # Configuración para WebRTC
2380
+ rtc_configuration = RTCConfiguration(
2381
+ {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2382
  )
2383
 
2384
+ # Verificar disponibilidad de WebRTC
2385
+ if not WEBRTC_AVAILABLE:
2386
+ st.error("WebRTC components are not available. Real-time camera features will be limited.")
2387
+ st.info("This may be due to running in Hugging Face Spaces environment or missing dependencies.")
2388
+ # Saltar directo al modo alternativo de captura
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2389
  st.session_state.continuous_capture = True
2390
+ st.session_state.webrtc_available = False
2391
+ else:
2392
+ st.session_state.webrtc_available = True
2393
+
2394
+ # Solo mostrar WebRTC si está disponible
2395
+ class VideoProcessor(VideoProcessorBase):
2396
+ def __init__(self):
2397
+ self.frame_count = 0
2398
+ self.face_count = 0
2399
+ self.start_time = time.time()
2400
+ self.processing = True
2401
+ self.frame_skip = 2 # Process every other frame to reduce load
2402
+ self.frames_processed = 0
2403
+ self.last_log_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2404
 
2405
+ def recv(self, frame):
2406
+ try:
2407
+ img = frame.to_ndarray(format="bgr24")
2408
+ self.frame_count += 1
2409
 
2410
+ # Solo procesar algunos frames para reducir carga
2411
+ if self.frame_count % self.frame_skip != 0:
2412
+ return av.VideoFrame.from_ndarray(img, format="bgr24")
2413
+
2414
+ self.frames_processed += 1
2415
+ now = time.time()
2416
+
2417
+ # Registro de diagnóstico cada 5 segundos
2418
+ if now - self.last_log_time > 5:
2419
+ print(f"Frames procesados: {self.frames_processed}, " +
2420
+ f"Tiempo transcurrido: {now - self.start_time:.1f}s, " +
2421
+ f"FPS: {self.frames_processed/(now - self.start_time):.1f}")
2422
+ self.last_log_time = now
2423
+
2424
+ # Verificar que la imagen no sea nula
2425
+ if img is None or img.size == 0 or img.shape[0] == 0 or img.shape[1] == 0:
2426
+ # Si la imagen es inválida, devolver un frame en blanco
2427
+ blank_frame = np.ones((480, 640, 3), dtype=np.uint8) * 255
2428
+ cv2.putText(blank_frame, "Error: Invalid frame", (50, 240),
2429
+ cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
2430
+ return av.VideoFrame.from_ndarray(blank_frame, format="bgr24")
2431
+
2432
+ # Reducir tamaño del frame para procesamiento más rápido
2433
+ scale_factor = 0.5
2434
+ h, w = img.shape[:2]
2435
+
2436
+ small_img = safe_resize(img, (int(w * scale_factor), int(h * scale_factor)))
2437
+ if small_img is None:
2438
+ # Si no se puede redimensionar, usar el frame original (solo para diagnóstico)
2439
+ print("No se pudo redimensionar la imagen para procesamiento")
2440
+ return av.VideoFrame.from_ndarray(img, format="bgr24")
2441
+
2442
+ # Detect faces - la función ahora devuelve directamente los bboxes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2443
  try:
2444
+ bboxes = detect_face_dnn(face_net, small_img, confidence_threshold)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2445
  except Exception as e:
2446
+ print(f"Error al detectar rostros: {e}")
2447
+ bboxes = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2448
 
2449
+ # Ajustar bounding boxes al tamaño original
2450
+ original_bboxes = []
2451
+ for x1, y1, x2, y2, conf in bboxes:
2452
+ original_bboxes.append((
2453
+ int(x1 / scale_factor),
2454
+ int(y1 / scale_factor),
2455
+ int(x2 / scale_factor),
2456
+ int(y2 / scale_factor),
2457
+ conf
2458
+ ))
2459
+
2460
+ # Actualizar contadores
2461
+ self.face_count = len(original_bboxes)
2462
+ current_time = time.time()
2463
+ elapsed_time = current_time - self.start_time
2464
+ fps = self.frames_processed / elapsed_time if elapsed_time > 0 else 0
2465
+
2466
+ # Actualizar métricas en session_state para que sean accesibles fuera
2467
+ st.session_state.faces_detected = self.face_count
2468
+ st.session_state.fps = fps
2469
+
2470
+ # Dibujar cajas de los rostros
2471
+ result_img = img.copy()
2472
+ for i, (x1, y1, x2, y2, conf) in enumerate(original_bboxes):
2473
+ cv2.rectangle(result_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
2474
+ cv2.putText(result_img, f"Face {i+1}: {conf:.2f}", (x1, y1-10),
2475
+ cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
2476
+
2477
+ # Añadir información FPS y rostros
2478
+ cv2.putText(result_img, f"FPS: {fps:.1f}", (10, 30),
2479
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
2480
+ cv2.putText(result_img, f"Faces: {self.face_count}", (10, 60),
2481
+ cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
2482
+
2483
+ return av.VideoFrame.from_ndarray(result_img, format="bgr24")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2484
  except Exception as e:
2485
+ print(f"Error general en procesamiento de video: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2486
  try:
2487
+ # Intentar devolver el frame original
2488
+ return av.VideoFrame.from_ndarray(img, format="bgr24")
2489
  except:
2490
+ # Si eso falla, devolver un frame en blanco como último recurso
2491
+ blank = np.ones((480, 640, 3), dtype=np.uint8) * 255
2492
+ return av.VideoFrame.from_ndarray(blank, format="bgr24")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2493
 
2494
+ # Display WebRTC streamer con opciones simplificadas para mejorar compatibilidad
2495
+ # Solo mostrar si WebRTC está disponible
2496
+ if st.session_state.webrtc_available:
2497
+ st.info("⚠️ If the video doesn't load: Try using Chrome, reload the page, or use the alternative options below.")
2498
+ try:
2499
+ webrtc_ctx = webrtc_streamer(
2500
+ key="face-recognition",
2501
+ mode=WebRtcMode.SENDRECV,
2502
+ rtc_configuration=rtc_configuration,
2503
+ media_stream_constraints={"video": {"width": 640, "height": 480}, "audio": False},
2504
+ video_processor_factory=VideoProcessor,
2505
+ async_processing=True,
2506
+ )
2507
 
2508
+ # Establecer y actualizar métricas
2509
+ if webrtc_ctx.state.playing:
2510
+ faces_metric.metric("Faces detected", st.session_state.get('faces_detected', 0))
2511
+ fps_metric.metric("FPS", f"{st.session_state.get('fps', 0):.1f}")
2512
+ time_metric.metric("Status", "Running")
2513
+
2514
+ # Mostrar instrucciones de uso
2515
+ st.success("Webcam activated. Detected faces will be identified in real-time.")
2516
+ else:
2517
+ faces_metric.metric("Faces detected", 0)
2518
+ fps_metric.metric("FPS", "0")
2519
+ time_metric.metric("Status", "Stopped")
2520
+
2521
+ # Mostrar instrucciones de activación
2522
+ st.warning("Click START to activate the webcam. This feature may not be available in environments like Hugging Face Spaces due to security restrictions.")
2523
+ except Exception as e:
2524
+ st.error(f"Error initializing WebRTC: {str(e)}")
2525
+ st.info("Switching to alternative camera mode...")
2526
+ st.session_state.continuous_capture = True
2527
+ st.session_state.webrtc_available = False
2528
+
2529
+ elif app_mode == "Diagnóstico":
2530
+ st.title("Diagnóstico de Detección Facial")
2531
+ st.markdown("""
2532
+ Esta herramienta ayuda a identificar problemas con la detección de rostros.
2533
+ """)
2534
+
2535
+ # Verificación de archivos de modelo
2536
+ st.header("1. Verificación de archivos de modelo")
2537
+
2538
+ # Verificar archivos de modelo
2539
+ model_file = "res10_300x300_ssd_iter_140000.caffemodel"
2540
+ config_file = "deploy.prototxt.txt"
2541
+
2542
+ model_exists = os.path.exists(model_file)
2543
+ config_exists = os.path.exists(config_file)
2544
+
2545
+ if model_exists:
2546
+ st.success(f"✅ Modelo encontrado: {model_file}")
2547
+ else:
2548
+ st.error(f"❌ Modelo no encontrado: {model_file}")
2549
+
2550
+ if config_exists:
2551
+ st.success(f"✅ Modelo encontrado: {config_file}")
2552
+ else:
2553
+ st.error(f"❌ Archivo de configuración no encontrado: {config_file}")
2554
+
2555
+ # Prueba de carga del modelo
2556
+ st.header("2. Prueba de carga del modelo")
2557
+
2558
+ try:
2559
+ if model_exists and config_exists:
2560
+ net = cv2.dnn.readNetFromCaffe(config_file, model_file)
2561
+ st.success(f"✅ Modelo cargado correctamente: <class '{type(net).__name__}'>")
2562
+ else:
2563
+ st.warning("⚠️ No se puede cargar el modelo porque faltan archivos")
2564
+ except Exception as e:
2565
+ st.error(f"❌ Error al cargar el modelo: {str(e)}")
2566
+
2567
+ # Sección para probar detección
2568
+ st.header("3. Probar detección")
2569
+
2570
+ # Agregar una imagen de prueba
2571
+ test_image = st.file_uploader("Sube una imagen de prueba", type=['jpg', 'jpeg', 'png'])
2572
+
2573
+ if test_image is not None:
2574
+ # Leer y mostrar la imagen
2575
+ raw_bytes = np.asarray(bytearray(test_image.read()), dtype=np.uint8)
2576
+ image = cv2.imdecode(raw_bytes, cv2.IMREAD_COLOR)
2577
+
2578
+ st.image(image, channels='BGR', caption="Imagen de prueba", use_container_width=True)
2579
+
2580
+ # Umbral de confianza ajustable
2581
+ conf_threshold = st.slider(
2582
+ "Umbral de confianza",
2583
+ min_value=0.05,
2584
+ max_value=0.95,
2585
+ value=0.3,
2586
+ step=0.05
2587
+ )
2588
+
2589
+ # Intentar detectar rostros
2590
+ if st.button("Probar detección"):
2591
+ st.write("Resultado con umbral", conf_threshold)
2592
+
2593
+ try:
2594
+ if model_exists and config_exists:
2595
+ # Intentar cargar el modelo nuevamente para asegurarse
2596
+ net = cv2.dnn.readNetFromCaffe(config_file, model_file)
2597
 
2598
+ # Detectar rostros
2599
+ detections = detect_face_dnn(net, image, conf_threshold)
2600
+ processed_image, bboxes = process_face_detections(image, detections, conf_threshold)
 
 
 
 
2601
 
2602
+ # Mostrar estadísticas
2603
+ st.write(f"Detecciones encontradas: {len(bboxes)}")
2604
 
2605
+ # Mostrar imagen procesada
2606
+ st.image(processed_image, channels='BGR', caption="Resultado con detecciones", use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2607
 
2608
+ if len(bboxes) == 0:
2609
+ st.error("No se detectaron rostros en la imagen de prueba.")
2610
+ st.warning("Posibles problemas:")
2611
+ st.markdown("""
2612
+ 1. El modelo no se está cargando correctamente.
2613
+ 2. El procesamiento de la imagen es incorrecto.
2614
+ 3. El umbral de confianza es demasiado alto.
2615
+ 4. Hay un problema con la visualización de los resultados.
2616
+ """)
2617
+ else:
2618
+ st.error("No se puede probar la detección porque faltan archivos del modelo")
2619
+ except Exception as e:
2620
+ st.error(f"Error durante la detección: {str(e)}")
2621
+
2622
+ # Información del sistema
2623
+ st.header("Estadísticas de detección")
2624
+ if os.path.exists("diagnostico_deteccion.txt"):
2625
+ with open("diagnostico_deteccion.txt", "r") as f:
2626
+ log_content = f.read()
2627
+ with st.expander("Ver registro de diagnóstico"):
2628
+ st.code(log_content, language="text")
2629
+ else:
2630
+ st.info("No hay archivo de diagnóstico disponible.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2631
 
2632
  # Si se ejecuta este archivo directamente, llamar a la función main
2633
  if __name__ == "__main__":