Otro de los proyectos en los que he estado trabajando con IA generativa como elemento de apoyo ha sido en aspectos avanzados de control de la domótica. Llevaba mucho tiempo queriendo llevar a cabo un proyecto para poner en valor un Kinect v1 que había adquirido como hardware para realizar modelado 3D de figuras pero que, viendo la capacidad que tenía para obtener imágenes a color, captura de imágenes con detección de profundidad, captura infrarrojos y generación de nube de puntos, pensaba que se le podía sacar mucho partido en el ámbito de la domótica.

Cuadrante de tecnologías del proyecto. Generado con IA

En su momento había estado investigando sobre las posibilidades de uso de Libfreenect para sacar adelante este proyecto, pero no tardaba mucho en encontrarme con problemas de compatibilidad de librerías con los sistemas operativos que tenía disponible en mi entorno, por lo que no pude avanzar mucho en este proyecto. Pero, de nuevo, el hecho de disponer del nuevo servidor de virtualización me permitía poder tener múltiples sistemas en paralelo con los que jugar. Había llegado la hora de avanzar con esta idea.

Para poder evaluar la viabilidad del proyecto, empecé por hacer una búsqueda prospectiva apoyada por IA. Utilicé como motor de IA Perplexity. En seguida me dio algunas ideas interesantes, algunas de ellas con las que ya contaba (en concreto, con el uso de Libfreenect), pero ya desde el primer momento me dio un punto de partida muy interesante, y que yo no había contemplado: el uso de ROS, plataforma de software para robótica, como elemento central de la arquitectura. En concreto, proponía:

  • Uso OpenNI como elemento de para la gestión de la visión artificial, con acceso a los flujos de datos en bruto del Kinect. OpenNI ofrece una capa más alta de abstracción y procesamiento, con middleware para reconocimiento de movimiento, detección de esqueleto, reconocimiento de gestos, y soporta múltiples dispositivos además de Kinect.
  • Libfreenect: Drivers para proporcionar acceso básico a los sensores Kinect.
  • ROS como plataforma de gestión de robótica, que proporciona las herramientas y librerías para el manejo de datos del Kinect proporcionados por OpenNI, y que permite su explotación mediante la integración con otros algoritmos y sistemas robóticos. Una parte muy interesante es que se apoya en el contecto de topics para el acceso a estos datos y su integración, un concepto muy similar al que se maneja con MQTT.
  • Pyfreenect, como librería de acceso en python, para la explotación de los datos.
Kinect v1 con el cableado para su conexión a PC

Como este punto de partida parecía muy prometedor, en seguida me puse a ver cómo llevarlo a cabo. Y pronto volví a encontrarme con un problema habitual: la obsolescencia del Kinect. La mayoría de los desarrollos para este hardware llevaban al menos 5 años abandonados. Y esto era un problema importante por varios motivos:

  • Existen varias versiones de ROS; actualmente la versión predominante es ROS 2, pero no existe un driver oficial, mantenido y bien documentado para Kinect v1 en ROS 2 como sí lo hubo en ROS 1 (openni_camera, freenect_launch, etc.).
  • Repositorios como kinect_ros2 existen pero son no oficiales, con problemas de dependencia (rosdep no encuentra libfreenect) y mantenimiento limitado.
  • OpenNI/OpenNI2 ya no se mantiene activamente, y la integración “oficial” con Kinect se hacía a través de drivers adicionales (OpenNI-FreenectDriver o SDK de Microsoft), no de forma directa.
  • En ROS 1, los paquetes openni_* funcionaban razonablemente con Ubuntu/ROS antiguos (p. ej. Kinetic, 16.04), pero esa pila no se ha portado ni actualizado a ROS 2 de forma completa.
  • libfreenect como biblioteca es “agnóstica a ROS”, pero su integración exige compilarla a mano, instalarla en rutas de sistema y luego escribir o adaptar un nodo ROS 2 que publique las imágenes y percepción de la profundidad.
  • El seguimiento esquelético con Kinect v1 sobre OpenNI se apoya en NiTE, un middleware binario de PrimeSense que proporciona skeleton tracking, gestos y manos, no en OpenNI “puro”.
  • NiTE está discontinuado, es cerrado y no se distribuye ni mantiene oficialmente; muchas guías se limitan a ROS 1 (openni_tracker) en Ubuntu antiguos y ya no tienen soporte activo.
  • El paquete clásico openni_tracker que usa NiTE para publicar esqueletos vía tf existe solo para ROS 1; no hay equivalente mantenido para ROS 2, por lo que no dispones de un nodo listo para usar que publique joints esqueléticos.
  • La mayoría de hilos recientes sobre Kinect + ROS 2 se centran en sacar color y profundidad (RGB-D) con libfreenect/OpenNI, pero no tratan ni ofrecen soluciones maduras para skeleton tracking en ROS 2.

