👷 SafeSite Agent

Monitoreo de EPP en obra — demo interactiva: percibe, confirma en el tiempo (K=3) y arma la alerta de WhatsApp

Nota del deploy: el proyecto corre originalmente en un servidor Python/Gradio con PyTorch (deploy/app.py en el repositorio). Esta página pública usa el mismo modelo exportado a ONNX ejecutándose en tu navegador, porque el hospedaje con servidor dejó de tener capa gratuita. Detalles en la pestaña «Nota del deploy».

Sube una imagen, usa tu cámara o trae una foto del set de test. Cada imagen se procesa como un video corto de cuatro fotogramas: la falta solo se confirma si aparece en tres seguidos. El mensaje al supervisor se muestra; nunca se envía desde aquí.

Cargando el modelo en tu navegador (una sola vez, ~45 MB)…

Rojo = incumplimiento · verde = EPP presente · celeste = persona. Las cajas de gafas se re-anclan a nivel de ojos (corrección de presentación E30).

La decisión del agente, frame por frame

Lo que detectó (umbrales aplicados)

ClaseConfianzaTipo

Mensaje al supervisor

Aún no hay nada procesado.

Las cámaras fallan: dejan de entregar datos, mandan basura o un frame sin contenido. Lo correcto es registrar la falla y seguir en pie, sin caerse y sin inventar alertas. El frame negro no es una falla: es una imagen válida sin contenido, de la que el agente no saca nada.

Elige un tipo de falla y simúlala para ver qué recibió el agente y qué decidió con cada frame.

Las medidas M1–M4 son los compromisos numéricos que el proyecto se fijó antes de construir el agente (la M es de «medida»). Fijarlas antes es lo que hace honesta la evaluación: la meta no se movió después de ver los resultados.

MedidaQué mideCómo se calculaObjetivo
M1 Recall de incidentede las escenas que SÍ tienen una falta, en cuántas el agente alertaescenas con alerta / escenas con falta≥ 0.70
M2 Falsas alarmasde las escenas SIN falta, en cuántas el agente alerta igualescenas control alertadas / escenas control≤ 0.10
M3 Tiempo de notificacióncuánto tarda el aviso en llegar al supervisorconfirmación (3 frames) + procesamiento + envío a WhatsApp< 10 s
M4 Cooldown anti-spamque no repita la misma alerta dentro de la ventana de 30 salertas duplicadas en la ventana0

Cómo se corrieron y dónde están sus resultados

La suite que las mide (13 escenarios S1–S13 y las 283 escenas de val/test) se ejecuta en el servidor Python del proyecto (src/safesite/pruebas_medidas.py, misma lógica que esta página). En esta versión de navegador no corren en vivo — procesar 283 escenas × 4 frames tardaría horas en el navegador — así que aquí se muestran los resultados oficiales de la corrida real (GPU local, 5 repeticiones, 14 de octubre de 2026):

MedidaFórmulaUmbralResultado¿Cumple?

«± 0.0» no es sospechoso: en un mismo equipo la corrida es determinista y las 5 repeticiones dan exactamente igual. M4 se comprueba con S6 y S7: la reincidencia dentro de la ventana de cooldown produce 1 sola alerta; pasada la ventana, 2.

Corrida oficial de la suite, hecha en el equipo local con GPU y 5 repeticiones (14 de octubre de 2026). Esta es la evidencia que vale para la entrega.

Cómo leer las dos tablas

La primera es el veredicto: cada fila es una medida (M1–M4) en su split; «Fórmula» recuerda cómo se calcula, «Umbral» es el objetivo pre-registrado y «¿Cumple?» la compara contra el resultado. La segunda es el detalle escenario por escenario (S1–S13): «normal» es el caso típico, «límite» empuja los bordes del diseño y «falla» mete entradas rotas donde lo correcto es degradarse sin caerse. «Alertas (media±σ)» es el promedio de las repeticiones; «None» en t_alerta significa que ese escenario no debía alertar y no alertó.

De dónde salen los datos del agente, qué se verificó antes de construirlo y con qué números quedó.

1. El dataset y los supuestos que se validaron

1,416 imágenes de obra (Construction PPE de Ultralytics) con 11 clases: persona, 5 equipos de protección, 4 faltas no_* y una de ruido. Antes de construir el agente, cada supuesto se verificó midiendo; varios resultaron falsos, y esas correcciones definieron al agente que estás usando.

