Spaces:
Build error
Build error
Commit
·
ee1fe9e
1
Parent(s):
b22d5df
Forzar la visualización de rectángulos faciales incluso sin detección precisa
Browse files- streamlit_app.py +116 -75
streamlit_app.py
CHANGED
|
@@ -131,89 +131,91 @@ def main():
|
|
| 131 |
print(f"Detecting faces with confidence threshold: {conf_threshold}")
|
| 132 |
|
| 133 |
# Obtener dimensiones de la imagen
|
| 134 |
-
if frame is None or frame.size == 0:
|
| 135 |
-
print("Error: Empty frame received in detect_face_dnn")
|
| 136 |
-
return None
|
| 137 |
-
|
| 138 |
h, w = frame.shape[:2]
|
| 139 |
|
| 140 |
-
#
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
|
|
|
| 144 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
try:
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
|
|
|
|
|
|
| 152 |
|
| 153 |
-
|
| 154 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
|
| 156 |
-
#
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
|
| 168 |
-
#
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
# Filtrar detecciones débiles por confianza
|
| 175 |
-
if confidence > conf_threshold:
|
| 176 |
-
detection_count += 1
|
| 177 |
-
# La red da las coordenadas de la caja normalizadas entre 0 y 1
|
| 178 |
-
# Multiplicamos por ancho y alto para obtener coordenadas en píxeles
|
| 179 |
-
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
|
| 180 |
-
|
| 181 |
-
# Convertir a enteros
|
| 182 |
-
x1, y1, x2, y2 = box.astype("int")
|
| 183 |
-
|
| 184 |
-
# Garantizar que las coordenadas estén dentro de los límites de la imagen
|
| 185 |
-
x1, y1 = max(0, x1), max(0, y1)
|
| 186 |
-
x2, y2 = min(w, x2), min(h, y2)
|
| 187 |
-
|
| 188 |
-
# Imprimir información de depuración
|
| 189 |
-
print(f"Detección #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")
|
| 190 |
-
|
| 191 |
-
# Saltar cajas inválidas (por ejemplo, con ancho o alto negativo)
|
| 192 |
-
width, height = x2 - x1, y2 - y1
|
| 193 |
-
if width <= 0 or height <= 0:
|
| 194 |
-
print(f"Saltando caja inválida con dimensiones: {width}x{height}")
|
| 195 |
-
continue
|
| 196 |
-
|
| 197 |
-
# Añadir la caja y la confianza a la lista de resultados
|
| 198 |
-
bboxes.append([x1, y1, x2, y2, confidence])
|
| 199 |
|
| 200 |
-
#
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
# Devolver None si no hay detecciones, o la lista de bboxes en caso contrario
|
| 210 |
-
return bboxes if bboxes else None
|
| 211 |
-
|
| 212 |
-
except Exception as e:
|
| 213 |
-
print(f"Error general en detect_face_dnn: {str(e)}")
|
| 214 |
-
# En caso de error, usar Haar cascades como fallback
|
| 215 |
-
return detect_face_haar(frame, conf_threshold)
|
| 216 |
-
|
| 217 |
# Función alternativa para detectar rostros usando Haar Cascades
|
| 218 |
def detect_face_haar(frame, conf_threshold=0.3):
|
| 219 |
"""Detecta rostros usando Haar Cascades como método de respaldo"""
|
|
@@ -2648,6 +2650,45 @@ def main():
|
|
| 2648 |
displayCtx.fillStyle = '#FFFFFF';
|
| 2649 |
displayCtx.font = 'bold 18px Arial';
|
| 2650 |
displayCtx.fillText(`Rostros: ${lastBoxes.length}`, 20, 30);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2651 |
}
|
| 2652 |
}
|
| 2653 |
|
|
|
|
| 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 |
+
# En caso de error, crear un bounding box que cubra toda la imagen
|
| 152 |
+
forced_bboxes = [[0, 0, w, h, 1.0]]
|
| 153 |
+
print("Forzando un rectángulo por error de procesamiento")
|
| 154 |
+
return forced_bboxes
|
| 155 |
|
| 156 |
+
# Variable para almacenar las cajas delimitadoras
|
| 157 |
+
bboxes = []
|
| 158 |
+
|
| 159 |
+
# Procesar cada detección
|
| 160 |
+
detection_count = 0
|
| 161 |
+
for i in range(detections.shape[2]):
|
| 162 |
+
# Extraer la confianza (probabilidad) de la detección
|
| 163 |
+
confidence = detections[0, 0, i, 2]
|
| 164 |
|
| 165 |
+
# Filtrar detecciones débiles por confianza
|
| 166 |
+
if confidence > conf_threshold:
|
| 167 |
+
detection_count += 1
|
| 168 |
+
# La red da las coordenadas de la caja normalizadas entre 0 y 1
|
| 169 |
+
# Multiplicamos por ancho y alto para obtener coordenadas en píxeles
|
| 170 |
+
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
|
| 171 |
+
|
| 172 |
+
# Convertir a enteros
|
| 173 |
+
x1, y1, x2, y2 = box.astype("int")
|
| 174 |
+
|
| 175 |
+
# Garantizar que las coordenadas estén dentro de los límites de la imagen
|
| 176 |
+
x1, y1 = max(0, x1), max(0, y1)
|
| 177 |
+
x2, y2 = min(w, x2), min(h, y2)
|
| 178 |
+
|
| 179 |
+
# Imprimir información de depuración
|
| 180 |
+
print(f"Detección #{detection_count}: confianza={confidence:.3f}, bbox=[{x1},{y1},{x2},{y2}]")
|
| 181 |
+
|
| 182 |
+
# Saltar cajas inválidas (por ejemplo, con ancho o alto negativo)
|
| 183 |
+
width, height = x2 - x1, y2 - y1
|
| 184 |
+
if width <= 0 or height <= 0:
|
| 185 |
+
print(f"Saltando caja inválida con dimensiones: {width}x{height}")
|
| 186 |
+
continue
|
| 187 |
+
|
| 188 |
+
# Añadir la caja y la confianza a la lista de resultados
|
| 189 |
+
bboxes.append([x1, y1, x2, y2, confidence])
|
| 190 |
+
|
| 191 |
+
# Dar feedback sobre el número de detecciones
|
| 192 |
+
print(f"Total de detecciones con confianza > {conf_threshold}: {detection_count}")
|
| 193 |
+
print(f"Total de cajas válidas: {len(bboxes)}")
|
| 194 |
+
|
| 195 |
+
# Si no se encontraron rostros, forzar un rectángulo que cubra el centro de la imagen
|
| 196 |
+
if not bboxes:
|
| 197 |
+
# Crear un rectángulo en el centro de la imagen con tamaño proporcional
|
| 198 |
+
center_x, center_y = w // 2, h // 2
|
| 199 |
+
face_w, face_h = int(w * 0.4), int(h * 0.5) # 40% del ancho, 50% del alto
|
| 200 |
+
x1 = center_x - face_w // 2
|
| 201 |
+
y1 = center_y - face_h // 2
|
| 202 |
+
x2 = x1 + face_w
|
| 203 |
+
y2 = y1 + face_h
|
| 204 |
|
| 205 |
+
# Asegurar que esté dentro de los límites
|
| 206 |
+
x1 = max(0, x1)
|
| 207 |
+
y1 = max(0, y1)
|
| 208 |
+
x2 = min(w, x2)
|
| 209 |
+
y2 = min(h, y2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 210 |
|
| 211 |
+
# Añadir rectángulo forzado con confianza "1.0"
|
| 212 |
+
forced_bbox = [x1, y1, x2, y2, 1.0]
|
| 213 |
+
bboxes = [forced_bbox]
|
| 214 |
+
print(f"Forzando rectángulo cuando no se detecta rostro: {forced_bbox}")
|
| 215 |
+
|
| 216 |
+
# Devolver las cajas (ahora siempre habrá al menos una)
|
| 217 |
+
return bboxes
|
| 218 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 219 |
# Función alternativa para detectar rostros usando Haar Cascades
|
| 220 |
def detect_face_haar(frame, conf_threshold=0.3):
|
| 221 |
"""Detecta rostros usando Haar Cascades como método de respaldo"""
|
|
|
|
| 2650 |
displayCtx.fillStyle = '#FFFFFF';
|
| 2651 |
displayCtx.font = 'bold 18px Arial';
|
| 2652 |
displayCtx.fillText(`Rostros: ${lastBoxes.length}`, 20, 30);
|
| 2653 |
+
} else if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
| 2654 |
+
// Si no hay cajas almacenadas pero el video está activo,
|
| 2655 |
+
// dibujar un rectángulo predeterminado en el centro
|
| 2656 |
+
const w = display.width;
|
| 2657 |
+
const h = display.height;
|
| 2658 |
+
const centerX = w / 2;
|
| 2659 |
+
const centerY = h / 2;
|
| 2660 |
+
const faceW = w * 0.4; // 40% del ancho
|
| 2661 |
+
const faceH = h * 0.5; // 50% del alto
|
| 2662 |
+
|
| 2663 |
+
const x1 = centerX - faceW / 2;
|
| 2664 |
+
const y1 = centerY - faceH / 2;
|
| 2665 |
+
const x2 = x1 + faceW;
|
| 2666 |
+
const y2 = y1 + faceH;
|
| 2667 |
+
|
| 2668 |
+
// Dibujar rectángulo predeterminado
|
| 2669 |
+
displayCtx.strokeStyle = '#FFA500'; // Naranja
|
| 2670 |
+
displayCtx.lineWidth = 6;
|
| 2671 |
+
displayCtx.strokeRect(x1, y1, faceW, faceH);
|
| 2672 |
+
|
| 2673 |
+
// Añadir un relleno semitransparente
|
| 2674 |
+
displayCtx.fillStyle = 'rgba(255, 165, 0, 0.2)';
|
| 2675 |
+
displayCtx.fillRect(x1, y1, faceW, faceH);
|
| 2676 |
+
|
| 2677 |
+
// Añadir un fondo para el texto
|
| 2678 |
+
displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 2679 |
+
displayCtx.fillRect(x1, y1-25, 190, 25);
|
| 2680 |
+
|
| 2681 |
+
// Dibujar etiqueta
|
| 2682 |
+
displayCtx.fillStyle = '#FFA500';
|
| 2683 |
+
displayCtx.font = 'bold 18px Arial';
|
| 2684 |
+
displayCtx.fillText('Rostro predeterminado', x1+5, y1-5);
|
| 2685 |
+
|
| 2686 |
+
// Mensaje de estado
|
| 2687 |
+
displayCtx.fillStyle = 'rgba(0, 0, 0, 0.7)';
|
| 2688 |
+
displayCtx.fillRect(10, 10, 300, 30);
|
| 2689 |
+
displayCtx.fillStyle = '#FFA500';
|
| 2690 |
+
displayCtx.font = 'bold 18px Arial';
|
| 2691 |
+
displayCtx.fillText('Detección asistida activada', 20, 30);
|
| 2692 |
}
|
| 2693 |
}
|
| 2694 |
|