IoT e IA

Detección de objetos con YOLO sobre MQTT: publica las detecciones como JSON

Ejecuta un modelo YOLO sobre una webcam o un flujo RTSP en Python, convierte las detecciones de cada fotograma en JSON compacto y publica conteos y alertas por MQTT para que cualquier dashboard o automatización pueda reaccionar.

Ilustración de detección de objetos YOLO con MQTT con cuadros delimitadores sobre un fotograma de cámara y mensajes JSON de detección que fluyen hacia un broker
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

La detección de objetos con YOLO sobre MQTT convierte una cámara en un sensor: en lugar de transmitir vídeo, publicas pequeños mensajes JSON que describen lo que ve la cámara. En este tutorial ejecutarás un modelo YOLO sobre una webcam o un flujo RTSP con Python, publicarás las detecciones de cada fotograma, mantendrás conteos de objetos y generarás alertas que cualquier cliente MQTT puede consumir.

Por qué enviar detecciones en lugar de vídeo

El vídeo es pesado, difícil de buscar y necesita a una persona que lo mire. Las detecciones son ligeras, estructuradas y legibles por máquinas. Un único mensaje como "3 personas, 1 carretilla en la zona A" ocupa unos cientos de bytes, mientras que el fotograma del que procede ocupa decenas o cientos de kilobytes.

Publicar las detecciones por MQTT también desacopla la visión de todo lo demás:

  • Los dashboards se suscriben a los conteos y muestran la ocupación en tiempo real.
  • Un servicio de automatización se suscribe a las alertas y enciende una luz de aviso.
  • Un historian almacena los conteos por minuto para generar informes.
  • Otro modelo, quizá en otra máquina, combina la visión con otros sensores.

Ninguno de estos consumidores necesita saber qué cámara, modelo o GPU produjo el resultado. Es el mismo acoplamiento débil que hace popular a MQTT para los sensores, ahora aplicado a la visión artificial en dispositivos de borde.

Arquitectura

camera (USB / RTSP) -> Python + YOLO -> MQTT broker -> dashboards, alerts, storage

connectmq/demo/<id>/site1/dock/cam1/detections   every processed frame (throttled)
connectmq/demo/<id>/site1/dock/cam1/counts       retained, current count per class
connectmq/demo/<id>/site1/dock/cam1/alerts       event when a rule triggers
connectmq/demo/<id>/site1/dock/cam1/status       retained, online/offline (Last Will)

Requisitos previos

  • Python 3.9 o posterior, una webcam o una cámara RTSP
  • Una CPU basta para modelos pequeños a unos pocos fotogramas por segundo; una GPU o una Jetson permiten el tiempo real
  • Paquetes:
pip install ultralytics opencv-python "paho-mqtt>=2.0"

La primera ejecución descarga los pesos preentrenados yolov8n.pt (el modelo nano entrenado con las 80 clases de COCO). Consulta la documentación de Ultralytics para conocer otros tamaños de modelo y versiones más recientes; la API que se usa a continuación funciona con todos ellos.

El publicador de detecciones

Guárdalo como yolo_mqtt.py. Abre la cámara, ejecuta YOLO, convierte los resultados a JSON, publica las detecciones como máximo dos veces por segundo, actualiza los conteos retenidos cuando cambian y genera una alerta cuando hay personas en una zona restringida.

import json
import time
import uuid

import cv2
import paho.mqtt.client as mqtt
from ultralytics import YOLO

SOURCE = 0  # índice de la webcam, o "rtsp://user:pass@192.168.1.50:554/stream1"
PREFIX = "connectmq/demo/yolo-c83f/site1/dock/cam1"
CLASSES = {"person", "car", "truck", "forklift"}  # forklift solo con un modelo personalizado
MIN_CONF = 0.5
PUBLISH_EVERY = 0.5  # segundos
ZONE = (0.6, 0.0, 1.0, 1.0)  # zona restringida: 40 % derecho del fotograma (x1, y1, x2, y2)

model = YOLO("yolov8n.pt")
names = model.names

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2,
                     client_id=f"yolo-cam1-{uuid.uuid4().hex[:6]}")
client.will_set(f"{PREFIX}/status", json.dumps({"online": False}), qos=1, retain=True)
client.connect("broker.connectmq.com", 1883, keepalive=30)
client.loop_start()
client.publish(f"{PREFIX}/status", json.dumps({"online": True, "model": "yolov8n"}),
               qos=1, retain=True)

cap = cv2.VideoCapture(SOURCE)
last_pub, last_counts, zone_alert = 0.0, None, False


def in_zone(cx, cy):
    x1, y1, x2, y2 = ZONE
    return x1 <= cx <= x2 and y1 <= cy <= y2


