Kanan AI — Arquitectura (iteración 2, orientada a módulos)
Documento vivo. Esta iteración reorganiza la arquitectura alrededor de los seis módulos de seguridad del producto. Principio rector: los seis módulos no son seis pipelines. Comparten una capa de percepción (detección + pose) y cada módulo es, en su mayor parte, una configuración del motor de reglas sobre detecciones comunes. Esto reduce la superficie de mantenimiento, condición crítica para un equipo de CV reducido.
Los seis módulos del producto
- Zonas restringidas (Zone & Access Control) — zonas de exclusión sobre el CCTV; alerta cuando persona o vehículo entra a área prohibida.
- Detección de EPP (PPE Compliance) — casco, chaleco, guantes, calzado; alerta si un operario entra a zona de riesgo sin el equipo correcto.
- Ergonomía (Ergonomic Risk Detection) — análisis de postura; levantamientos incorrectos, sobreesfuerzo, posturas prolongadas.
- Control vehicular (Forklift & Vehicle Safety) — montacargas y vehículos, su interacción con peatones; velocidad, zonas prohibidas, puntos ciegos.
- Conducta segura (Behavioral Safety) — correr en planta, uso de celular en zona operativa, omisión de pasamanos.
- Analytics y reportes (Operational Impact Analysis) — dashboard de incidentes, reportes automáticos, patrones recurrentes y tendencias.
Flujo general
┌─────────────┐ ┌──────────────────────────────────────┐ ┌─────────────────────┐
│ Cámaras │ │ CAPA DE CV (Python) │ │ STACK MERN │
│ (archivo │───▶│ ingesta → PERCEPCIÓN COMPARTIDA → │───▶│ API Node → MongoDB │
│ inicial, │ │ reglas por módulo → eventos limpios │ │ │ │
│ RTSP │ │ │ │ ▼ │
│ posterior) │ │ │ │ Dashboard React │
└─────────────┘ └──────────────────────────────────────┘ │ (Analytics = mód. 6)│
│ └─────────────────────┘
▼ (offline)
┌──────────────────────┐
│ Pipeline de datos │
│ (moat, separado) │
└──────────────────────┘
La capa de CV concluye al producir eventos limpios. El módulo 6 (Analytics) no es un módulo de CV: reside por completo en el stack MERN y consume los eventos que la capa de CV ya entregó.
Capa de percepción compartida
Dos backbones alimentan a casi todos los módulos. La elección de cada uno determina la exposición de licencia de todo el sistema.
| Componente | Tecnología | Licencia | Alternativa permisiva | Alimenta a los módulos |
|---|---|---|---|---|
| Detección de objetos | RF-DETR (Roboflow) — seleccionado | Apache 2.0 ✅ | YOLO26 (AGPL-3.0), mejor tooling/ecosistema | Zonas, EPP, Control vehicular, Conducta (celular, correr) |
| Estimación de pose | YOLO26-pose (Ultralytics) | AGPL-3.0 / Enterprise | MediaPipe (Apache 2.0) · RF-DETR keypoints (Apache 2.0, preview) | Ergonomía, Conducta (correr, pasamanos) |
| Tracking (diferido) | Modo tracking de Ultralytics | AGPL-3.0 / Enterprise | ByteTrack / BoT-SORT (permisivos), desacoplables del detector | Control vehicular, EPP por persona, posturas prolongadas, reincidencia |
Si la prioridad es evitar licencias, la ruta permisiva completa es RF-DETR (detección) + MediaPipe o RF-DETR-keypoints (pose) + ByteTrack (tracking) — todo Apache/MIT, a cambio de fragmentar el pipeline en varias familias. La decisión es legal y de mantenimiento, no puramente técnica, y requiere revisión interna según el modo de despliegue (SaaS vs. on-premise).
Decisión de detección (validada empíricamente en el módulo 1). Se selecciona RF-DETR como backbone de detección. En pruebas sobre footage CCTV de almacén, RF-DETR-Medium detectó más personas que YOLO26-m y que MediaPipe/EfficientDet-Lite2 (9 vs 8 vs 5 en el frame de referencia), con cajas más consistentes en figuras pequeñas/lejanas —donde MediaPipe parpadeaba, produciendo falsos negativos inaceptables para una alerta de seguridad. Rendimiento medido: sobre una RTX 3070 Ti con FP16, ~57 FPS (17 ms/frame) —tiempo real holgado—; en CPU ~4.5 FPS, suficiente para procesamiento por jobs. Al ser Apache 2.0, elimina la exposición AGPL en la capa de detección (ver Pendientes). Nota de implementación: RF-DETR usa la indexación COCO de 91 clases (
person= clase 1). Cifras sujetas a revalidación con footage propio y por módulo.
Mapa módulo → tecnología
| Módulo | Técnica principal | Percepción que usa | ¿Tracking? | ¿Entrenamiento custom? | Tecnología (stack) | Capa | Validado |
|---|---|---|---|---|---|---|---|
| 1. Zonas restringidas | Point-in-polygon sobre detecciones | Detección | No (intrusión simple) | No | RF-DETR + point-in-polygon (OpenCV) | CV | ✅ |
| 2. Detección de EPP | Detección de clases PPE + asociación a persona | Detección | Recomendable (PPE sostenido por persona) | Sí (casco, chaleco, guantes, calzado) | RF-DETR (fine-tune PPE) + tracking † | CV | 🟡 (EPP 13 clases: detección + ausencia ✅, split sin fuga; falta asociación a persona) |
| 3. Ergonomía | Keypoints + cálculo de ángulos + scoring OWAS/RULA/REBA | Pose | Para "posturas prolongadas" (duración) | No para pose; sí para calibrar scoring | Pose † + scoring OWAS/RULA/REBA | CV | 🟡 (posible; requiere entrenamiento custom) |
| 4. Control vehicular | Detección de vehículos + tracking + estimación de velocidad + reglas espaciales | Detección + tracking | Sí (velocidad, interacción peatón) | Sí (montacargas, vehículos) | RF-DETR (fine-tune vehículos) + tracking † + calibración de cámara | CV | 🟡 (montacargas: detección ✅; proximidad y velocidad = PoC sin calibrar) |
| 5. Conducta segura | Mixto por sub-caso (ver notas) | Detección + pose + tracking | Sí (correr = velocidad) | Sí (conducta, no objeto — ver ruta B) | RF-DETR (fine-tune conducta) + pose † + tracking † | CV | 🟡 (correr y celular: PoC zero-shot ✅; sin entrenar) |
| 6. Analytics y reportes | Agregación, heatmaps, reportes, tendencias | — (consume eventos) | — | — | React + Node + MongoDB | MERN | — |
Leyenda. ✅ = validado empíricamente en prototipo · 🟡 = validado parcialmente o viable pero con trabajo pendiente —típicamente entrenamiento custom— (ver detalle en la celda) · — = pendiente. † = backbone aún por decidir (pose: MediaPipe / YOLO26-pose / RF-DETR-keypoints · tracking: ByteTrack / BoT-SORT / Ultralytics). La detección ya está fija en RF-DETR para todos los módulos que la usan.
Notas de dificultad y decisiones honestas por módulo
1. Zonas restringidas — el punto de arranque. El más simple y stack-agnóstico: una vez que hay detección de persona/vehículo, la regla es geometría pura (point-in-polygon sobre el punto de "pies" de cada caja — centro-inferior, el punto que pisa el suelo). Funciona por frame, sin tracking. Es el primer módulo a construir; entrega una victoria temprana y valida toda la cadena cámara → detección → regla → evento. Detección: RF-DETR (ver decisión en Capa de percepción compartida). Setup manual de polígonos por cámara (definidos en el frontend; ver Orquestación CV ↔ MERN). Prototipo funcional validado con este backbone.
2. Detección de EPP — el primer módulo que exige dataset propio. Las clases (casco, chaleco, guantes, calzado) requieren entrenamiento custom; aquí empieza de verdad el moat de datos anotados. "Sin EPP en zona de riesgo" combina este módulo con el de zonas. La asociación EPP-a-persona se beneficia de tracking para evitar falsos positivos por frame; sin tracking, la regla es más ruidosa. Guantes y calzado son objetos pequeños y de difícil visibilidad en CCTV picado: esperar degradación, validar con footage.
Hallazgo empírico — guantes rompen los landmarks de mano genéricos. Se probó el HandLandmarker de MediaPipe (21 keypoints por mano, el mejor modelo pragmático para manos desnudas) sobre footage de manos en línea de ensamblaje. Resultado: cero manos detectadas en todo el clip, incluso bajando el umbral a 0.05 — no por tamaño (las manos aparecían grandes y nítidas) sino porque el modelo se entrenó con piel desnuda y los guantes de trabajo lo dejan ciego. Confirma que la vía "landmarks de mano de propósito general" no aplica al contexto industrial: la detección de guante (puesto/no puesto) debe resolverse como detección de objeto con dataset propio de guantes, no con estimación de keypoints de mano. Es el mismo patrón que la clase
NO-Hardhatdel casco: los modelos genéricos fallan justo en la realidad industrial. MediaPipe hands queda validado solo para manos desnudas de cerca (útil para prototipos/UX, no para la planta).
Validación parcial — detector de guantes por fine-tuning (RF-DETR). Se hizo el primer fine-tuning propio: RF-DETR-Medium reentrenado sobre un dataset COCO de guantes (~490 imágenes, split 70/20/10), en un entorno de entrenamiento separado (Python 3.12 +
rfdetr[train]; el de inferencia sigue en 3.14). Corrió en la RTX 3070 Ti conbatch=4+grad_accum=4(batch efectivo 16, sin desbordar los 8 GB), y cortó por early stopping en ~23 épocas. Resultado sobre el mismo video de manos enguantadas que dejó ciego a MediaPipe: el modelo sí detecta los guantes, con cajas correctas y confianza 0.86–0.91 —pese a la brecha de dominio (entrenado con guantes genéricos, no de esta planta)—. Es la tesis del producto demostrada de punta a punta: modelo genérico falla → dataset propio resuelve.⚠️ Alcance exacto de lo validado — leer con cuidado. Lo probado solo dibuja una caja donde HAY un guante (detección de presencia de guante). Esto NO es todavía el módulo de cumplimiento de EPP. Falta explícitamente: (a) detectar la ausencia de guante de forma confiable —la clase
No-Glovesquedó débil (solo 34 instancias de entrenamiento, 7 de validación; métrica estadísticamente frágil), y la ausencia es justo la condición que dispara la alerta de seguridad—; (b) la asociación guante↔persona (¿este operario trae guantes puestos?), que requiere detección de persona + tracking y pertenece a la fase de integración. En resumen: hoy sabemos ver el guante, no sabemos afirmar "esta persona incumple". Para cerrar el módulo se necesita más data real del sitio (sobre todo deNo-Gloves) y la lógica de asociación.
Validación — detector de EPP de 13 clases por fine-tuning (RF-DETR), el primero hecho sin fuga. Tercer fine-tuning propio (van guantes y montacargas), y el primero ejecutado bien de principio a fin. Dataset
EPP.coco(Roboflow, 13 clases: Casco, Cubreboca, Guante, Lentes, Ropa alta visibilidad, Orejera, Pantalla Facial, Red Cabello, Protector Auditivo, Persona… y la AUSENCIA de protección como clases propias: Mano Expuesta, Extremidad Expuesta, Oreja Expuesta). RF-DETR-Medium, entorno de entrenamiento separado (Python 3.12 +rfdetr[train]),batch=4+grad_accum=4(efectivo 16) sobre la RTX 3070 Ti, early stopping a 48/50 épocas; se usacheckpoint_best_total.pth(el ganador EMA). Demos:50_entrenamiento.py(fine-tuning) y50_demo_1_epp.py(aplicación sobre video). Buenos resultados.Las dos trampas del dataset, corregidas —y por eso el número es creíble.
EPP.cocovenía solo contrain(1690 imágenes, sinvalidnitest) y mezcla fotos sueltas con videos troceados. Se aplicaron las dos lecciones que montacargas nos cobró: (a) split de 3 vías 70/15/15; (b) reparto agrupado por video de origen —grupo_de()(regex sobre el nombre Roboflow) manda todos los frames de un clip al mismo split, con reparto best-fit para respetar proporciones—. Resultado: train 1183 / valid 254 / test 253, con 0 grupos compartidos entre splits. Por eso el mAP@50:95 = 0.572 (del EMA) es confiable, no inflado, a diferencia del 0.7277 de montacargas que sí tenía fuga por frames adyacentes. Matiz honesto: 0.572 es la métrica devalid(con la que se eligió el checkpoint), así que es ligeramente optimista; la cifra totalmente limpia saldría de correr eltestintacto (253 imgs), aún pendiente (50_evaluar.py).La ausencia como clase propia cierra el hueco de
No-Gloves. En guantes, la clase que dispara la alerta —la ausencia— quedó débil porque el dataset apenas la traía. Aquí el dataset anota la ausencia directamente (Mano/Extremidad/Oreja Expuesta), así que el modelo ve la mano descubierta en vez de inferirla. Es exactamente lo que faltaba para que "detectar EPP" pueda convertirse en "detectar incumplimiento". Sobrepeople_1.mp4el demo lo muestra en vivo con código de color (verde=EPP presente, rojo=zona expuesta, azul=persona): detecta Casco, Persona, Ropa alta visibilidad, Mano Expuesta y Lentes de forma consistente.Predicción a medias equivocada (se documenta porque la medición manda). Predije que las clases raras saldrían débiles por el desbalance. La medición desmintió la mitad:
Pantalla Facial(59 cajas) pasó de 0.00 → 0.64 yRed Cabello(104 cajas) de 0.00 → 0.66 —recuperaron—; soloProtector Auditivoquedó débil (0.14). Es decir, pocas muestras no condenó por sí solo a una clase; el desbalance golpeó a una, no a las tres. Fuertes: Casco 0.76, Persona 0.67, Ropa alta visibilidad 0.61, Extremidad expuesta 0.58. Mismo criterio que el "persona ⇒ montacargas" del módulo 4: la intuición se reporta, pero la que decide es la medición.⚠️ Alcance honesto.
50_demo_1_epp.pydetecta y dibuja el EPP (y su ausencia); todavía NO decide "esta persona cumple / incumple". Esa asociación EPP↔persona —¿este operario trae casco y guantes puestos?— requiere detección de persona + tracking y es el paso siguiente (50_demo_2), en la fase de integración. Hoy sabemos ver el casco, el chaleco y la mano descubierta; falta atribuirlos a cada individuo.
Capacidad futura — esqueleto de la mano (21 keypoints) sobre guantes. Objetivo distinto y más ambicioso que la caja de guante: obtener los 21 puntos articulares de la mano (muñeca + 4 por dedo, lo que MediaPipe da sobre manos desnudas) pero funcionando con guante puesto — útil para ergonomía fina de mano/muñeca, análisis de agarre o de tarea manual. Es factible, pero es estimación de pose/keypoints, no detección: otra cabeza de modelo y, sobre todo, otro tipo de dato. Lo que exige (el esfuerzo real está en la anotación):
- Dataset propio de keypoints sobre guantes. No existe ninguno público: todos los datasets de keypoints de mano (FreiHAND, InterHand2.6M, etc.) son de piel desnuda. Hay que anotar 21 puntos por mano (~15-20× el costo por instancia de una caja). Estimado inicial: ~300–500 manos anotadas para un primer modelo por fine-tuning.
- Anotación asistida por modelo (model-in-the-loop) para que sea viable: un modelo de mano bare-hand pre-rellena los puntos y el humano solo corrige; cada pocos cientos se reentrena y el siguiente lote se anota más rápido. Herramienta recomendada: CVAT (soporta esqueletos de keypoints + modelo en el loop); Roboflow también tiene proyectos de keypoints.
- Modelo a entrenar (pose de mano): YOLO-pose (el más documentado para keypoints custom, corre en 8 GB, AGPL) · RF-DETR-keypoints (consistente con la arquitectura, Apache; su
TrainConfigya expone parámetroskeypoint_*, aunque es menos documentado) · RTMPose-Hand (MMPose, especializado, setup pesado).- Validación previa recomendada (medir antes de invertir): correr un modelo de keypoints de mano bare-hand (p. ej. RTMPose-Hand) sobre footage de guantes para ver si da puntos aprovechables. Si sí → la anotación asistida es viable y se arranca; si da basura (como MediaPipe, que dio 0) → replantear con datos sintéticos o primer lote 100% manual.
Conclusión: el detector de esqueleto de mano enguantada sí es posible, pero es un mini-proyecto de datos (días de anotación, no una tarde). Encaja como capacidad avanzada de la capa de pose, principalmente al servicio del módulo 3 (Ergonomía). No es MVP.
3. Ergonomía — pose, no detección. Depende de keypoints y del cálculo de ángulos traducido a scoring ergonómico (OWAS/RULA/REBA). "Posturas prolongadas" introduce el factor tiempo, que requiere seguir a la persona el tiempo suficiente. La confiabilidad de los keypoints bajo ángulos de CCTV es el principal riesgo empírico de este módulo. La API clásica de pose de MediaPipe está deprecada; usar MediaPipe Tasks si se va por esa ruta.
4. Control vehicular — el módulo más complejo, correctamente en Fase 2. Tres dificultades acumuladas: (a) la estimación de velocidad requiere tracking y calibración de cámara (sin calibración, "velocidad" es solo píxeles por frame, no m/s); (b) la interacción montacargas-peatón exige tracking multi-objeto y razonamiento espacial entre dos entidades; (c) "puntos ciegos en cruces" implica modelar geometría de la escena. No es un módulo de MVP; difiérase con su dependencia de tracking explícita.
Validación parcial — detector de montacargas por fine-tuning (RF-DETR). Segundo fine-tuning propio del producto, mismo patrón que el de guantes. Dataset
Forklift.coco(Roboflow), RF-DETR-Medium, entorno de entrenamiento separado (Python 3.12 +rfdetr[train]; inferencia sigue en 3.14),batch=4+grad_accum=4(efectivo 16, sin desbordar 8 GB), early stopping, sobre la RTX 3070 Ti. Se usa siempre el checkpointcheckpoint_best_total.pth—el ganador entrebest_regularybest_ema—; el.pth(128 MB) son solo pesos y debe cargarse conRFDETRMedium, la misma clase del entrenamiento (no codifica la arquitectura), a diferencia del.ckpt(510 MB) que además lleva el estado del optimizador para reanudar. Resultado en inferencia sobre video real de planta (montacargas_1..5.mp4): dibuja la caja del montacargas de forma estable. Demos:30_demo_1_montacargas.py(detección 1 clase). Es el mismo salto que EPP: modelo genérico no traía "montacargas" útil → dataset propio lo resuelve.⚠️ El mAP reportado (0.7277) está inflado — no usarlo como cifra de venta. El dataset venía solo con splits train/test, sin
valid. Dos problemas se detectaron y corrigieron antes de creer cualquier número: (a) falta devalid—seleccionar el checkpoint sobre el mismotestcon el que luego se mide es "presentar el examen con las respuestas"; se re-particionó a train 202 / valid 36 / test 60 (seed 42, 0 solapamiento) enForklift.coco.prep, sin tocar el original—; (b) fuga de datos por frames adyacentes —train y test salían del mismo video con 1–3 frames de diferencia, es decir casi duplicados: el test se parece demasiado al train y la métrica sube sin que el modelo generalice—. La cifra real sobre footage nuevo será menor; queda sujeta a revalidación con video independiente, no interleaved.Hallazgo empírico — un modelo entrenado solo con positivos aprende a decir "sí". El dataset traía 202 imágenes / 203 cajas de montacargas y prácticamente ningún ejemplo negativo (escenas sin montacargas, personas sueltas, otros vehículos). Consecuencia medida: falsos positivos sobre personas. El arreglo correcto no es subir el umbral —eso solo esconde el síntoma— sino agregar ejemplos negativos al dataset. Es el gemelo del hallazgo de
No-Glovesen EPP: la clase que dispara la decisión (ausencia, o "esto NO es un montacargas") es justo la que el dataset descuida. Diferido de forma consciente para estos demos ("no lo mejoremos ahora"), pero es requisito para producción.Hallazgo contraintuitivo — agregar la clase
personaEMPEORÓ la detección. Para el demo de proximidad se pseudo-etiquetópersonausando un modelo COCO-preentrenado como anotador (30_autoetiquetar.py;persona= clase COCO que el modelo ya conoce, revisado como una migración de datos, no anotación manual). Predicción del desarrollador: "esto va a arreglar los falsos positivos". Medición: los empeoró en todos los umbrales por debajo de 0.70 (a 0.50, los falsos montacargas pasaron de 1 a 19). Causa raíz: sin negativos + las personas etiquetadas eran en su mayoría el operador SOBRE el montacargas, así que el modelo aprendió "persona ⇒ montacargas cerca". Se documenta el error porque la medición manda sobre la intuición: es exactamente el tipo de suposición que un demo barato desmiente antes de que llegue a producción.30_demo_2_montacargas_persona.py.Alerta de proximidad (demo) — geometría de cajas, no de centros.
30_demo_3_alerta_proximidad.py. Tres decisiones que un enfoque ingenuo erra: (1) separación rectángulo-a-rectángulo, no centro-a-centro —dos cajas grandes y pegadas tienen centros lejanos pero separación cero, y lo que importa para una colisión es la separación—; (2) normalización de escala tipo "em vs px":distancia_relativa = separación_px / ancho_montacargas_px, usando el ancho real conocido del montacargas (~1.2 m) como regla → la medida es invariante al zoom y a la profundidad, sin calibrar cámara; (3) exclusión del operador por contención: la fracción del área de la persona que cae DENTRO de la caja del montacargas identifica a quien lo conduce (umbral 0.60) para no gritar PELIGRO por el propio operador —la primera corrida disparó en 109/240 frames, todos el operador; tras excluirlo, 43 frames reales—. Más confirmación en N frames consecutivos antes de alertar (una alarma que parpadea se ignora).Estimación de velocidad (demo) — la lección de calibración, ya medida.
30_demo_4_velocidad.py. Se dibuja un rectángulo que cubre el ancho del camino, se declara que mide 10 m, y al cruzar el montacargas se calculavelocidad = distancia / tiempo. Confirma empíricamente lo que la nota del módulo advertía en teoría: sin calibración, "velocidad" son píxeles, no m/s. Detalles que evitan errores sutiles: el tiempo es de video, no de reloj (segundos = n_frame / fps, nuncatime.time(); el equivalente acreatedAtvsDate.now());metros_por_px = largo_m / ancho_px; una toma cenital elimina la perspectiva (un píxel = los mismos metros en todo el cuadro), mientras que una cámara inclinada exigiría homografía de piso; y si se pierde el tracking del montacargas dentro del tramo (>8 frames) se descarta la medición, nunca se interpola. Honestidad del alcance: esto es un PoC con distancia declarada y una sola cámara, no velocidad calibrada ni tracking multi-objeto real —es "píxeles vueltos métricos por una declaración manual"—. Sirve para demostrar el cálculo y para dimensionar exactamente lo que Fase 2 tendrá que calibrar de verdad.
5. Conducta segura — técnicamente heterogéneo, dividir por sub-caso. No es un módulo único:
- Correr en área operativa → pose + tracking para estimar velocidad de la persona. Viable pero depende de tracking.
- Uso de celular → detección de un objeto pequeño (teléfono) + pose (mano cerca de la cabeza). El teléfono es de los objetos más difíciles de detectar en CCTV; alto riesgo de falsos negativos.
- Omisión de pasamanos → pose + relación espacial mano-barandal en escaleras. Caso de nicho, geometría específica por escena, baja generalización. Candidato a posponer o tratar como prueba de concepto.
Recomendación: este módulo no debe tratarse como una sola entrega. Sus sub-casos tienen dificultad muy dispar y conviene priorizarlos por separado.
PoC — correr en planta por velocidad de persona (sin entrenar).
40_demo_1_correr.py. RF-DETR COCO detectaperson(clase 1), un tracker casero por IoU (permisivo, sin AGPL) le da ID estable a cada persona, y se estima su velocidad con la misma física del30_demo_4(velocidad = distancia / tiempo, tiempo de video no de reloj). Hallazgo de diseño que corrige el supuesto del demo de montacargas:people_1.mp4es un pasillo en perspectiva, no una toma cenital, así que la escala fija de un tramo (10 m) miente cuando la persona cambia de profundidad. La solución —la misma "regla del objeto conocido" del30_demo_3— es usar la estatura de la persona (~1.70 m) como regla local:metros_por_px = 1.70 / altura_caja_px. Como la caja se encoge al alejarse igual que el desplazamiento, la escala se autocorrige con la profundidad, sin calibrar cámara. El demo trae ambos modos (teclam) para ver el contraste en vivo. Honestidad del alcance: sobre el clip disponible las personas caminan (máx. medido ~6 km/h), así que la alerta no se dispara —correcto, no se fuerza un falso positivo—; y el tracker casero aguanta 2–4 personas que no se cruzan, pero confunde IDs al ocluirse. Esa es la dependencia de tracking serio (ByteTrack) que la arquitectura ya marcaba para este módulo.
PoC — uso de celular por detección de objeto zero-shot (sin entrenar).
40_demo_2_celular.py. El mismo RF-DETR COCO trae la clasecell phone(clase 77) lista; se atribuye el teléfono a su dueño por contención (qué fracción de la caja del teléfono cae dentro de cada persona, el mismo truco con que en el30_demo_3se identificaba al operador SOBRE el montacargas), con tracking + confirmación temporal e histéresis para que la alerta no parpadee. Resultado sobrepeople_2_phone.mp4(3 personas, 1 con teléfono): el teléfono se atribuyó siempre a la persona correcta y nunca a las otras dos (0 falsos positivos de atribución). Pero la medición confirma la advertencia teórica del módulo: el teléfono es un objeto diminuto en CCTV — confianza 0.25–0.46 (prom. 0.32) frente a 0.9+ deperson, y al bajar el umbral para no perderlo aparecen falsos positivos (el modelo confunderemote/bookcon el teléfono; el demo los muestra con la teclat). No hay umbral mágico: es el trade-off falso-negativo vs. falso-positivo, ahora medido en vez de supuesto.
Decisión de mejora — entrenar la CONDUCTA, no el objeto (ruta B). Para llevar el uso de celular de PoC a producción, el fine-tuning propio (RF-DETR + imágenes de la planta, el mismo moat de EPP y vehículos) es el camino, pero la clase que se entrena importa más que el hecho de entrenar. Dos rutas:
- Ruta A — mejorar la detección del objeto
cell phone. Es la intuitiva y la peor: pelea contra la física. Un teléfono ocupa ~20–40 px en CCTV; el fine-tuning sube la confianza y quita falsos positivos, pero NO crea resolución que no está en los píxeles. En cámara de techo lejana empeora. (Nota:people_2_phone.mp4es una condición favorable —cámara a media distancia— y aun así el teléfono no pasó de 0.46.)- Ruta B — entrenar la conducta como una clase holística (
persona-usando-telefono): brazo doblado + teléfono a la oreja / cabeza inclinada mirando las manos. La silueta de la postura mide cientos de píxeles, no decenas → se detecta de forma estable, y es directamente el evento que importa ("usa el teléfono"), no un proxy ("hay un teléfono"). Resuelve de paso el límite del PoC actual, que ve el objeto pero no confirma el gesto.Ruta B es la recomendada. Aplica el mismo mecanismo de detección RF-DETR ya validado (guantes, montacargas) sobre una clase de mayor tamaño aparente. Requisitos heredados de las lecciones ya pagadas: ejemplos negativos obligatorios (personas trabajando SIN teléfono en la misma pose, o el modelo aprende "persona ⇒ teléfono", como el "persona ⇒ montacargas" que medimos en el módulo 4); sin fuga por frames adyacentes del mismo clip; fotos de la planta en varios ángulos y turnos; y anotación asistida (model-in-the-loop) usando el zero-shot actual como pre-etiquetador para abaratar el etiquetado. Antes de escalar el dataset conviene medir con un lote chico (~150–300 imágenes) y comparar contra el zero-shot, el mismo principio de "medir antes de comprometer". El factor que inclina A vs B es la cámara real en planta (techo lejano refuerza B; media distancia deja A como opción).
6. Analytics y reportes — territorio MERN, con una bifurcación importante. Dashboard, heatmaps, reportes EHS automáticos y tendencias se construyen sobre los eventos en MongoDB, con React y Node; no requieren CV. La excepción es "reincidencia por operario": identificar al mismo operario entre eventos implica re-identificación de personas (re-ID), una capacidad de CV técnicamente difícil y sensible en privacidad. Existe una alternativa no-biométrica (atribución por credencial/badge, por zona o por turno) que evita el re-ID. Esta decisión —re-ID vs. atribución alternativa— debe tomarse de forma consciente y con criterio legal/ético, no resolverse por defecto.
Reparto por fase
- Fase 1 (MVP): Zonas restringidas + Detección de EPP + Ergonomía + Analytics base (dashboard de eventos). Cubre los tres módulos de menor riesgo técnico y la visualización.
- Fase 2: Control vehicular + Conducta segura (sub-casos priorizados) + Analytics avanzado (tendencias, reincidencia, integraciones EHS) + motor de reglas formal. Concentra las dependencias de tracking, calibración y re-ID.
Este reparto coincide con que la Fase 1 evita tracking salvo refinamientos opcionales, mientras que la Fase 2 lo asume como requisito.
Orquestación CV ↔ MERN
El contrato de la sección siguiente define qué datos cruzan de la capa de CV al stack MERN; esta sección define cómo. La capa de CV vive en Python (detección, pose, tracking); el stack de negocio vive en Node. Son dos runtimes distintos: Node no ejecuta modelos; Python no es dueño de la base. El puente entre ambos es el punto de integración crítico.
Dos hechos condicionan el diseño:
- Runtimes separados. Toda la percepción ocurre en Python. Node orquesta, persiste y sirve. La lógica de negocio y el acceso a MongoDB quedan de un solo lado (Node).
- Procesamiento largo. Analizar un video no es una operación de milisegundos (medición interna: ~20 s para un clip de 25 s a 10 análisis/seg en GPU de desarrollo). No cabe en un request HTTP síncrono; obliga a un modelo de trabajos asíncronos (jobs).
Ciclo de vida de un job (ingesta por archivo)
1. Front sube el .mp4 + zona (polígono JSON) → Node guarda archivo y crea Job(status: "processing")
2. Node lanza el worker: spawn python worker.py --id <jobId> --token <secreto>
3. Worker → GET /api/jobs/:id/config → Node responde { zona, rutaVideo, fpsAnalisis }
4. Worker procesa (percepción + reglas del módulo) → produce eventos
5. Worker → POST /api/jobs/:id/result → { eventos[], métricas, artefactos } (con token)
6. Node valida, guarda en Mongo, marca status: "done"
7. Front (polling o WebSocket) ve "done" y renderiza el dashboard
El worker Python solo habla HTTP con Node: recibe su configuración y devuelve su resultado. No necesita credenciales de MongoDB ni conocer el esquema. Esto lo mantiene como componente reemplazable y conserva a Node como único dueño de la base.
Tres garantías operativas mínimas
- Detección de fallo. Como Node hace el
spawn, retiene el handle del proceso y observa su código de salida. Si el worker muere sin hacer el POST final, Node marca el job comoerroren lugar de dejarlo colgado enprocessing. No se depende únicamente del callback del worker. - Autenticación worker → API. El endpoint de resultados no puede estar abierto. Node genera un token por job en el
spawn; el worker lo devuelve en el callback; Node lo valida. Evita inyección de resultados falsos. - Concurrencia acotada. Cada worker carga un modelo en la GPU. Sobre una sola GPU de desarrollo el número de procesos simultáneos es 1–2 antes de agotar VRAM (sujeto a medición). Se requiere un tope explícito y una cola de espera, aunque sea trivial al inicio.
Evolución del mecanismo
El spawn directo es la v1 correcta para bajo volumen. El día que la concurrencia acotada se vuelva el cuello de botella real —no antes— el mecanismo migra a una cola de trabajos (p. ej. BullMQ/Redis con worker Python consumidor), sin cambiar el contrato: el worker sigue hablando HTTP, solo cambia "Node hace spawn" por "Node encola". La transición a RTSP en vivo (fase posterior) sustituye la ingesta por archivo por streams continuos, pero conserva la separación worker/negocio.
Definición de zonas en el frontend
El "setup manual de polígonos por cámara" (módulo 1) ocurre en un canvas de React sobre un frame de referencia, no en la ventana de OpenCV (que es solo herramienta de desarrollo). El frontend captura las coordenadas del polígono y las envía como configuración del job; el worker las consume tal cual en el point-in-polygon. Los eventos de mouse del navegador reemplazan al callback de OpenCV.
Modelo de despliegue
Dónde vive el cómputo de CV es una decisión de arquitectura, no un detalle de operaciones: afecta costo, privacidad del video y exposición de licencia.
| Web / SaaS | Desktop / on-premise | |
|---|---|---|
| Frontend | React servido como estático | React empaquetado (Electron/Tauri) |
| ¿Dónde corre la percepción? | GPU en el servidor | GPU de la máquina del cliente |
| Costo de infraestructura | Alto (GPU 24/7) | Bajo (usa hardware del cliente) |
| Privacidad del video | El footage sale hacia el servidor | El footage no abandona el sitio |
| Exposición AGPL-3.0 | El SaaS distribuye salida sobre red → activa el copyleft de forma más agresiva | On-premise cambia el análisis, no lo elimina |
| Actualización | Centralizada | Re-distribución al cliente |
Para footage de CCTV —sensible por naturaleza, con clientes que suelen tener hardware en sitio— el despliegue on-premise es competitivo: aprovecha la GPU del cliente, mantiene el video local y desacopla el costo del volumen. La ruta SaaS simplifica operación y actualización a cambio de infraestructura GPU y un análisis de licencia más delicado.
Esta decisión interactúa con la de licencias de la capa de percepción: la ruta permisiva (RF-DETR + MediaPipe + ByteTrack) reduce el riesgo en el escenario SaaS, donde la exposición AGPL es mayor.
Contrato con el stack MERN
La capa de CV entrega un evento por incidente detectado. El motor de reglas de cada módulo produce el mismo tipo de objeto, lo que permite que el stack MERN trate todos los módulos de forma uniforme. Borrador a refinar:
{
"camera_id": "string",
"module": "zone | ppe | ergonomics | vehicle | behavior | ...",
"event_type": "zone_intrusion | missing_ppe | bad_lift | overspeed | phone_use | ...",
"timestamp": "ISO-8601",
"severity": "low | medium | high",
"detail": { /* específico por módulo: zona, EPP faltante, ángulo, velocidad, etc. */ },
"actor_ref": "opcional — id de tracking o credencial, según módulo y decisión de re-ID"
}
El campo module permite que Analytics (módulo 6) agrupe, filtre y reporte por módulo sin lógica especial. El campo actor_ref queda abierto a la decisión de reincidencia descrita arriba.
Pendientes y riesgos abiertos
- Licencia AGPL-3.0: la detección ya se resolvió con RF-DETR (Apache 2.0), eliminando esa exposición en la capa de detección. Quedan por decidir pose (YOLO26-pose AGPL vs. MediaPipe / RF-DETR-keypoints permisivos) y tracking (Ultralytics AGPL vs. ByteTrack/BoT-SORT permisivos). Requiere revisión legal según despliegue.
- Entrenamiento custom para EPP, vehículos y celular: es trabajo de datos anotados, no de configuración. Constituye el moat y debe planearse desde el inicio. Confirmado en el módulo 4: el dataset de montacargas venía casi sin ejemplos negativos y produjo falsos positivos sobre personas; el arreglo es más data (negativos), no ajustar el umbral. Todo dataset propio debe presupuestar sus negativos y evitar fuga por frames adyacentes del mismo video (ver notas del módulo 4). EPP ya es el tercer fine-tuning validado (13 clases,
50_entrenamiento.py) y el primero hecho con el método correcto: split de 3 vías agrupado por video de origen (0 fuga), lo que hace su mAP (0.572) creíble en vez de inflado. Ese patrón anti-fuga es el que todo dataset futuro debe seguir. - Calibración de cámara para velocidad (módulo 4): sin ella no hay medición real de velocidad, solo movimiento en píxeles. Confirmado empíricamente en el demo
30_demo_4_velocidad.py: el cálculo funciona con una distancia declarada (10 m) y toma cenital, pero una cámara inclinada exige homografía de piso y una velocidad defendible exige calibración real + tracking, no una única detección por frame. - Re-identificación de personas (módulo 6, reincidencia): difícil y sensible en privacidad; evaluar la alternativa por credencial antes de comprometer re-ID.
- Objetos pequeños en CCTV (guantes, calzado, celular): degradación esperada respecto a benchmarks; magnitud sujeta a footage propio. Nota validada: los guantes no solo son pequeños, además anulan los landmarks de mano genéricos (MediaPipe hands → 0 detecciones sobre manos enguantadas); se tratan como detección de objeto con dataset propio, no como keypoints (ver módulo 2). Validado en el módulo 2: el fine-tuning de EPP (13 clases) detecta guante, lentes y —lo clave— modela la ausencia de protección como clase propia (Mano/Extremidad/Oreja Expuesta) en vez de inferirla, resolviendo el punto ciego de
No-Gloves. Confirmado también con el celular (módulo 5): zero-shot da confianza 0.25–0.46 y falsos positivos al bajar el umbral; el fine-tuning sube la confianza pero no crea resolución — por eso la mejora recomendada es entrenar la conducta (silueta grande), no el objeto diminuto (ruta B, ver módulo 5). - Pose en ángulos de CCTV (módulo 3): confiabilidad de keypoints es cuestión empírica.
- Cámaras concurrentes sobre el hardware de desarrollo: límite acotado por medición, no asumido. El submuestreo temporal —analizar N frames/seg en vez de todos— es la palanca directa: medición interna muestra ~2.8× menos cómputo al pasar de 30 a 10 análisis/seg, sin pérdida perceptible para intrusión en zona. Desacopla la tasa de inferencia (cara) de la de visualización y multiplica las cámaras atendibles por GPU. El piso de FPS analizables lo fija la detección de eventos rápidos (velocidad, correr).
- Accuracy: sin datos propios, ninguna cifra es defendible. La arquitectura está diseñada para medir y mejorar, no para garantizar a priori.
Elemento invariante
El diferenciador del producto no reside en el modelo, sino en el dataset anotado propio —concentrado en los módulos que exigen entrenamiento custom (EPP, vehículos, conducta)— y en la velocidad del pipeline de datos. Los competidores de referencia emplean los mismos bloques tecnológicos. Por ello el pipeline de datos (offline) figura en la arquitectura desde la primera iteración. Los modelos son reemplazables; el moat no lo es.