Es decir, me estaba encontrando con un problema de obsolescencia, tanto en la parte hardware como en la parte software, sobre todo si quería hacer uso de las vías que más trabajadas han estado de manera habitual. En condiciones normales, a estas alturas habría abandonado el proyecto, pero valía la pena intentar sacarle partido a este hardware tan interesante.

Divide et impera. Cómo abordar el problema

Es decir, llegados a este punto, me tenía que enfrentar con tres problemas: obsolescencia del hardware, obsolescencia del software de adquisición de datos y de control y obsolescencia del software de explotación:

  • Obsolescencia del hardware: Directamente aquí no había nada que hacer. Quería utilizar este hardware. Pero sí nos obligaba a tomar una serie de decisiones. Si quería hacer uso del mismo, tocaba prescindir de ROS 2, y hacer uso de ROS 1.
  • Obsolescencia del software de control: El camino venía dado por lo anterior. Si iba a usar ROS 1, necesitaba encontrar un sistema operativo sobre el que lo pudiera desplegar y, sobre todo, que fuera compatible con Kinect v1. En este caso, vino a salvarme un repositorio de GitHub en el que daban instrucciones precisas de cómo configurar Kinect v1 para su correcto funcionamiento con ROS Noetic: Setup Kinect in ROS Noetic. Ya tenía mi punto de partida desde el punto de vista del software de control. Con ROS Noetic desplegado sobre una Ubuntu 20.04, que aún cuenta con soporte a largo plazo, tenía un sistema lo suficientemente sólido como para plantearme emprender el sistema. Eso sí, teniendo claro que es una solución viable sólo a medio plazo.
  • Obsolescencia del software de explotación: Aquí sí que iba a tener que enfrentar un problema inmediato. Daba igual las vueltas que le diera, que OpenNI y NiTE no funcionaban: no existe una versión 2 compatible con Kinect v1, y la 1.5 daba problemas de todo tipo, algo de esperar, debido a que lleva un tiempo abandonado. Era preciso abordar el problema de una manera diferente. Y aquí, la IA generativa tuvo algo nuevo que decir.

Puesta en marcha de Kinect v1 en ROS Noetic

La guía anterior explica, de manera detallada, cómo poner en marcha un Kinect v1 en ROS Noetic sobre Ubuntu 20.04, desde las dependencias hasta la visualización en RViz:

  • El tutorial parte de un sistema con Ubuntu 20.04 y ROS Noetic ya instalado, verificando la versión de Ubuntu con lsb_release -a y la distro de ROS en /opt/ros.​
  • Primero se actualiza el sistema con sudo apt update y sudo apt upgrade para tener todos los paquetes al día.
  • Después se instalan los metapaquetes ros-noetic-rgbd-launch, ros-noetic-openni-launch y la librería de desarrollo libfreenect-dev, que proporcionan la base para trabajar con sensores RGB-D y el Kinect.
  • Como el paquete freenect_stack no está disponible precompilado para Noetic, la guía indica crear un workspace catkin_ws, clonar el repositorio oficial ros-drivers/freenect_stack dentro de src y compilarlo con catkin_make.
  • Una vez construido, se ejecuta source ~/catkin_ws/devel/setup.bash para que ROS detecte los nuevos paquetes en el entorno de trabajo.
  • ​Con el workspace cargado, se vuelve a hacer source del setup, se conecta el Kinect y se comprueba que el sistema lo detecta con lsusb.
  • A continuación se lanza roslaunch freenect_launch freenect.launch depth_registration:=true para obtener una nube de puntos RGB-D y, en otra terminal, se abre rviz, configurando el Fixed Frame en camera_link y añadiendo un PointCloud2 en el tópico /camera/depth_registered/points para ver los datos en 3D.

Con ello, ya teníamos solucionados los problemas de obsolescencia del hardware y del software de control. Quedaba el problema de cómo explotar los datos.