try:
    while True:
        ok, frame = cap.read()
        if not ok:
            time.sleep(1)
            cap.open(SOURCE)  # reconexión ante cortes de RTSP
            continue
        h, w = frame.shape[:2]
        result = model(frame, conf=MIN_CONF, verbose=False)[0]

        objects, counts, people_in_zone = [], {}, 0
        for box in result.boxes:
            label = names[int(box.cls)]
            if label not in CLASSES:
                continue
            x1, y1, x2, y2 = box.xyxyn[0].tolist()  # normalizado 0..1
            cx, cy = (x1 + x2) / 2, (y1 + y2) / 2
            objects.append({"label": label, "conf": round(float(box.conf), 2),
                            "box": [round(v, 3) for v in (x1, y1, x2, y2)]})
            counts[label] = counts.get(label, 0) + 1
            if label == "person" and in_zone(cx, cy):
                people_in_zone += 1

        now = time.time()
        if now - last_pub >= PUBLISH_EVERY:
            last_pub = now
            client.publish(f"{PREFIX}/detections", json.dumps(
                {"ts": now, "frame": [w, h], "objects": objects}), qos=0)

        if counts != last_counts:
            last_counts = counts
            client.publish(f"{PREFIX}/counts", json.dumps({"ts": now, **counts}),
                           qos=1, retain=True)

        if people_in_zone and not zone_alert:
            zone_alert = True
            client.publish(f"{PREFIX}/alerts", json.dumps(
                {"ts": now, "rule": "person-in-zone", "state": "raised",
                 "count": people_in_zone}), qos=1)
        elif not people_in_zone and zone_alert:
            zone_alert = False
            client.publish(f"{PREFIX}/alerts", json.dumps(
                {"ts": now, "rule": "person-in-zone", "state": "cleared"}), qos=1)
finally:
    cap.release()
    client.publish(f"{PREFIX}/status", json.dumps({"online": False}), qos=1, retain=True)
    client.loop_stop()
    client.disconnect()

Observa la salida desde otra terminal:

mosquitto_sub -h broker.connectmq.com -p 1883 -i yolo-watch-c83f -t "connectmq/demo/yolo-c83f/#" -v

Entra en la parte derecha del encuadre y verás una alerta raised, seguida de cleared cuando salgas.

Diseñar el payload de detección

Un mensaje de detección del script tiene este aspecto:

{
  "ts": 1792140112.42,
  "frame": [1280, 720],
  "objects": [
    {"label": "person", "conf": 0.88, "box": [0.612, 0.204, 0.731, 0.958]},
    {"label": "car", "conf": 0.74, "box": [0.051, 0.402, 0.338, 0.811]}
  ]
}

Pautas que mantienen útiles los payloads de detección de objetos YOLO por MQTT:

Elección Recomendación Motivo
Coordenadas de los cuadros Normalizadas de 0 a 1 Los consumidores no necesitan la resolución; las zonas sobreviven a los cambios de cámara
Confianza Redondeada a 2 decimales Ahorra bytes sin perder significado
Fotogramas Nunca en el topic de detecciones Mantén las imágenes en un topic aparte o fuera de MQTT
Frecuencia Limitada a entre 1 y 5 mensajes por segundo Los consumidores rara vez necesitan todos los fotogramas
Conteos Retenidos, publicados al cambiar Los dashboards nuevos ven el estado actual al instante
Información del modelo En el topic de estado retenido Permite relacionar los resultados con una versión del modelo

Reglas de conteo y alerta

Contar objetos por fotograma es fácil; contar los objetos que pasan es más difícil, porque la misma persona aparece en muchos fotogramas. Para contar el flujo, activa el seguimiento en Ultralytics (model.track(frame, persist=True)), que asigna un ID a cada objeto, y cuenta cada ID una sola vez cuando su centro cruce una línea. Publica el cruce como un evento en lugar de como un conteo, y deja que un servicio posterior agregue por minuto.

Las reglas de alerta deben estar cerca de la cámara cuando la latencia importa (una persona que entra en una célula robotizada) y más abajo en la cadena cuando son lógica de negocio (más de cinco camiones esperando en el muelle durante diez minutos). Como los resultados están en MQTT, puedes mover una regla de un sitio a otro sin cambiar el código de la cámara.

Consumir las detecciones