SupuestoQué se encontró al medirloQué se decidió
Las etiquetas YOLO están bien escritasConfirmado: las 1,129 etiquetas de train se verificaron una por una contra el NDJSON oficial, sin una sola discrepanciaNo había nada que re-etiquetar
Los splits de train/val/test estaban bien repartidosFalso: 112 imágenes del test oficial estaban metidas en el train, y no existía set de validaciónSe rehízo el split completo (dataset_v2) y se creó el set de validación que faltaba
Hay suficientes ejemplos de faltas para aprenderFalso: solo el 20.3% de las imágenes de train muestra alguna falta no_*, y no_boots apenas apareceUmbrales bajos (0.15) y confirmación temporal K=3; el proyecto se mide por recall por clase, no por exactitud global
«Sin casco» se puede deducir buscando el casco dentro de la caja de la personaFalso: esa regla falla el 38.2% de las vecesLas decisiones usan únicamente las clases no_* explícitas del detector
Las cajas de gafas están a la altura de los ojosFalso: la mediana queda al 14.5% de la altura de la persona, cuando los ojos reales van por el ~8%Re-anclaje visual a nivel de ojos (solo presentación: no toca detecciones ni métricas)
Todas las imágenes son parecidasNo tanto: el 21.2% no es de 640×640 y el 4.5% de la muestra es muy oscuraLa inferencia se mantiene en 640 o más; el aumento nocturno queda como recomendación para cámaras 24/7
2. Límites de la validación: lo que quedó sin comprobar

Un análisis honesto también dice qué es lo que NO puede certificar. Tres cosas quedaron fuera y así quedó escrito en el informe del EDA:

  1. La calidad de las etiquetas. Se comprobó que las 1,129 etiquetas de train coinciden al 100% con los metadatos oficiales y respetan el formato YOLO; lo que no se puede comprobar es si quien las dibujó a ojo acertó (si esa caja es realmente un casco).
  2. Fotos casi duplicadas. Dos copias de la misma escena en splits distintos inflarían la generalización. No se midió; queda como trabajo futuro.
  3. El dominio. Nadie comprobó que toda foto viniera de una obra de construcción.

El tercero falló en la práctica, y la evidencia es la foto del fútbol: en la imagen de un partido, el detector marcó botas sobre los tenis y dibujó una gafa por encima de los ojos. Al barrer las 1,413 imágenes se encontraron 9 pares de botas repetidas y 2 gafas fuera de lugar. La corrección fue solo visual (no repetir cajas solapadas; el re-anclaje de gafas ya no sube tanto): las alertas y las métricas no cambiaron — se volvió a correr la suite para comprobarlo. La lección quedó escrita: el agente es para obras; otros contextos exigen otros datos.

3. Métricas del detector (nivel caja)

Precisión, recall y F1 por clase del detector (IoU 0.5), en el punto donde cada clase alcanza su mejor F1. ¿Y la accuracy del detector? En detección no se usa: una imagen tiene muchos objetos a la vez y el fondo acierta solo, así que un accuracy global saldría inflado y no diría nada. La exactitud del agente sí se puede medir bien, por escena: está en la sección 4.

Por qué no_boots es lo más difícil de detectar (y lo sería con cualquier modelo entrenado con este dataset). No es un bug del modelo: es la señal que hubo para aprender. no_boots tiene 115 cajas de las 11,521 del dataset (1%) — 19.5 veces menos que la clase más común (Person) — y solo 88 de las 1,130 imágenes de train muestran a alguien sin botas. Encima, la geometría juega en contra: en planos abiertos las botas son de los objetos más pequeños de la foto (la caja mediana ocupa el 1.85% de la imagen, figura 4 de la sección 1) y suelen quedar en el borde inferior, medio fuera de cuadro. Para la evaluación el soporte es tan pequeño (2 escenas en val, 6 en test) que un solo acierto o fallo salta la métrica en pasos enormes: el número casi no se puede fiar. Ni el oversampling ×3 probado en el proyecto la arregló (hallazgo negativo documentado en la bitácora). Por eso queda declarada como la clase más débil en vez de maquillada: el agente se mide por escena, y allí el sistema sí cumple (sección 4).

4. Métricas del agente (nivel escena — las que valen)