MediaPipe como solución de reconocimiento de pose

De nuevo apoyándome en Perplexity, busqué soluciones alternativas, viables en 2025, para reemplazar a NiTE como solución de reconocimiento esquelético, y entre las distintas posibilidades que me ofrecía, una de ellas llamó pronto mi atención: MediaPipe de Google:

  • MediaPipe es una solución avanzada y moderna de reconocimiento de pose y skeleton tracking basada en machine learning.
  • Funciona con cámaras RGB normales y puede adaptarse con ajustes para Kinect v1.
  • Existe un wrapper para ROS y puedes integrar fácilmente sus datos para control domótico.
  • Es multiplataforma, con soporte para Python, C++, y buena documentación.
MediaPipe y Google

Es decir, una solución moderna, activa, con soporte, y que integra capacidades de ML para el reconocimiento de poses. Y que es una solución que, si en el futuro decido prescindir del Kinect v1, podré seguir usando con hardware más moderno. Lo tenía todo.

Poniéndolo todo junto

Es decir, teníamos un proyecto con los siguientes puntales:

  • Kinect (Sensor): Es el hardware, una cámara de detección de profundidad (RGB-D) desarrollada por Microsoft. El Kinect v1, en particular, es compatible con el estándar OpenNI y es ampliamente utilizado con ROS para obtener datos de color, profundidad y nubes de puntos.
  • OpenNI (Framework/Controlador): Es un framework de interacción natural (NITE) y un conjunto de controladores (drivers) de código abierto que permiten la comunicación entre el software y el hardware compatible, como el Kinect y las cámaras PrimeSense. Proporciona acceso a flujos de datos brutos (imágenes RGB e IR, mapas de profundidad) y, en versiones anteriores, a funciones de seguimiento esquelético. En ROS, los paquetes openni_camera y openni_launch utilizan estos controladores.
  • ROS (Robot Operating System): Es una meta-plataforma de software para robótica. Proporciona las herramientas y librerías para manejar los datos del Kinect (publicados como tópicos de ROS mediante OpenNI) y permite la integración con otros algoritmos y sistemas robóticos (como la navegación o manipulación). Permite la comunicación entre diferentes módulos de software, por ejemplo, pasar la imagen de la cámara a un nodo de procesamiento de MediaPipe.
  • MediaPipe (Librería de visión por computadora): Es una librería de Google (ahora parte de TensorFlow) que proporciona soluciones de visión por computadora listas para usar, como seguimiento de manos, rostros y poses (esqueletos). MediaPipe opera sobre las imágenes (normalmente RGB) obtenidas del sensor (Kinect) y procesadas a través de ROS. Existen paquetes en ROS 2 (y probablemente adaptaciones en ROS 1) que integran directamente MediaPipe para publicar los datos de seguimiento como tópicos de ROS.
  • MQTT: Plataforma de mensajería para la integración con el sistema de domótica.

Una propuesta muy, muy potente. Y era hora de avanzar con ella.

  • Sensor Kinect v1 conectado.
  • Driver libre que publique RGB: freenect_launch disponible en ROS para Kinect.
  • Instalar MediaPipe y OpenCV para Python:
pip3 install mediapipe opencv-python
sudo apt install ros-noetic-cv-bridge ros-noetic-image-transport
  • Compilar el workspace catkin y fuente el entorno:
chmod +x mediapipe_pose_node.py
cd ~/catkin_ws
catkin_make
source devel/setup.bash

Tras ello, ya era posible desarrollar un aplicativo que explotara ROS, que procesara los movimientos con MediaPipe, y que volcara los hallazgos a un topic MQTT.

Reconocimiento de gestos. MediaPipe y Python

Este es el punto donde entraba juego MediaPipe Pose. Este software permite detectar 33 puntos de referencia en el cuerpo, lo que hace posible reconocer diversas posturas y gestos complejos, como por ejemplo:

  • Posturas básicas: De pie, sentado, agachado, acostado.
  • Gestos de brazos y manos:
    • Brazo levantado (derecho o izquierdo).
    • Mano abierta o cerrada.
    • Dedo índice apuntando hacia arriba.
    • Pulgar arriba o abajo.
    • Señal de «victoria» (dos dedos en V).
    • Gestos personalizados definidos por posición relativa de puntos clave.
  • Movimientos de piernas:
    • Levantar una pierna.
    • Caminar, correr, salto.
  • Inclinación o giro del torso y cabeza:
    • Inclinarse hacia adelante o atrás.
    • Girar la cabeza hacia un lado.
  • Análisis de postura para ergonomía o rehabilitación:
    • Detectar encorvamiento o postura erguida.
    • Seguimiento y corrección de ejercicios.
  • Gestos comunes predefinidos en MediaPipe Hands:
    • Puño cerrado, palma abierta, señal de amor, etc.