El valor de publicar los resultados de la detección de objetos YOLO por MQTT se aprecia en el lado de los consumidores. Algunos patrones que los equipos construyen en las primeras semanas:

  • Dashboard de ocupación. Suscríbete al topic retenido counts de todas las cámaras con un comodín como site1/+/+/counts. Cada nueva sesión del dashboard recibe al instante el conteo actual de cada cámara y después las actualizaciones en directo.
  • Estadísticas por minuto. Un pequeño servicio en Python o Node-RED agrega los conteos por minuto y los escribe en una base de datos de series temporales. Los informes sobre horas punta o la utilización del muelle salen de ahí, no del vídeo.
  • Enclavamientos y avisos. Un servicio de automatización se suscribe al topic de alertas y enciende una luz de aviso o envía un mensaje a un supervisor. Mantén los enclavamientos críticos para la seguridad en hardware certificado; las alertas de la cámara son un complemento, no un sustituto.
  • Comprobación cruzada con otros sensores. Una alerta de persona combinada con una barrera fotoeléctrica o un contacto de puerta es mucho más fiable que cualquiera de ellos por separado.

Como cada uno de ellos es simplemente otro suscriptor, añadir uno nunca ralentiza la cámara. El broker reparte el mismo mensaje a todos.

Privacidad y minimización de datos

Las cámaras que ven personas conllevan responsabilidades. Publicar solo etiquetas, conteos y cuadros normalizados, nunca caras ni fotogramas, es la medida de privacidad más sólida que puedes tomar, y encaja de forma natural con esta arquitectura. Si necesitas instantáneas para verificación, almacénalas localmente con un periodo de retención corto o envíalas a un topic privado que solo puedan leer los servicios autorizados. Documenta qué detecta la cámara y por qué, y consulta la normativa local antes de desplegarla en lugares de trabajo o espacios públicos.

Resolución de problemas

  1. Frecuencia de fotogramas muy baja en CPU. Usa el modelo nano, reduce el tamaño de entrada con imgsz=416 o procesa uno de cada dos fotogramas.
  2. El flujo RTSP se congela. OpenCV almacena fotogramas en búfer; lee de forma continua en un hilo aparte y procesa solo el fotograma más reciente.
  3. Alertas que parpadean. Exige una detección en varios fotogramas consecutivos antes de activarla y varios fotogramas vacíos antes de desactivarla.
  4. Mensajes rechazados o perdidos. En el servidor público, mantente por debajo de 10 mensajes por segundo por cliente y de 64 KB por mensaje; la limitación de frecuencia se encarga de ello.
  5. Clases incorrectas. El modelo preentrenado solo conoce las clases de COCO. Para carretillas, cascos o productos concretos, entrena un modelo personalizado con tus propias imágenes.

Notas para producción

Para cámaras reales, publica en un broker privado por TLS (puerto 8883) con credenciales, y nunca envíes fotogramas con personas a un topic público. Un broker MQTT gestionado como ConnectMQ se encarga de la autenticación, los certificados y la monitorización, y su explorador de topics en directo es una forma rápida de confirmar que las detecciones están fluyendo. Usa el servidor MQTT público gratuito solo para desarrollo y prototipos con escenas de prueba, nunca con datos de producción.

Si tu cámara funciona sobre una placa NVIDIA, consulta NVIDIA Jetson con MQTT para la inferencia acelerada por GPU, y para una variante de bajo coste en un microcontrolador consulta detección de personas con ESP32-CAM. La guía de IA e IoT explica cómo encaja la visión con otras cargas de trabajo de IA en el borde.

Empieza con ConnectMQ

Crea un broker privado de ConnectMQ en el plan gratuito Serverless en app.connectmq.com, sin tarjeta de crédito, añade client.username_pw_set("YOUR_USERNAME", "YOUR_PASSWORD") y client.tls_set(), cambia al puerto 8883 y las detecciones de tu cámara fluirán de forma segura.

Preguntas frecuentes

¿Puedo enviar fotogramas de la cámara por MQTT junto con las detecciones?

Puedes enviar instantáneas JPEG en un topic aparte dentro del límite de tamaño de mensaje del broker, pero mantén el vídeo continuo en un protocolo de streaming. Las detecciones deben seguir siendo pequeñas y sin fotogramas.

¿Qué tamaño de modelo YOLO debo usar?

Empieza con el modelo nano para CPU y placas pequeñas, y pasa a small o medium solo si la precisión no es suficiente. Los modelos más grandes necesitan una GPU para funcionar en tiempo real.

¿Cómo cuento personas en lugar de detectarlas en cada fotograma?

Usa el tracker para asignar IDs y cuenta cada ID una sola vez cuando cruce una línea o entre en una zona. Publica cada cruce como un evento y agrega más adelante en la cadena.

¿El broker ejecuta el modelo YOLO?

No. El modelo se ejecuta en el ordenador de tu cámara y el broker solo transporta los mensajes resultantes a quien esté suscrito.

Artículos relacionados

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.