El fichero con la clave de la camara y el token de Telegram no estaba en .gitignore: solo la suerte evitaba que un git add -A lo subiera. |
||
|---|---|---|
| models | ||
| scripts | ||
| src/pet_monitor | ||
| .gitignore | ||
| .python-version | ||
| pet_alerts.py | ||
| pet_audio.py | ||
| pet_common.py | ||
| pet_speed.py | ||
| pet_telegram.py | ||
| pyproject.toml | ||
| Readme.md | ||
| README.md | ||
| uv.lock | ||
Pet Monitor con Raspberry Pi 5 & Hailo-8 (AI HAT+) Sistema de monitorización inteligente para mascotas utilizando aceleración por hardware NPU (chip Hailo-8) para visión por computador y detección de audio local en CPU para ladridos/sonidos anómalos.
- Hardware Utilizado Placa: Raspberry Pi 5. Coprocesador IA: Raspberry Pi AI HAT (Hailo-8, 26 TOPS). Cámara: cámara IP TP-Link Tapo accesible por RTSP en la red local. Entrada de audio: micrófono integrado de la propia cámara Tapo (viaja en el stream RTSP). No hace falta micrófono USB.
Corrección respecto a la primera versión de este documento: el módulo instalado identifica como Hailo-8, no Hailo-8L. Verificado con
hailortcli fw-control identify:Board Name: Hailo-8 Device Architecture: HAILO8 Firmware Version: 4.23.0Por eso el modelo cargado es
yolov8s_h8.hef(el de 8 carriles de cómputo de la NPU), no la variante_h8l.
- Configuración del Sistema y Hardware
Habilitar PCIe Gen 3
Para maximizar el ancho de banda con el chip Hailo, se configuró el bus PCIe en
/boot/firmware/config.txt:
dtparam=pciex1
dtparam=pciex1_gen=3
Paquetes del Sistema Instalados Se instalaron las utilidades de cámara, controladores del kernel y librerías dinámicas de audio requeridas por el sistema operativo:
sudo apt update && sudo apt full-upgrade -y
sudo apt install -y hailo-all rpicam-apps libportaudio2 portaudio19-dev libasound2-dev python3-picamera2 python3-av
python3-av(PyAV) es imprescindible para el audio: OpenCV no sabe decodificar la pista de sonido del RTSP. Se instala a nivel de sistema y el venv lo ve gracias a--system-site-packages. Verificación de hardware NPU Hailo:lspcimuestra el coprocesador Hailo Technologies yhailortcli fw-control identifyresponde con el firmware cargado. Cámara:ffprobe -rtsp_transport tcp rtsp://usuario:clave@IP:554/stream2debe describir un stream H.264.
- Cámara IP Tapo (RTSP)
La cámara Tapo no expone RTSP con la cuenta de TP-Link: hay que crear una cuenta local de cámara en la app Tapo (Ajustes de la cámara → Configuración avanzada → Cuenta de cámara). El usuario y la contraseña de esa cuenta son los que van en el
.env.
Streams disponibles (verificados con ffprobe):
stream1: 2304x1296 @ 15 fps (máxima calidad)stream2: 1280x720 @ 15 fps (el que usa el monitor: menos CPU de decodificación)stream3: no existe en este modelo (devuelve 404)
El vídeo va por H.264, así que la decodificación es por software (la Pi 5 solo acelera HEVC por hardware). A 720p el coste es asumible; con stream1 sube bastante sin mejorar la detección, porque el modelo trabaja a 640x640 en cualquier caso.
Credenciales en ~/pet_monitor/.env (no se versiona):
CAMERA_IP=192.168.178.234
CAMERA_USER=camera_dog
CAMERA_PW=...
# Opcionales:
# CAMERA_STREAM=stream2 # stream1 | stream2
# CAMERA_RTSP_URL=rtsp://... # URL completa; tiene prioridad sobre lo anterior
# PET_AUDIO=0 # desactiva la escucha de ladridos
# BARK_THRESHOLD=0.15 # umbral de la puntuación de Bark
# BARK_COOLDOWN=15 # segundos mínimos entre dos avisos de ladrido
- Entorno de Desarrollo Python (
uv) Se configuró el entorno virtual utilizandouvcon acceso a los paquetes del sistema (--system-site-packages) para reutilizar los bindings compilados depicamera2,hailo_platformyOpenCVinstalados por Debian/Raspberry Pi OS:
cd ~/pet_monitor
# Creación del entorno con acceso a librerías nativas
uv venv --system-site-packages
# Instalación de librerías en el entorno virtual
uv pip install sounddevice numpy scipy requests onnxruntime
Nota técnica: Se seleccionó
onnxruntimeen lugar detflite-runtimepara garantizar compatibilidad con versiones modernas de Python en ARM64 sin necesidad de compilar desde fuente.
Nota:
uvvive en~/.local/bin/uvy no está en el PATH de sesiones SSH no interactivas; usabash -lc 'uv run ...'o~/.local/bin/uv run ....
- Estado Actual del Software El proyecto se reparte en módulos, más los scripts de apoyo:
pet_common.py: carga del.env, construcción de la URL RTSP y enmascarado de credenciales.pet_speed.py: visión por computador (vídeo + NPU Hailo). Es el punto de entrada.pet_audio.py: detección de ladridos sobre el audio de la cámara.pet_alerts.py: decide qué sale por consola, qué se guarda como captura y qué va a Telegram.pet_telegram.py: cliente de Telegram (cola + hilo de envío, reintentos).scripts/fetch_models.sh: descarga y verifica (SHA-256) el modelo YAMNet.scripts/telegram_check.py: puesta en marcha de Telegram (valida el token y deduce el chat_id).
pet_speed.py: Script de visión artificial optimizado con aceleración por hardware que:
- Lee el vídeo de la cámara Tapo por RTSP en un hilo aparte (
RtspVideoStream), que se queda siempre con el fotograma más reciente y descarta los antiguos. Así no se acumula latencia si el análisis va más lento que la cámara. - Se reconecta automáticamente con retroceso exponencial (2 s → 30 s) si la cámara se cae, se reinicia o cambia de IP.
- Escala el fotograma con letterbox (mantiene la relación de aspecto, relleno gris) hasta los 640x640 que espera el modelo, y deshace esa transformación para dar las cajas y el centroide en píxeles del fotograma original.
- Utiliza la API nativa de aceleración
picamera2.devices.hailo.Hailo(parte del paquete del sistema, no requiere la cámara CSI) cargando el modelo optimizado (/usr/share/hailo-models/yolov8s_h8.hef). - Filtra detecciones del conjunto COCO para gato (id: 15) y perro (id: 16).
- Calcula el centroide del animal y evalúa el desplazamiento entre fotogramas consecutivos para alertar si la velocidad supera el umbral de carrera (
SPEED_THRESHOLD = 0.30por segundo). - Imprime estadísticas cada 10 s (fps de captura, fps de análisis, latencia de inferencia, reconexiones).
- Libera el stream RTSP y el acelerador Hailo al recibir
SIGINT(Ctrl+C) oSIGTERM, de cara a ejecutarlo como servicio systemd.
Rendimiento medido en la Pi 5 con stream2:
[STATS] captura 14.7 fps | análisis 12-15 fps | inferencia 11.8 ms | reconexiones 0
Nota sobre el orden de color: los HEF de YOLOv8 esperan RGB, y así se alimenta al modelo. Comprobado con imágenes de prueba: con un perro la confianza pasa de 0.42 (BGR) a 0.85 (RGB), y con un gato de 0.79 a 0.87. Si algún día hiciera falta comparar, PET_COLOR_ORDER=bgr uv run pet_speed.py.
Ejecución:
uv run pet_speed.py
- Detección de Ladridos (audio de la cámara) La cámara Tapo publica su micrófono dentro del mismo stream RTSP que el vídeo, así que no hace falta micrófono USB. El audio va en G.711 A-law, 8 kHz mono, pero el vídeo y el audio se piden en dos conexiones RTSP distintas (la cámara admite varias en paralelo), porque OpenCV no sabe decodificar sonido y hay que usar PyAV.
pet_audio.py:
- Remuestrea el audio a los 16 kHz que espera YAMNet y lo analiza en ventanas de 0.975 s con medio solape.
- Carga YAMNet en ONNX (
models/yamnet.onnx, 16 MB, no versionado: se descarga conbash scripts/fetch_models.sh). - Dispara por la clase
Barkde AudioSet, no por el grupo canino: con el mismo material,Barksepara perros de gatos con 6 veces más margen, porque las clases genéricas (Animal,Whimper) también se activan con maullidos. El grupo canino se calcula solo como contexto en el aviso. - Avisa como máximo una vez cada
BARK_COOLDOWNsegundos, para no inundar la consola con un ladrido sostenido. - Se reconecta solo si la cámara se cae, y nunca imprime la contraseña: las excepciones de PyAV incluyen la URL completa con credenciales, así que todo el texto de error pasa por un enmascarador.
Calidad del audio, medida con BarkMeowDB (CC-BY-4.0, 46 wavs de perro y 50 de gato) pasando el audio por el códec real de la cámara con ffmpeg:
| ruta | Bark en perros | Bark en gatos | disparan (umbral 0.15) |
|---|---|---|---|
| 16 kHz (referencia) | 0.715 | 0.020 | — |
| 8 kHz A-law (cámara) | 0.616 | 0.007 (máx 0.049) | 20/20 perros, 0/20 gatos |
El recorte de banda a 4 kHz cuesta un 14% de puntuación, pero la separación entre perro y gato sigue siendo enorme, así que el audio de la cámara sirve de sobra. Coste: 4.6 ms por ventana en CPU, unas 200 veces más rápido que el tiempo real.
Cómo ajustar el umbral en tu casa: la línea
[STATS]muestraBark máxde los últimos 10 s junto al umbral. Si en una semana de uso ves que se acerca al umbral sin que ladre nadie, súbelo conBARK_THRESHOLDen el.env. Si se le escapan ladridos, bájalo.Pendiente de comprobar in situ: el experimento aísla la pérdida de códec y banda, pero no la calidad del micrófono de la cámara, la distancia al animal ni la acústica de la sala. Conviene validarlo con un ladrido real delante de la cámara.
- Avisos por Telegram
Cuando salta una alerta (carrera o ladrido), el monitor manda un mensaje con la foto de la cámara anotada: la caja de la mascota detectada, su confianza y la fecha y hora. Las capturas se guardan además en
capturas/(ignorado por git).
Puesta en marcha (una sola vez):
- En Telegram, habla con @BotFather y envía
/newbot. Te devolverá un token. - Escríbele cualquier mensaje a tu bot (si no, el bot no puede ver tu chat).
- Añade el token a
~/pet_monitor/.env:TELEGRAM_BOT_TOKEN=123456:ABC... - Comprueba que todo funciona:
Ese script valida el token, deduce eluv run scripts/telegram_check.pychat_idsolo a partir del último mensaje que recibió el bot, manda un mensaje y una foto de prueba, y te dice qué línea añadir al.env(TELEGRAM_CHAT_ID) para no tener que deducirlo cada vez.
Cómo se comporta:
- Los envíos van por una cola con su propio hilo: la red nunca bloquea ni la visión ni la escucha de audio. Si Telegram va lento, se encolan.
- Si falla, reintenta 3 veces con retroceso exponencial, y respeta el
retry_afterque pide Telegram ante un límite de frecuencia. - Si la cola se llena (Telegram caído mucho rato), descarta los avisos viejos en favor de los nuevos: uno de hace cinco minutos ya no sirve.
- Anti-repetición en dos niveles: la consola agrupa avisos cada 10 s (antes un perro corriendo imprimía 14 líneas por segundo y tapaba las estadísticas) y las capturas/Telegram como mucho una cada
ALERT_COOLDOWN(60 s por defecto) y por tipo de aviso. - El token nunca se imprime: todo texto de error pasa por el enmascarador, así que en
journalctlaparecería.../bot***/sendMessage.
Variables opcionales en el .env:
# TELEGRAM_CHAT_ID=123456789 # deducido solo si no se indica
# ALERT_COOLDOWN=60 # segundos entre capturas del mismo tipo de aviso
# SNAPSHOTS=0 # no guardar las capturas en disco
# SNAPSHOT_DIR=capturas # dónde guardarlas
- Próximos Pasos (Hoja de Ruta)
Fase A: Integración de Audio (Ladridos y Sonidos) — hecha
Descartado el micrófono USB: se usa el de la propia cámara.
El modelo YAMNet ONNX y el diccionario de clases de AudioSet (
yamnet_classes.csv) están enmodels/. Listener en hilo secundario analizando ventanas de 0.975 s en busca de la claseBark: implementado enpet_audio.py. Fase B: Sistema de Alertas y Notificaciones — hecha (falta poner el token) Bot de Telegram, despachador asíncrono con control de enfriamiento y captura JPEG con la caja delimitadora: implementado enpet_telegram.pyypet_alerts.py. Fase C: Despliegue Autónomo (Systemd) Crear una unidad de servicio/etc/systemd/system/pet_monitor.service. Habilitar arranque automático al encender la Raspberry Pi:
sudo systemctl enable --now pet_monitor.service