Spaces:
Build error
Build error
Commit
·
3617555
1
Parent(s):
7b0cfb4
Mejorar visibilidad de la detección facial con rectángulos más visibles y colores destacados
Browse files- streamlit_app.py +172 -71
streamlit_app.py
CHANGED
|
@@ -114,50 +114,83 @@ def main():
|
|
| 114 |
return eye_cascade, smile_cascade
|
| 115 |
|
| 116 |
# Function for detecting faces in an image
|
| 117 |
-
def detect_face_dnn(net, frame, conf_threshold=0.
|
| 118 |
"""
|
| 119 |
-
Detecta rostros
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
try:
|
| 122 |
-
# Verificar que el frame sea válido
|
| 123 |
-
if frame is None or frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0:
|
| 124 |
-
return []
|
| 125 |
-
|
| 126 |
-
# Crear blob a partir del frame (redimensionar a 300x300, escalar, etc.)
|
| 127 |
-
blob = cv2.dnn.blobFromImage(frame, 1.0, (300, 300), [104, 117, 123], False, False)
|
| 128 |
-
|
| 129 |
-
# Establecer la entrada para la red neuronal
|
| 130 |
-
net.setInput(blob)
|
| 131 |
-
|
| 132 |
-
# Realizar la detección
|
| 133 |
detections = net.forward()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
|
| 162 |
# Function for processing face detections
|
| 163 |
def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):
|
|
@@ -2433,7 +2466,7 @@ def main():
|
|
| 2433 |
<div style="margin-bottom: 20px;">
|
| 2434 |
<video id="webcam" autoplay playsinline width="640" height="480" style="border-radius: 5px; display: none;"></video>
|
| 2435 |
<canvas id="canvas" width="640" height="480" style="display: none;"></canvas>
|
| 2436 |
-
<canvas id="display" width="640" height="480" style="border-radius: 5px; display: block; margin: 0 auto;"></canvas>
|
| 2437 |
</div>
|
| 2438 |
<script>
|
| 2439 |
const video = document.getElementById('webcam');
|
|
@@ -2446,6 +2479,17 @@ def main():
|
|
| 2446 |
// Para depuración
|
| 2447 |
console.log('Componente de cámara inicializado');
|
| 2448 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2449 |
// Configuración dinámica del FPS (desde Streamlit)
|
| 2450 |
const captureDelay = 1000 / %s;
|
| 2451 |
|
|
@@ -2484,6 +2528,9 @@ def main():
|
|
| 2484 |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 2485 |
displayCtx.drawImage(video, 0, 0, display.width, display.height);
|
| 2486 |
|
|
|
|
|
|
|
|
|
|
| 2487 |
// Convertir a base64
|
| 2488 |
const imageData = canvas.toDataURL('image/jpeg', 0.8);
|
| 2489 |
|
|
@@ -2521,22 +2568,46 @@ def main():
|
|
| 2521 |
const [x1, y1, x2, y2, confidence] = box;
|
| 2522 |
console.log(`Dibujando caja: (${x1}, ${y1}, ${x2}, ${y2}, ${confidence})`);
|
| 2523 |
|
| 2524 |
-
// Dibujar rectángulo
|
| 2525 |
-
displayCtx.strokeStyle = '#00FF00';
|
| 2526 |
-
displayCtx.lineWidth =
|
| 2527 |
displayCtx.strokeRect(x1, y1, x2-x1, y2-y1);
|
| 2528 |
|
| 2529 |
-
//
|
| 2530 |
-
displayCtx.fillStyle = '
|
| 2531 |
-
displayCtx.
|
| 2532 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2533 |
} else {
|
| 2534 |
console.warn('Formato de caja inválido:', box);
|
| 2535 |
}
|
| 2536 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2537 |
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2538 |
console.log('No hay cajas para dibujar o formato inválido');
|
| 2539 |
}
|
|
|
|
|
|
|
|
|
|
| 2540 |
}
|
| 2541 |
} catch (error) {
|
| 2542 |
console.error('Error al procesar mensaje:', error);
|
|
@@ -2545,12 +2616,14 @@ def main():
|
|
| 2545 |
|
| 2546 |
// Dibujar un rectángulo de prueba para verificar que el canvas funciona
|
| 2547 |
function drawTestRect() {
|
| 2548 |
-
displayCtx.strokeStyle = '#FF0000';
|
| 2549 |
-
displayCtx.lineWidth =
|
| 2550 |
displayCtx.strokeRect(50, 50, 100, 100);
|
|
|
|
|
|
|
| 2551 |
displayCtx.fillStyle = '#FF0000';
|
| 2552 |
-
displayCtx.font = '16px Arial';
|
| 2553 |
-
displayCtx.fillText('
|
| 2554 |
console.log('Rectángulo de prueba dibujado');
|
| 2555 |
}
|
| 2556 |
|
|
@@ -2626,30 +2699,58 @@ def main():
|
|
| 2626 |
|
| 2627 |
# Crear script para enviar datos al componente JavaScript
|
| 2628 |
face_boxes_js = f"""
|
| 2629 |
-
|
| 2630 |
-
|
| 2631 |
-
console.log('Enviando cajas al componente: {bbox_list}');
|
| 2632 |
-
// Usar postMessage para comunicarse con el componente
|
| 2633 |
-
window.postMessage({{
|
| 2634 |
-
type: 'faceBoxes',
|
| 2635 |
-
boxes: {json.dumps(bbox_list)}
|
| 2636 |
-
}}, '*');
|
| 2637 |
|
| 2638 |
-
//
|
| 2639 |
-
|
| 2640 |
-
window.parent.postMessage(
|
| 2641 |
-
|
| 2642 |
-
|
| 2643 |
-
|
| 2644 |
-
|
| 2645 |
-
|
| 2646 |
-
|
| 2647 |
-
|
| 2648 |
-
|
| 2649 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2650 |
"""
|
| 2651 |
-
|
|
|
|
| 2652 |
st.components.v1.html(face_boxes_js, height=0, width=0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2653 |
except Exception as e:
|
| 2654 |
st.error(f"Error processing camera frame: {str(e)}")
|
| 2655 |
st.info("Camera continues to run. Processing will be attempted on next frame.")
|
|
|
|
| 114 |
return eye_cascade, smile_cascade
|
| 115 |
|
| 116 |
# Function for detecting faces in an image
|
| 117 |
+
def detect_face_dnn(net, frame, conf_threshold=0.3):
|
| 118 |
"""
|
| 119 |
+
Detecta rostros en una imagen utilizando un modelo DNN pre-entrenado.
|
| 120 |
+
|
| 121 |
+
Args:
|
| 122 |
+
net: Modelo DNN cargado
|
| 123 |
+
frame: Imagen en formato BGR
|
| 124 |
+
conf_threshold: Umbral de confianza para la detección (0.0-1.0)
|
| 125 |
+
|
| 126 |
+
Returns:
|
| 127 |
+
Lista de bounding boxes con formato [x1, y1, x2, y2, confidence]
|
| 128 |
+
o None si no se detectan rostros
|
| 129 |
"""
|
| 130 |
+
# Añadir impresión de depuración para el umbral usado
|
| 131 |
+
print(f"Detecting faces with confidence threshold: {conf_threshold}")
|
| 132 |
+
|
| 133 |
+
# Obtener dimensiones de la imagen
|
| 134 |
+
h, w = frame.shape[:2]
|
| 135 |
+
|
| 136 |
+
# Crear un blob de la imagen (redimensionada a 300x300 y normalizada)
|
| 137 |
+
# IMPORTANTE: Los valores de media (104.0, 177.0, 123.0) son específicos
|
| 138 |
+
# para el modelo res10_300x300_ssd_iter_140000.caffemodel entrenado en Caffe
|
| 139 |
+
blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0,
|
| 140 |
+
(300, 300), (104.0, 177.0, 123.0))
|
| 141 |
+
|
| 142 |
+
# Pasar el blob a través de la red
|
| 143 |
+
net.setInput(blob)
|
| 144 |
+
|
| 145 |
+
# Realizar la detección (forward pass)
|
| 146 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
detections = net.forward()
|
| 148 |
+
print(f"Shape of detection output: {detections.shape}")
|
| 149 |
+
except Exception as e:
|
| 150 |
+
print(f"Error al procesar la imagen con el modelo DNN: {str(e)}")
|
| 151 |
+
return None
|
| 152 |
|
| 153 |
+
# Variable para almacenar las cajas delimitadoras
|
| 154 |
+
bboxes = []
|
| 155 |
+
|
| 156 |
+
# Procesar cada detección
|
| 157 |
+
detection_count = 0
|
| 158 |
+
for i in range(detections.shape[2]):
|
| 159 |
+
# Extraer la confianza (probabilidad) de la detección
|
| 160 |
+
confidence = detections[0, 0, i, 2]
|
| 161 |
|
| 162 |
+
# Filtrar detecciones débiles por confianza
|
| 163 |
+
if confidence > conf_threshold:
|
| 164 |
+
detection_count += 1
|
| 165 |
+
# La red da las coordenadas de la caja normalizadas entre 0 y 1
|
| 166 |
+
# Multiplicamos por ancho y alto para obtener coordenadas en píxeles
|
| 167 |
+
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
|
| 168 |
+
|
| 169 |
+
# Convertir a enteros
|
| 170 |
+
x1, y1, x2, y2 = box.astype("int")
|
| 171 |
+
|
| 172 |
+
# Garantizar que las coordenadas estén dentro de los límites de la imagen
|
| 173 |
+
x1, y1 = max(0, x1), max(0, y1)
|
| 174 |
+
x2, y2 = min(w, x2), min(h, y2)
|
| 175 |
+
|
| 176 |
+
# Imprimir información de depuración
|
| 177 |
+
print(f"Detección #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")
|
| 178 |
+
|
| 179 |
+
# Saltar cajas inválidas (por ejemplo, con ancho o alto negativo)
|
| 180 |
+
width, height = x2 - x1, y2 - y1
|
| 181 |
+
if width <= 0 or height <= 0:
|
| 182 |
+
print(f"Saltando caja inválida con dimensiones: {width}x{height}")
|
| 183 |
+
continue
|
| 184 |
+
|
| 185 |
+
# Añadir la caja y la confianza a la lista de resultados
|
| 186 |
+
bboxes.append([x1, y1, x2, y2, confidence])
|
| 187 |
+
|
| 188 |
+
# Dar feedback sobre el número de detecciones
|
| 189 |
+
print(f"Total de detecciones con confianza > {conf_threshold}: {detection_count}")
|
| 190 |
+
print(f"Total de cajas válidas: {len(bboxes)}")
|
| 191 |
+
|
| 192 |
+
# Devolver None si no hay detecciones, o la lista de bboxes en caso contrario
|
| 193 |
+
return bboxes if bboxes else None
|
| 194 |
|
| 195 |
# Function for processing face detections
|
| 196 |
def process_face_detections(frame, detections, conf_threshold=0.5, bbox_color=(0, 255, 0)):
|
|
|
|
| 2466 |
<div style="margin-bottom: 20px;">
|
| 2467 |
<video id="webcam" autoplay playsinline width="640" height="480" style="border-radius: 5px; display: none;"></video>
|
| 2468 |
<canvas id="canvas" width="640" height="480" style="display: none;"></canvas>
|
| 2469 |
+
<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>
|
| 2470 |
</div>
|
| 2471 |
<script>
|
| 2472 |
const video = document.getElementById('webcam');
|
|
|
|
| 2479 |
// Para depuración
|
| 2480 |
console.log('Componente de cámara inicializado');
|
| 2481 |
|
| 2482 |
+
// Dibujar un rectángulo de prueba inmediatamente
|
| 2483 |
+
displayCtx.fillStyle = 'rgba(255, 255, 255, 0.5)';
|
| 2484 |
+
displayCtx.fillRect(0, 0, display.width, display.height);
|
| 2485 |
+
displayCtx.strokeStyle = '#FF0000';
|
| 2486 |
+
displayCtx.lineWidth = 8;
|
| 2487 |
+
displayCtx.strokeRect(200, 100, 240, 280);
|
| 2488 |
+
displayCtx.fillStyle = '#FF0000';
|
| 2489 |
+
displayCtx.font = '28px Arial';
|
| 2490 |
+
displayCtx.fillText('Rectángulo de Prueba', 210, 90);
|
| 2491 |
+
console.log('Rectángulo inicial de prueba dibujado');
|
| 2492 |
+
|
| 2493 |
// Configuración dinámica del FPS (desde Streamlit)
|
| 2494 |
const captureDelay = 1000 / %s;
|
| 2495 |
|
|
|
|
| 2528 |
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 2529 |
displayCtx.drawImage(video, 0, 0, display.width, display.height);
|
| 2530 |
|
| 2531 |
+
// Dibujar un rectángulo de prueba en cada captura
|
| 2532 |
+
drawTestRect();
|
| 2533 |
+
|
| 2534 |
// Convertir a base64
|
| 2535 |
const imageData = canvas.toDataURL('image/jpeg', 0.8);
|
| 2536 |
|
|
|
|
| 2568 |
const [x1, y1, x2, y2, confidence] = box;
|
| 2569 |
console.log(`Dibujando caja: (${x1}, ${y1}, ${x2}, ${y2}, ${confidence})`);
|
| 2570 |
|
| 2571 |
+
// Dibujar rectángulo con colores más brillantes y líneas más gruesas
|
| 2572 |
+
displayCtx.strokeStyle = '#00FF00'; // Verde brillante
|
| 2573 |
+
displayCtx.lineWidth = 6; // Línea más gruesa
|
| 2574 |
displayCtx.strokeRect(x1, y1, x2-x1, y2-y1);
|
| 2575 |
|
| 2576 |
+
// Añadir un relleno semitransparente para mayor visibilidad
|
| 2577 |
+
displayCtx.fillStyle = 'rgba(0, 255, 0, 0.2)';
|
| 2578 |
+
displayCtx.fillRect(x1, y1, x2-x1, y2-y1);
|
| 2579 |
+
|
| 2580 |
+
// Añadir un fondo para el texto
|
| 2581 |
+
displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 2582 |
+
displayCtx.fillRect(x1, y1-25, 140, 25);
|
| 2583 |
+
|
| 2584 |
+
// Dibujar etiqueta con fuente más grande
|
| 2585 |
+
displayCtx.fillStyle = '#FFFF00'; // Amarillo brillante
|
| 2586 |
+
displayCtx.font = 'bold 18px Arial';
|
| 2587 |
+
displayCtx.fillText(`Rostro: ${confidence.toFixed(2)}`, x1+5, y1-5);
|
| 2588 |
} else {
|
| 2589 |
console.warn('Formato de caja inválido:', box);
|
| 2590 |
}
|
| 2591 |
});
|
| 2592 |
+
|
| 2593 |
+
// Añadir indicador de cantidad de rostros detectados
|
| 2594 |
+
displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 2595 |
+
displayCtx.fillRect(10, 10, 200, 30);
|
| 2596 |
+
displayCtx.fillStyle = '#FFFFFF';
|
| 2597 |
+
displayCtx.font = 'bold 18px Arial';
|
| 2598 |
+
displayCtx.fillText(`Rostros: ${boxes.length}`, 20, 30);
|
| 2599 |
} else {
|
| 2600 |
+
// Mensaje cuando no se detectan rostros
|
| 2601 |
+
displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 2602 |
+
displayCtx.fillRect(10, 10, 250, 30);
|
| 2603 |
+
displayCtx.fillStyle = '#FF9900';
|
| 2604 |
+
displayCtx.font = 'bold 18px Arial';
|
| 2605 |
+
displayCtx.fillText('No se detectan rostros', 20, 30);
|
| 2606 |
console.log('No hay cajas para dibujar o formato inválido');
|
| 2607 |
}
|
| 2608 |
+
|
| 2609 |
+
// Dibuja un rectángulo de prueba siempre, para verificar que el canvas funciona
|
| 2610 |
+
drawTestRect();
|
| 2611 |
}
|
| 2612 |
} catch (error) {
|
| 2613 |
console.error('Error al procesar mensaje:', error);
|
|
|
|
| 2616 |
|
| 2617 |
// Dibujar un rectángulo de prueba para verificar que el canvas funciona
|
| 2618 |
function drawTestRect() {
|
| 2619 |
+
displayCtx.strokeStyle = '#FF0000'; // Rojo brillante
|
| 2620 |
+
displayCtx.lineWidth = 5;
|
| 2621 |
displayCtx.strokeRect(50, 50, 100, 100);
|
| 2622 |
+
displayCtx.fillStyle = 'rgba(255, 0, 0, 0.3)'; // Relleno semitransparente
|
| 2623 |
+
displayCtx.fillRect(50, 50, 100, 100);
|
| 2624 |
displayCtx.fillStyle = '#FF0000';
|
| 2625 |
+
displayCtx.font = 'bold 16px Arial';
|
| 2626 |
+
displayCtx.fillText('TEST', 85, 100);
|
| 2627 |
console.log('Rectángulo de prueba dibujado');
|
| 2628 |
}
|
| 2629 |
|
|
|
|
| 2699 |
|
| 2700 |
# Crear script para enviar datos al componente JavaScript
|
| 2701 |
face_boxes_js = f"""
|
| 2702 |
+
<script>
|
| 2703 |
+
console.log("Enviando cajas de rostros al componente: {bbox_list}");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2704 |
|
| 2705 |
+
// Usar setTimeout para asegurarse de que el componente está inicializado
|
| 2706 |
+
setTimeout(function() {{
|
| 2707 |
+
window.parent.postMessage(
|
| 2708 |
+
{{
|
| 2709 |
+
type: 'faceBoxes',
|
| 2710 |
+
boxes: {json.dumps(bbox_list)}
|
| 2711 |
+
}},
|
| 2712 |
+
'*'
|
| 2713 |
+
);
|
| 2714 |
+
|
| 2715 |
+
// También enviar un evento directo al iframe si existe
|
| 2716 |
+
try {{
|
| 2717 |
+
const frames = window.parent.document.getElementsByTagName('iframe');
|
| 2718 |
+
for(let i = 0; i < frames.length; i++) {{
|
| 2719 |
+
frames[i].contentWindow.postMessage(
|
| 2720 |
+
{{
|
| 2721 |
+
type: 'faceBoxes',
|
| 2722 |
+
boxes: {json.dumps(bbox_list)}
|
| 2723 |
+
}},
|
| 2724 |
+
'*'
|
| 2725 |
+
);
|
| 2726 |
+
}}
|
| 2727 |
+
console.log("Mensaje enviado a todos los iframes");
|
| 2728 |
+
}} catch(e) {{
|
| 2729 |
+
console.error("Error enviando mensaje directo a iframes:", e);
|
| 2730 |
+
}}
|
| 2731 |
+
}}, 200);
|
| 2732 |
+
</script>
|
| 2733 |
"""
|
| 2734 |
+
|
| 2735 |
+
# Inyectar el script en la página
|
| 2736 |
st.components.v1.html(face_boxes_js, height=0, width=0)
|
| 2737 |
+
|
| 2738 |
+
# Agregar un script para dibujar un rectángulo de prueba,
|
| 2739 |
+
# independientemente de si hay detecciones o no
|
| 2740 |
+
test_rect_js = """
|
| 2741 |
+
<script>
|
| 2742 |
+
console.log("Enviando señal de rectángulo de prueba");
|
| 2743 |
+
setTimeout(function() {
|
| 2744 |
+
window.parent.postMessage(
|
| 2745 |
+
{
|
| 2746 |
+
type: 'testRect'
|
| 2747 |
+
},
|
| 2748 |
+
'*'
|
| 2749 |
+
);
|
| 2750 |
+
}, 100);
|
| 2751 |
+
</script>
|
| 2752 |
+
"""
|
| 2753 |
+
st.components.v1.html(test_rect_js, height=0, width=0)
|
| 2754 |
except Exception as e:
|
| 2755 |
st.error(f"Error processing camera frame: {str(e)}")
|
| 2756 |
st.info("Camera continues to run. Processing will be attempted on next frame.")
|