El agente acierta o falla por escena: si había falta y alertó, acierto (TP); había falta y no alertó, se le escapó (FN); no había falta y alertó, falsa alarma (FP). Con los umbrales reales de operación quedó así:

Las medidas pre-registradas de la entrega (las mismas M1–M4 de la pestaña de pruebas; el umbral es el objetivo fijado antes de construir el agente — corrida oficial, 5 repeticiones):

La idea central del proyecto está en el contraste con la sección 3: el detector falla mucho caja por caja en las faltas (no_boots y no_goggle tienen F1 casi nulo), pero el agente las confirma viéndolas 3 frames seguidos y llega a un F1 de escena de 0.92. La confirmación temporal compensa la debilidad del detector.

Transparencia sobre cómo está hecha esta página y por qué difiere de la que describe el repositorio.

Las dos versiones de la demo

Versión original (el repositorio)Esta página pública
TecnologíaPython · Gradio · PyTorch (ultralytics)HTML/JS estático · ONNX Runtime Web
¿Dónde corre el modelo?En el servidor (CPU con GPU local para la sesión en vivo)En el navegador de cada visitante (WebGPU si está disponible, WASM si no)
Modelotuned_v2_s/weights/best.ptEl MISMO modelo exportado a ONNX (best.onnx)
Lógica del agentesrc/safesite/ (percepción → razonamiento K=3 → cooldown)Reimplementación fiel en JS de la misma lógica, con los mismos parámetros
Pruebas M1–M4 en vivoSí — la suite corre en el navegador sobre el servidorNo (horas en un navegador); se muestran los resultados oficiales

Por qué el cambio (solo en el deploy)

El proyecto no cambió: entrenamiento, suite, métricas y código del agente siguen siendo los del repositorio. Lo que pasó es el hospedaje: esta demo necesita ~700 MB de RAM en servidor, y las capas gratuitas con servidor desaparecieron durante el desarrollo (Hugging Face pasó su tier CPU a pago; Claw Cloud empezó a cobrar; Render/Koyek tienen 512 MB, donde el modelo muere por memoria). Los Static Spaces — solo archivos, sin servidor — siguen siendo gratis para siempre, así que la demo pública se reescribió para que el modelo corra del lado del visitante.

Equivalencia verificada (no es una versión «aproximada»)

Antes de publicar se exportó el modelo de despliegue a ONNX y se comparó contra el original en fotos de test: mismas detecciones, mismas clases, diferencia máxima de confianza de 0.003–0.018. Los umbrales por clase, la confirmación K=3, el cooldown de 30 s y el re-anclaje visual de gafas se replicaron con los valores exactos de src/safesite/config.py. Las métricas y tablas de esta página son las de la corrida oficial del proyecto, no recalculadas aquí.

El código de ambas versiones está en el repositorio: la original en deploy/app.py (Gradio) y esta en deploy/estatica/. La bitácora del proyecto (Documentation/BITACORA.md, entradas E48–E52) registra la cadena de decisiones, incluidas las plataformas descartadas y sus motivos.

Cómo probar el agente (10 a 15 minutos)

  1. Una foto con falta (el caso típico). Trae una foto del set de test hasta que salga una con faltas (la página te dice cuáles, según la etiqueta oficial). Procesa y fíjate en las cajas rojas, en la línea de tiempo (la falta se confirma a la tercera vez que se ve) y en el mensaje que le llegaría al supervisor.
  2. Una foto sin faltas (el caso control). Trae más fotos hasta que salga una de control: el agente debería quedarse callado. Las pocas veces que alerte en una escena así son las falsas alarmas que mide M2.
  3. El caso de falla del sensor. En la pestaña «Fallas del sensor»: el agente registra el frame fallido y sigue trabajando. Con el frame negro no hay falla — es una imagen válida sin contenido.
  4. Con tu propia cámara. Activa la webcam: si apareces con la cabeza descubierta en primer plano, lo más probable es que te marque como «SIN CASCO». Aquí la confirmación es real: 3 capturas seguidas (K=3) y cooldown de 30 s.
  5. Las pruebas de desempeño. Lee qué mide cada M1–M4 en su pestaña y compáralas contra los resultados oficiales.

Qué hace cada parte

Código, entrenamiento y bitácora del proyecto: github.com/WilliamLzn/SAFESITE-AGENT