Reconocimiento esquelético

Estos gestos y posturas se pueden analizar con reglas geométricas, diferencias de ángulos entre articulaciones o mediante modelos de aprendizaje automático que clasifiquen patrones en los 33 puntos 3D. Más que de sobra para mi proyecto. En mi caso, me centré en reconocer gestos básicos como la presencia o ausencia de una persona, levantar los brazos, o unir las manos por encima de la cabeza. Y la idea sería volcar a un topic MQTT, para utilizarlos posteriormente para controlar la domótica.

Para ello, pedí a Perplexity que desarrollara un código básico con estos detalles. Y el resultado, tras refinarlo un poco, empezó a funcionar extraordinariamente bien. Adjunto el código en python desarrollado:

#!/usr/bin/env python3
import rospy
from sensor_msgs.msg import Image
from geometry_msgs.msg import PointStamped
from cv_bridge import CvBridge
import mediapipe as mp
import cv2
import paho.mqtt.client as mqtt
import time
import threading

class MediaPipePoseNode:

    def __init__(self):
        rospy.init_node('mediapipe_pose_node', anonymous=True)
        self.bridge = CvBridge()
        self.pose = mp.solutions.pose.Pose(min_detection_confidence=0.5)

        self.pub_right_hand = rospy.Publisher('/mediapipe/right_hand', PointStamped, queue_size=10)
        self.pub_left_hand = rospy.Publisher('/mediapipe/left_hand', PointStamped, queue_size=10)

        rospy.Subscriber('/camera/rgb/image_raw', Image, self.image_callback)

        self.mqtt_client = mqtt.Client()
        self.mqtt_client.connect("localhost")

        # Inicia loop no bloqueante MQTT en segundo plano
        self.mqtt_client.loop_start()

        self.mqtt_topic = "domotica/brazo_presencia"

        self.threshold_y = 0.5  # Umbral para brazo levantado
        self.hand_join_threshold = 0.1  # Distancia XY para manos juntas
        self.last_publish_time = 0
        self.publish_interval = 1.0  # mínimo 1 segundo entre mensajes
        self.last_processed_time = 0
        self.process_interval = 0.1  # Procesar máximo 10 FPS

        self.person_detected = False

        rospy.loginfo("MediaPipe Pose Node with MQTT started")
        rospy.spin()

    def distance_xy(self, lm1, lm2):
        return ((lm1.x - lm2.x)**2 + (lm1.y - lm2.y)**2)**0.5

    def image_callback(self, msg):
        try:
            current_time = time.time()
            # Limita tasa de procesamiento
            if current_time - self.last_processed_time < self.process_interval:
                return
            self.last_processed_time = current_time

            cv_image = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
            results = self.pose.process(cv_image)

            if results.pose_landmarks:
                if not self.person_detected:
                    rospy.loginfo("Persona detectada")
                    self.mqtt_client.publish(self.mqtt_topic, "PERSONA_DETECTADA")
                    self.person_detected = True
                    self.last_publish_time = current_time

                right_hand = results.pose_landmarks.landmark[mp.solutions.pose.PoseLandmark.RIGHT_WRIST]
                left_hand = results.pose_landmarks.landmark[mp.solutions.pose.PoseLandmark.LEFT_WRIST]
                nose = results.pose_landmarks.landmark[mp.solutions.pose.PoseLandmark.NOSE]

                point_right = PointStamped(header=msg.header)
                point_right.point.x = right_hand.x
                point_right.point.y = right_hand.y
                point_right.point.z = right_hand.z
                self.pub_right_hand.publish(point_right)

                point_left = PointStamped(header=msg.header)
                point_left.point.x = left_hand.x
                point_left.point.y = left_hand.y
                point_left.point.z = left_hand.z
                self.pub_left_hand.publish(point_left)

                right_up = right_hand.y < self.threshold_y
                left_up = left_hand.y < self.threshold_y
                hands_together_above_head = (self.distance_xy(right_hand, left_hand) < self.hand_join_threshold) and \
                                            (right_hand.y < nose.y) and (left_hand.y < nose.y)

                if current_time - self.last_publish_time > self.publish_interval:
                    if right_up and left_up:
                        rospy.loginfo("Ambos brazos levantados")
                        self.mqtt_client.publish(self.mqtt_topic, "AMBOS_BRAZOS_LEVANTADOS")
                        self.last_publish_time = current_time
                    elif hands_together_above_head:
                        rospy.loginfo("Manos unidas encima cabeza")
                        self.mqtt_client.publish(self.mqtt_topic, "MANOS_UNIDAS_ENCIMA_CABEZA")
                        self.last_publish_time = current_time
                    elif right_up:
                        rospy.loginfo("Brazo derecho levantado")
                        self.mqtt_client.publish(self.mqtt_topic, "BRAZO_DERECHO_LEVANTADO")
                        self.last_publish_time = current_time
                    elif left_up:
                        rospy.loginfo("Brazo izquierdo levantado")
                        self.mqtt_client.publish(self.mqtt_topic, "BRAZO_IZQUIERDO_LEVANTADO")
                        self.last_publish_time = current_time

            else:
                if self.person_detected:
                    rospy.loginfo("Fin de detección persona")
                    self.mqtt_client.publish(self.mqtt_topic, "FIN_DETECCION_PERSONA")
                    self.person_detected = False
                    self.last_publish_time = current_time

        except Exception as e:
            rospy.logwarn(f"Error en procesamiento: {e}")

