Spaces:
Build error
Build error
Commit
·
1f91d26
1
Parent(s):
6629b55
Restaurar implementación completa de detección facial en tiempo real con WebRTC
Browse files- 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 |
-
#
|
| 1023 |
-
|
| 1024 |
-
|
| 1025 |
-
|
| 1026 |
-
|
| 1027 |
-
|
| 1028 |
-
|
| 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 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
|
| 1046 |
-
|
| 1047 |
-
|
| 1048 |
-
|
| 1049 |
-
|
| 1050 |
-
|
| 1051 |
-
|
| 1052 |
-
|
| 1053 |
-
|
| 1054 |
-
|
| 1055 |
-
|
| 1056 |
-
|
| 1057 |
-
|
| 1058 |
-
|
| 1059 |
-
|
| 1060 |
-
|
| 1061 |
-
|
| 1062 |
-
|
| 1063 |
-
|
| 1064 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 2362 |
-
|
| 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 |
-
|
| 2413 |
-
|
| 2414 |
-
|
| 2415 |
-
fps_metric = st.empty()
|
| 2416 |
-
with metrics_cols[2]:
|
| 2417 |
-
time_metric = st.empty()
|
| 2418 |
|
| 2419 |
-
#
|
| 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 |
-
#
|
| 2435 |
-
|
| 2436 |
-
|
| 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 |
-
#
|
| 2546 |
-
if
|
| 2547 |
-
|
| 2548 |
-
|
| 2549 |
-
|
| 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.
|
| 2628 |
-
|
| 2629 |
-
st.session_state.
|
| 2630 |
-
|
| 2631 |
-
#
|
| 2632 |
-
|
| 2633 |
-
|
| 2634 |
-
|
| 2635 |
-
|
| 2636 |
-
|
| 2637 |
-
|
| 2638 |
-
|
| 2639 |
-
|
| 2640 |
-
|
| 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 |
-
|
| 2794 |
-
|
| 2795 |
-
|
|
|
|
| 2796 |
|
| 2797 |
-
|
| 2798 |
-
if
|
| 2799 |
-
|
| 2800 |
-
|
| 2801 |
-
|
| 2802 |
-
|
| 2803 |
-
|
| 2804 |
-
|
| 2805 |
-
|
| 2806 |
-
|
| 2807 |
-
|
| 2808 |
-
|
| 2809 |
-
|
| 2810 |
-
|
| 2811 |
-
|
| 2812 |
-
|
| 2813 |
-
|
| 2814 |
-
|
| 2815 |
-
|
| 2816 |
-
|
| 2817 |
-
|
| 2818 |
-
|
| 2819 |
-
|
| 2820 |
-
|
| 2821 |
-
|
| 2822 |
-
|
| 2823 |
-
|
| 2824 |
-
|
| 2825 |
-
|
| 2826 |
-
|
| 2827 |
-
|
| 2828 |
-
|
| 2829 |
-
|
| 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 |
-
|
| 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 |
-
|
| 3059 |
-
|
| 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 |
-
|
| 3101 |
-
|
| 3102 |
-
|
| 3103 |
-
|
| 3104 |
-
|
| 3105 |
-
|
| 3106 |
-
|
| 3107 |
-
|
| 3108 |
-
|
| 3109 |
-
|
| 3110 |
-
|
| 3111 |
-
|
| 3112 |
-
|
| 3113 |
-
|
| 3114 |
-
|
| 3115 |
-
|
| 3116 |
-
|
| 3117 |
-
|
| 3118 |
-
|
| 3119 |
-
|
| 3120 |
-
|
| 3121 |
-
|
| 3122 |
-
|
| 3123 |
-
|
| 3124 |
-
|
| 3125 |
-
|
| 3126 |
-
|
| 3127 |
-
|
| 3128 |
-
|
| 3129 |
-
|
| 3130 |
-
|
| 3131 |
-
|
| 3132 |
-
|
| 3133 |
-
|
| 3134 |
-
|
| 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 |
-
|
| 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 |
-
|
| 3226 |
-
|
| 3227 |
except:
|
| 3228 |
-
|
| 3229 |
-
|
| 3230 |
-
|
| 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 |
-
#
|
| 3250 |
-
|
| 3251 |
-
|
| 3252 |
-
|
| 3253 |
-
|
| 3254 |
-
|
| 3255 |
-
|
| 3256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3257 |
|
| 3258 |
-
#
|
| 3259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3260 |
|
| 3261 |
-
#
|
| 3262 |
-
|
| 3263 |
-
|
| 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
|
| 3270 |
-
st.
|
| 3271 |
|
| 3272 |
-
#
|
| 3273 |
-
|
| 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 |
-
|
| 3300 |
-
|
| 3301 |
-
|
| 3302 |
-
|
| 3303 |
-
|
| 3304 |
-
|
| 3305 |
-
|
| 3306 |
-
|
| 3307 |
-
|
| 3308 |
-
|
| 3309 |
-
|
| 3310 |
-
|
| 3311 |
-
|
| 3312 |
-
|
| 3313 |
-
|
| 3314 |
-
|
| 3315 |
-
|
| 3316 |
-
|
| 3317 |
-
|
| 3318 |
-
|
| 3319 |
-
|
| 3320 |
-
|
| 3321 |
-
|
| 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__":
|