if __name__ == '__main__':
    try:
        MediaPipePoseNode()
    except rospy.ROSInterruptException:
        pass

Automatización de la plataforma

El siguiente paso era automatizar el inicio del software. Para ello, opté por utilizar systemd, pero desplegando dos servicios separados:

  • Un servicio para el componente ROS que lance roslaunch freenect_launch freenect.launch depth_registration:=true.
  • Otro servicio para el nodo Python mediapipe_pose_node.py que asume que ROS ya está corriendo.

…el primero con el siguiente servicio:

[Unit]
Description=ROS Freenect Launch
After=network.target

[Service]
Type=simple
User=tu_usuario
Environment="HOME=/home/tu_usuario"
ExecStart=/bin/bash -c 'source /home/tu_usuario/catkin_ws/devel/setup.bash && roslaunch freenect_launch freenect.launch depth_registration:=true'
Restart=on-failure
RestartSec=5s
WorkingDirectory=/home/tu_usuario

[Install]
WantedBy=multi-user.target

…y el segundo con este otro:

[Unit]
Description=MediaPipe Pose ROS Node
After=freenect.launch.service

[Service]
Type=simple
User=tu_usuario
Environment="HOME=/home/tu_usuario"
ExecStart=/bin/bash -c 'source /home/tu_usuario/catkin_ws/devel/setup.bash && rosrun mediapipe_pose mediapipe_pose_node.py'
Restart=on-failure
RestartSec=5s
WorkingDirectory=/home/tu_usuario/catkin_ws

[Install]
WantedBy=multi-user.target

Captura de los detalles de la suscripción al topic MQTT de control domótico

Siguientes pasos

El proyecto, hasta ahora, está muy bien, pero tiene una serie de puntos de mejora a tratar:

  • Todo el procesado se realiza en una máquina virtual en mi servidor de virtualización, por lo que el Kinect tiene que estar conectado a este último. Como éste es un ProLiant DL360, no es que pueda tenerlo precisamente en el salón de casa, que es lo ideal para poder hacer detección de movimientos y control domótico. La idea pasa por conectar el Kinect a un sistema más sencillo, que contenga la parte básica de ROS, y crear un sistema maestro/esclavo, algo que ROS soporta perfectamente.
  • Ampliar el tipo de gestos reconocidos por el sistema. He empezado por algunos básicos, pero las posibilidades son inmensas.
  • Utilizar otros canales para el reconocimiento de imagen. Actualmente hago uso del RGB, pero sería muy interesante poder usar el espectro infrarrojo, o el reconocimiento de puntos.
  • Explorar otro tipo de hardware más moderno.
  • Explorar la utilización de ROS 2, para evitar la obsolescencia de ROS 1.
Flujo de comunicaciones en ROS

Como se puede ver, hay ramificaciones para rato.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.