{"id":11361,"date":"2025-12-07T13:02:40","date_gmt":"2025-12-07T12:02:40","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11361"},"modified":"2025-12-14T08:49:37","modified_gmt":"2025-12-14T07:49:37","slug":"integracion-de-sistemas-con-ia-generativa-deteccion-de-movimientos-como-control-domotico","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11361","title":{"rendered":"Integraci\u00f3n de sistemas con IA generativa: Detecci\u00f3n de movimientos como control dom\u00f3tico"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 1 de 3 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=deteccion-de-movimientos-con-ia-como-control-domotico\" class=\"series-1896\" title=\"Detecci\u00f3n de movimientos con IA como control dom\u00f3tico\">Detecci\u00f3n de movimientos con IA como control dom\u00f3tico<\/a><\/div>\n<p>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\u00f3tica. Llevaba mucho tiempo queriendo llevar a cabo un proyecto para poner en valor un Kinect v1 que hab\u00eda adquirido como <em>hardware<\/em> para realizar modelado 3D de figuras pero que, viendo la capacidad que ten\u00eda para obtener im\u00e1genes a color, captura de im\u00e1genes con detecci\u00f3n de profundidad, captura infrarrojos y generaci\u00f3n de nube de puntos, pensaba que se le pod\u00eda sacar mucho partido en el \u00e1mbito de la dom\u00f3tica.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"960\" height=\"960\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/11\/proyecto-kinect.jpg\" alt=\"\" class=\"wp-image-11163\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/11\/proyecto-kinect.jpg 960w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/11\/proyecto-kinect-300x300.jpg 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/11\/proyecto-kinect-150x150.jpg 150w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/11\/proyecto-kinect-768x768.jpg 768w\" sizes=\"auto, (max-width: 960px) 100vw, 960px\" \/><figcaption class=\"wp-element-caption\">Cuadrante de tecnolog\u00edas del proyecto. Generado con IA<\/figcaption><\/figure>\n\n\n\n<p>En su momento hab\u00eda estado investigando sobre las posibilidades de uso de <em>Libfreenect<\/em> para sacar adelante este proyecto, pero no tardaba mucho en encontrarme con problemas de compatibilidad de librer\u00edas con los sistemas operativos que ten\u00eda 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\u00f3n me permit\u00eda poder tener m\u00faltiples sistemas en paralelo con los que jugar. Hab\u00eda llegado la hora de avanzar con esta idea.<\/p>\n\n\n\n<p>Para poder evaluar la viabilidad del proyecto, empec\u00e9 por hacer una b\u00fasqueda prospectiva apoyada por IA. Utilic\u00e9 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\u00eda contemplado: el uso de <a href=\"https:\/\/www.ros.org\/\" target=\"_blank\" rel=\"noreferrer noopener\">ROS<\/a>, plataforma de software para rob\u00f3tica, como elemento central de la arquitectura. En concreto, propon\u00eda:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Uso <strong>OpenNI como elemento de para la gesti\u00f3n de la visi\u00f3n artificial,<\/strong> con acceso a los flujos de datos en bruto del Kinect. OpenNI ofrece una capa m\u00e1s alta de abstracci\u00f3n y procesamiento, con middleware para reconocimiento de movimiento, detecci\u00f3n de esqueleto, reconocimiento de gestos, y soporta m\u00faltiples dispositivos adem\u00e1s de Kinect.<\/li>\n\n\n\n<li><strong>Libfreenect<\/strong>: Drivers para proporcionar acceso b\u00e1sico a los sensores Kinect.<\/li>\n\n\n\n<li><strong>ROS<\/strong> como plataforma de gesti\u00f3n de rob\u00f3tica, que proporciona las herramientas y librer\u00edas para el manejo de datos del Kinect proporcionados por OpenNI, y que permite su explotaci\u00f3n mediante la integraci\u00f3n con otros algoritmos y sistemas rob\u00f3ticos. Una parte muy interesante es que se apoya en el contecto de <em>topics<\/em> para el acceso a estos datos y su integraci\u00f3n, un concepto muy similar al que se maneja con MQTT.<\/li>\n\n\n\n<li><strong>Pyfreenect<\/strong>, como librer\u00eda de acceso en python, para la explotaci\u00f3n de los datos.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"957\" height=\"487\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/kinect-v1-1.webp\" alt=\"\" class=\"wp-image-11366\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/kinect-v1-1.webp 957w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/kinect-v1-1-300x153.webp 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/kinect-v1-1-768x391.webp 768w\" sizes=\"auto, (max-width: 957px) 100vw, 957px\" \/><figcaption class=\"wp-element-caption\">Kinect v1 con el cableado para su conexi\u00f3n a PC<\/figcaption><\/figure>\n\n\n\n<p>Como este punto de partida parec\u00eda muy prometedor, en seguida me puse a ver c\u00f3mo llevarlo a cabo. Y pronto volv\u00ed a encontrarme con un problema habitual: la obsolescencia del Kinect. La mayor\u00eda de los desarrollos para este <em>hardware<\/em> llevaban al menos 5 a\u00f1os abandonados. Y esto era un problema importante por varios motivos:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Existen v<strong>arias versiones de ROS<\/strong>; actualmente la versi\u00f3n predominante es ROS 2, pero no existe un driver oficial, mantenido y bien documentado para Kinect v1 en ROS 2 como s\u00ed lo hubo en ROS 1 (openni_camera, freenect_launch, etc.).<\/li>\n\n\n\n<li>Repositorios como <strong><code>kinect_ros2<\/code> existen pero son no oficiales<\/strong>, con problemas de dependencia (rosdep no encuentra libfreenect) y mantenimiento limitado.<\/li>\n\n\n\n<li><strong>OpenNI\/OpenNI2 ya no se mantiene activamente,<\/strong> y la integraci\u00f3n \u201coficial\u201d con Kinect se hac\u00eda a trav\u00e9s de drivers adicionales (OpenNI-FreenectDriver o SDK de Microsoft), no de forma directa.<\/li>\n\n\n\n<li>En ROS 1, los paquetes openni_* funcionaban razonablemente con Ubuntu\/ROS antiguos (p. ej. Kinetic, 16.04), pero esa <strong>pila no se ha portado ni actualizado a ROS 2 <\/strong>de forma completa.<\/li>\n\n\n\n<li>libfreenect como biblioteca es \u201cagn\u00f3stica a ROS\u201d, pero su <strong>integraci\u00f3n exige compilarla a mano<\/strong>, instalarla en rutas de sistema y luego escribir o adaptar un nodo ROS 2 que publique las im\u00e1genes y percepci\u00f3n de la profundidad.<\/li>\n\n\n\n<li>El <strong>seguimiento esquel\u00e9tico con Kinect v1 sobre OpenNI se apoya en NiTE,<\/strong> un <em>middleware<\/em> binario de PrimeSense que proporciona skeleton tracking, gestos y manos, no en OpenNI \u201cpuro\u201d.<\/li>\n\n\n\n<li>NiTE est\u00e1 discontinuado, es cerrado y no se distribuye ni mantiene oficialmente; muchas gu\u00edas se limitan a ROS 1 (openni_tracker) en Ubuntu antiguos y ya no tienen soporte activo.<a href=\"https:\/\/github.com\/abhinavtripathi95\/tutorials\/blob\/master\/Kinect-v1-for-ROS.md\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>\u200b<\/li>\n\n\n\n<li>El paquete cl\u00e1sico <code>openni_tracker<\/code> que usa NiTE para publicar esqueletos v\u00eda tf existe s<strong>olo para ROS 1<\/strong>; no hay equivalente mantenido para ROS 2, por lo que no dispones de un nodo listo para usar que publique joints esquel\u00e9ticos.<a href=\"http:\/\/wiki.ros.org\/kinect\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/li>\n\n\n\n<li>La mayor\u00eda de hilos recientes sobre Kinect + ROS 2 se centran en sacar color y profundidad (RGB-D) con libfreenect\/OpenNI, pero <strong>no tratan ni ofrecen soluciones maduras<\/strong> para skeleton tracking en ROS 2.<\/li>\n<\/ul>\n\n\n\n<p>Es decir, me estaba encontrando con un problema de obsolescencia, tanto en la parte <em>hardware<\/em> como en la parte <em>software<\/em>, sobre todo si quer\u00eda hacer uso de las v\u00edas que m\u00e1s trabajadas han estado de manera habitual. En condiciones normales, a estas alturas habr\u00eda abandonado el proyecto, pero val\u00eda la pena intentar sacarle partido a este <em>hardware<\/em> tan interesante.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><em>Divide et impera<\/em>. C\u00f3mo abordar el problema<\/h2>\n\n\n\n<p>Es decir, llegados a este punto, me ten\u00eda que enfrentar con tres problemas: obsolescencia del <em>hardware<\/em>, obsolescencia del <em>software<\/em> de adquisici\u00f3n de datos y de control y obsolescencia del <em>software<\/em> de explotaci\u00f3n:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Obsolescencia del <em>hardware<\/em>: <\/strong>Directamente aqu\u00ed no hab\u00eda nada que hacer. <em>Quer\u00eda utilizar este hardware<\/em>. Pero s\u00ed nos obligaba a tomar una serie de decisiones. Si quer\u00eda hacer uso del mismo, tocaba prescindir de ROS 2, y hacer uso de ROS 1.<\/li>\n\n\n\n<li><strong>Obsolescencia del <em>software<\/em> de control<\/strong>: El camino ven\u00eda 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\u00f3mo configurar Kinect v1 para su correcto funcionamiento con ROS Noetic: <a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\">Setup Kinect in ROS Noetic<\/a>. Ya ten\u00eda mi punto de partida desde el punto de vista del software de control. Con ROS Noetic desplegado sobre una Ubuntu 20.04, que a\u00fan cuenta con soporte a largo plazo, ten\u00eda un sistema lo suficientemente s\u00f3lido como para plantearme emprender el sistema. Eso s\u00ed, teniendo claro que es una soluci\u00f3n viable s\u00f3lo a medio plazo.<\/li>\n\n\n\n<li><strong>Obsolescencia del <em>software<\/em> de explotaci\u00f3n:<\/strong> Aqu\u00ed s\u00ed 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\u00f3n 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\u00ed, la IA generativa tuvo algo nuevo que decir.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Puesta en marcha de Kinect v1 en ROS Noetic<\/h2>\n\n\n\n<p>La gu\u00eda anterior explica, de manera detallada, c\u00f3mo poner en marcha un Kinect v1 en ROS Noetic sobre Ubuntu 20.04, desde las dependencias hasta la visualizaci\u00f3n en RViz:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El tutorial parte de un sistema con Ubuntu 20.04 y ROS Noetic ya instalado, verificando la versi\u00f3n de Ubuntu con <code>lsb_release -a<\/code> y la distro de ROS en <code>\/opt\/ros<\/code>.\u200b<\/li>\n\n\n\n<li>Primero se actualiza el sistema con <code>sudo apt update<\/code> y <code>sudo apt upgrade<\/code> para tener todos los paquetes al d\u00eda.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>\u200b<\/li>\n\n\n\n<li>Despu\u00e9s se instalan los metapaquetes <code>ros-noetic-rgbd-launch<\/code>, <code>ros-noetic-openni-launch<\/code> y la librer\u00eda de desarrollo <code>libfreenect-dev<\/code>, que proporcionan la base para trabajar con sensores RGB-D y el Kinect.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/li>\n\n\n\n<li>Como el paquete <code>freenect_stack<\/code> no est\u00e1 disponible precompilado para Noetic, la gu\u00eda indica crear un <em>workspace<\/em> <code>catkin_ws<\/code>, clonar el repositorio oficial <code>ros-drivers\/freenect_stack<\/code> dentro de <code>src<\/code> y compilarlo con <code>catkin_make<\/code>.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>\u200b<\/li>\n\n\n\n<li>Una vez construido, se ejecuta <code>source ~\/catkin_ws\/devel\/setup.bash<\/code> para que ROS detecte los nuevos paquetes en el entorno de trabajo.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a><\/li>\n\n\n\n<li>\u200bCon el <em>workspace<\/em> cargado, se vuelve a hacer <code>source<\/code> del <em>setup<\/em>, se conecta el Kinect y se comprueba que el sistema lo detecta con <code>lsusb<\/code>.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>\u200b<\/li>\n\n\n\n<li>A continuaci\u00f3n se lanza <code>roslaunch freenect_launch freenect.launch depth_registration:=true<\/code> para obtener una nube de puntos RGB-D y, en otra terminal, se abre <code>rviz<\/code>, configurando el <code>Fixed Frame<\/code> en <code>camera_link<\/code> y a\u00f1adiendo un <code>PointCloud2<\/code> en el t\u00f3pico <code>\/camera\/depth_registered\/points<\/code> para ver los datos en 3D.<a href=\"https:\/\/github.com\/Shivam-Kumar-1\/ros-noetic-kinectv1-setup\" target=\"_blank\" rel=\"noreferrer noopener\"><\/a>\u200b<\/li>\n<\/ul>\n\n\n\n<p>Con ello, ya ten\u00edamos solucionados los problemas de obsolescencia del <em>hardware<\/em> y del <em>software<\/em> de control. Quedaba el problema de c\u00f3mo explotar los datos.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">MediaPipe como soluci\u00f3n de reconocimiento de pose<\/h2>\n\n\n\n<p>De nuevo apoy\u00e1ndome en Perplexity, busqu\u00e9 soluciones alternativas, viables en 2025, para reemplazar a NiTE como soluci\u00f3n de reconocimiento esquel\u00e9tico, y entre las distintas posibilidades que me ofrec\u00eda, una de ellas llam\u00f3 pronto mi atenci\u00f3n: MediaPipe de Google:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>MediaPipe es una soluci\u00f3n avanzada y moderna de reconocimiento de pose y <em>skeleton tracking<\/em> basada en <em>machine learning<\/em>.<\/li>\n\n\n\n<li>Funciona con c\u00e1maras RGB normales y puede adaptarse con ajustes para Kinect v1.<\/li>\n\n\n\n<li>Existe un wrapper para ROS y puedes integrar f\u00e1cilmente sus datos para control dom\u00f3tico.<\/li>\n\n\n\n<li>Es multiplataforma, con soporte para Python, C++, y buena documentaci\u00f3n.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"531\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-1024x531.jpg\" alt=\"\" class=\"wp-image-11369\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-1024x531.jpg 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-300x156.jpg 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-768x398.jpg 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-1536x797.jpg 1536w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/mediapipe-google-2048x1062.jpg 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">MediaPipe y Google<\/figcaption><\/figure>\n\n\n\n<p>Es decir, una soluci\u00f3n moderna, activa, con soporte, y que integra capacidades de ML para el reconocimiento de poses. Y que es una soluci\u00f3n que, si en el futuro decido prescindir del Kinect v1, podr\u00e9 seguir usando con <em>hardware<\/em> m\u00e1s moderno. Lo ten\u00eda todo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Poni\u00e9ndolo todo junto<\/h2>\n\n\n\n<p>Es decir, ten\u00edamos un proyecto con los siguientes puntales:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Kinect (Sensor):<\/strong> Es el hardware, una c\u00e1mara de detecci\u00f3n de profundidad (RGB-D) desarrollada por Microsoft. El Kinect v1, en particular, es compatible con el est\u00e1ndar OpenNI y es ampliamente utilizado con ROS para obtener datos de color, profundidad y nubes de puntos.<\/li>\n\n\n\n<li><strong>OpenNI (Framework\/Controlador):<\/strong> Es un framework de interacci\u00f3n natural (NITE) y un conjunto de controladores (drivers) de c\u00f3digo abierto que permiten la comunicaci\u00f3n entre el software y el hardware compatible, como el Kinect y las c\u00e1maras PrimeSense. Proporciona acceso a flujos de datos brutos (im\u00e1genes RGB e IR, mapas de profundidad) y, en versiones anteriores, a funciones de seguimiento esquel\u00e9tico. En ROS, los paquetes openni_camera y openni_launch utilizan estos controladores.<\/li>\n\n\n\n<li><strong>ROS (Robot Operating System): <\/strong>Es una meta-plataforma de software para rob\u00f3tica. Proporciona las herramientas y librer\u00edas para manejar los datos del Kinect (publicados como t\u00f3picos de ROS mediante OpenNI) y permite la integraci\u00f3n con otros algoritmos y sistemas rob\u00f3ticos (como la navegaci\u00f3n o manipulaci\u00f3n). Permite la comunicaci\u00f3n entre diferentes m\u00f3dulos de software, por ejemplo, pasar la imagen de la c\u00e1mara a un nodo de procesamiento de MediaPipe.<\/li>\n\n\n\n<li> <strong>MediaPipe (Librer\u00eda de visi\u00f3n por computadora): <\/strong>Es una librer\u00eda de Google (ahora parte de TensorFlow) que proporciona soluciones de visi\u00f3n por computadora listas para usar, como seguimiento de manos, rostros y poses (esqueletos). MediaPipe opera sobre las im\u00e1genes (normalmente RGB) obtenidas del sensor (Kinect) y procesadas a trav\u00e9s 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\u00f3picos de ROS. <\/li>\n\n\n\n<li><strong>MQTT: <\/strong>Plataforma de mensajer\u00eda para la integraci\u00f3n con el sistema de dom\u00f3tica.<\/li>\n<\/ul>\n\n\n\n<p>Una propuesta muy, muy potente. Y era hora de avanzar con ella.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Sensor Kinect v1 conectado.<\/li>\n\n\n\n<li>Driver libre que publique RGB: <code>freenect_launch<\/code> disponible en ROS para Kinect.<\/li>\n\n\n\n<li>Instalar MediaPipe y OpenCV para Python:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>pip3 install mediapipe opencv-python\nsudo apt install ros-noetic-cv-bridge ros-noetic-image-transport<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Compilar el workspace catkin y fuente el entorno:<br><\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>chmod +x mediapipe_pose_node.py\ncd ~\/catkin_ws\ncatkin_make\nsource devel\/setup.bash<\/code><\/pre>\n\n\n\n<p>Tras ello, ya era posible desarrollar un aplicativo que explotara ROS, que procesara los movimientos con MediaPipe, y que volcara los hallazgos a un <em>topic<\/em> MQTT.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reconocimiento de gestos. MediaPipe y Python<\/h2>\n\n\n\n<p>Este es el punto donde entraba juego MediaPipe Pose. Este <em>software<\/em> permite detectar 33 puntos de referencia en el cuerpo, lo que hace posible reconocer diversas posturas y gestos complejos, como por ejemplo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Posturas b\u00e1sicas<\/strong>: De pie, sentado, agachado, acostado.<\/li>\n\n\n\n<li><strong>Gestos de brazos y manos<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Brazo levantado (derecho o izquierdo).<\/li>\n\n\n\n<li>Mano abierta o cerrada.<\/li>\n\n\n\n<li>Dedo \u00edndice apuntando hacia arriba.<\/li>\n\n\n\n<li>Pulgar arriba o abajo.<\/li>\n\n\n\n<li>Se\u00f1al de \u00abvictoria\u00bb (dos dedos en V).<\/li>\n\n\n\n<li>Gestos personalizados definidos por posici\u00f3n relativa de puntos clave.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Movimientos de piernas<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Levantar una pierna.<\/li>\n\n\n\n<li>Caminar, correr, salto.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Inclinaci\u00f3n o giro del torso y cabeza<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Inclinarse hacia adelante o atr\u00e1s.<\/li>\n\n\n\n<li>Girar la cabeza hacia un lado.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>An\u00e1lisis de postura para ergonom\u00eda o rehabilitaci\u00f3n<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Detectar encorvamiento o postura erguida.<\/li>\n\n\n\n<li>Seguimiento y correcci\u00f3n de ejercicios.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Gestos comunes predefinidos en MediaPipe Hands<\/strong>:\n<ul class=\"wp-block-list\">\n<li>Pu\u00f1o cerrado, palma abierta, se\u00f1al de amor, etc.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"640\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/What_is_Gesture_Technology_3-1024x640.webp\" alt=\"\" class=\"wp-image-11367\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/What_is_Gesture_Technology_3-1024x640.webp 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/What_is_Gesture_Technology_3-300x187.webp 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/What_is_Gesture_Technology_3-768x480.webp 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/What_is_Gesture_Technology_3.webp 1266w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Reconocimiento esquel\u00e9tico<\/figcaption><\/figure>\n\n\n\n<p>Estos gestos y posturas se pueden analizar con reglas geom\u00e9tricas, diferencias de \u00e1ngulos entre articulaciones o mediante modelos de aprendizaje autom\u00e1tico que clasifiquen patrones en los 33 puntos 3D. M\u00e1s que de sobra para mi proyecto. En mi caso, me centr\u00e9 en reconocer gestos b\u00e1sicos como la presencia o ausencia de una persona, levantar los brazos, o unir las manos por encima de la cabeza. Y la idea ser\u00eda volcar a un <em>topic<\/em> MQTT, para utilizarlos posteriormente para controlar la dom\u00f3tica.<\/p>\n\n\n\n<p>Para ello, ped\u00ed a Perplexity que desarrollara un c\u00f3digo b\u00e1sico con estos detalles. Y el resultado, tras refinarlo un poco, empez\u00f3 a funcionar extraordinariamente bien. Adjunto el c\u00f3digo en python desarrollado:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/usr\/bin\/env python3\nimport rospy\nfrom sensor_msgs.msg import Image\nfrom geometry_msgs.msg import PointStamped\nfrom cv_bridge import CvBridge\nimport mediapipe as mp\nimport cv2\nimport paho.mqtt.client as mqtt\nimport time\nimport threading\n\nclass MediaPipePoseNode:\n\n    def __init__(self):\n        rospy.init_node('mediapipe_pose_node', anonymous=True)\n        self.bridge = CvBridge()\n        self.pose = mp.solutions.pose.Pose(min_detection_confidence=0.5)\n\n        self.pub_right_hand = rospy.Publisher('\/mediapipe\/right_hand', PointStamped, queue_size=10)\n        self.pub_left_hand = rospy.Publisher('\/mediapipe\/left_hand', PointStamped, queue_size=10)\n\n        rospy.Subscriber('\/camera\/rgb\/image_raw', Image, self.image_callback)\n\n        self.mqtt_client = mqtt.Client()\n        self.mqtt_client.connect(\"localhost\")\n\n        # Inicia loop no bloqueante MQTT en segundo plano\n        self.mqtt_client.loop_start()\n\n        self.mqtt_topic = \"domotica\/brazo_presencia\"\n\n        self.threshold_y = 0.5  # Umbral para brazo levantado\n        self.hand_join_threshold = 0.1  # Distancia XY para manos juntas\n        self.last_publish_time = 0\n        self.publish_interval = 1.0  # m\u00ednimo 1 segundo entre mensajes\n        self.last_processed_time = 0\n        self.process_interval = 0.1  # Procesar m\u00e1ximo 10 FPS\n\n        self.person_detected = False\n\n        rospy.loginfo(\"MediaPipe Pose Node with MQTT started\")\n        rospy.spin()\n\n    def distance_xy(self, lm1, lm2):\n        return ((lm1.x - lm2.x)**2 + (lm1.y - lm2.y)**2)**0.5\n\n    def image_callback(self, msg):\n        try:\n            current_time = time.time()\n            # Limita tasa de procesamiento\n            if current_time - self.last_processed_time &lt; self.process_interval:\n                return\n            self.last_processed_time = current_time\n\n            cv_image = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')\n            results = self.pose.process(cv_image)\n\n            if results.pose_landmarks:\n                if not self.person_detected:\n                    rospy.loginfo(\"Persona detectada\")\n                    self.mqtt_client.publish(self.mqtt_topic, \"PERSONA_DETECTADA\")\n                    self.person_detected = True\n                    self.last_publish_time = current_time\n\n                right_hand = results.pose_landmarks.landmark&#91;mp.solutions.pose.PoseLandmark.RIGHT_WRIST]\n                left_hand = results.pose_landmarks.landmark&#91;mp.solutions.pose.PoseLandmark.LEFT_WRIST]\n                nose = results.pose_landmarks.landmark&#91;mp.solutions.pose.PoseLandmark.NOSE]\n\n                point_right = PointStamped(header=msg.header)\n                point_right.point.x = right_hand.x\n                point_right.point.y = right_hand.y\n                point_right.point.z = right_hand.z\n                self.pub_right_hand.publish(point_right)\n\n                point_left = PointStamped(header=msg.header)\n                point_left.point.x = left_hand.x\n                point_left.point.y = left_hand.y\n                point_left.point.z = left_hand.z\n                self.pub_left_hand.publish(point_left)\n\n                right_up = right_hand.y &lt; self.threshold_y\n                left_up = left_hand.y &lt; self.threshold_y\n                hands_together_above_head = (self.distance_xy(right_hand, left_hand) &lt; self.hand_join_threshold) and \\\n                                            (right_hand.y &lt; nose.y) and (left_hand.y &lt; nose.y)\n\n                if current_time - self.last_publish_time > self.publish_interval:\n                    if right_up and left_up:\n                        rospy.loginfo(\"Ambos brazos levantados\")\n                        self.mqtt_client.publish(self.mqtt_topic, \"AMBOS_BRAZOS_LEVANTADOS\")\n                        self.last_publish_time = current_time\n                    elif hands_together_above_head:\n                        rospy.loginfo(\"Manos unidas encima cabeza\")\n                        self.mqtt_client.publish(self.mqtt_topic, \"MANOS_UNIDAS_ENCIMA_CABEZA\")\n                        self.last_publish_time = current_time\n                    elif right_up:\n                        rospy.loginfo(\"Brazo derecho levantado\")\n                        self.mqtt_client.publish(self.mqtt_topic, \"BRAZO_DERECHO_LEVANTADO\")\n                        self.last_publish_time = current_time\n                    elif left_up:\n                        rospy.loginfo(\"Brazo izquierdo levantado\")\n                        self.mqtt_client.publish(self.mqtt_topic, \"BRAZO_IZQUIERDO_LEVANTADO\")\n                        self.last_publish_time = current_time\n\n            else:\n                if self.person_detected:\n                    rospy.loginfo(\"Fin de detecci\u00f3n persona\")\n                    self.mqtt_client.publish(self.mqtt_topic, \"FIN_DETECCION_PERSONA\")\n                    self.person_detected = False\n                    self.last_publish_time = current_time\n\n        except Exception as e:\n            rospy.logwarn(f\"Error en procesamiento: {e}\")\n\nif __name__ == '__main__':\n    try:\n        MediaPipePoseNode()\n    except rospy.ROSInterruptException:\n        pass\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Automatizaci\u00f3n de la plataforma<\/h2>\n\n\n\n<p>El siguiente paso era automatizar el inicio del <em>software<\/em>. Para ello, opt\u00e9 por utilizar <em>systemd,<\/em> pero desplegando dos servicios separados:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Un servicio para el componente ROS que lance <code>roslaunch freenect_launch freenect.launch depth_registration:=true<\/code>.<\/li>\n\n\n\n<li>Otro servicio para el nodo Python <code>mediapipe_pose_node.py<\/code> que asume que ROS ya est\u00e1 corriendo.<\/li>\n<\/ul>\n\n\n\n<p>&#8230;el primero con el siguiente servicio:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=ROS Freenect Launch\nAfter=network.target\n\n&#91;Service]\nType=simple\nUser=tu_usuario\nEnvironment=\"HOME=\/home\/tu_usuario\"\nExecStart=\/bin\/bash -c 'source \/home\/tu_usuario\/catkin_ws\/devel\/setup.bash &amp;&amp; roslaunch freenect_launch freenect.launch depth_registration:=true'\nRestart=on-failure\nRestartSec=5s\nWorkingDirectory=\/home\/tu_usuario\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p>&#8230;y el segundo con este otro:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&#91;Unit]\nDescription=MediaPipe Pose ROS Node\nAfter=freenect.launch.service\n\n&#91;Service]\nType=simple\nUser=tu_usuario\nEnvironment=\"HOME=\/home\/tu_usuario\"\nExecStart=\/bin\/bash -c 'source \/home\/tu_usuario\/catkin_ws\/devel\/setup.bash &amp;&amp; rosrun mediapipe_pose mediapipe_pose_node.py'\nRestart=on-failure\nRestartSec=5s\nWorkingDirectory=\/home\/tu_usuario\/catkin_ws\n\n&#91;Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" width=\"539\" height=\"1024\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-539x1024.jpg\" alt=\"\" class=\"wp-image-11364\" style=\"aspect-ratio:0.5263637489398191;width:345px;height:auto\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-539x1024.jpg 539w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-158x300.jpg 158w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-768x1460.jpg 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-808x1536.jpg 808w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-1077x2048.jpg 1077w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037-1024x1946.jpg 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/IMG_20251117_153037.jpg 1079w\" sizes=\"auto, (max-width: 539px) 100vw, 539px\" \/><figcaption class=\"wp-element-caption\">Captura de los detalles de la suscripci\u00f3n al <em>topic<\/em> MQTT de control dom\u00f3tico<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Siguientes pasos<\/h2>\n\n\n\n<p>El proyecto, hasta ahora, est\u00e1 muy bien, pero tiene una serie de puntos de mejora a tratar:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Todo el procesado se realiza en una m\u00e1quina virtual en mi servidor de virtualizaci\u00f3n, por lo que el Kinect tiene que estar conectado a este \u00faltimo. Como \u00e9ste es un ProLiant DL360, no es que pueda tenerlo precisamente en el sal\u00f3n de casa, que es lo ideal para poder hacer detecci\u00f3n de movimientos y control dom\u00f3tico. La idea pasa por conectar el Kinect a un sistema m\u00e1s sencillo, que contenga la parte b\u00e1sica de ROS, y crear un sistema maestro\/esclavo, algo que ROS soporta perfectamente.<\/li>\n\n\n\n<li>Ampliar el tipo de gestos reconocidos por el sistema. He empezado por algunos b\u00e1sicos, pero las posibilidades son inmensas.<\/li>\n\n\n\n<li>Utilizar otros canales para el reconocimiento de imagen. Actualmente hago uso del RGB, pero ser\u00eda muy interesante poder usar el espectro infrarrojo, o el reconocimiento de puntos.<\/li>\n\n\n\n<li>Explorar otro tipo de <em>hardware<\/em> m\u00e1s moderno.<\/li>\n\n\n\n<li>Explorar la utilizaci\u00f3n de ROS 2, para evitar la obsolescencia de ROS 1.<\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"677\" height=\"278\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/ros_master_communication_znqheg.png\" alt=\"\" class=\"wp-image-11368\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/ros_master_communication_znqheg.png 677w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2025\/12\/ros_master_communication_znqheg-300x123.png 300w\" sizes=\"auto, (max-width: 677px) 100vw, 677px\" \/><figcaption class=\"wp-element-caption\">Flujo de comunicaciones en ROS<\/figcaption><\/figure>\n\n\n\n<p>Como se puede ver, hay ramificaciones para rato.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 1 de 3 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=deteccion-de-movimientos-con-ia-como-control-domotico\" class=\"series-1896\" title=\"Detecci\u00f3n de movimientos con IA como control dom\u00f3tico\">Detecci\u00f3n de movimientos con IA como control dom\u00f3tico<\/a><\/div><p>Otro de los proyectos en los que he estado trabajando<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[13],"tags":[543,735,824,1885,1895,1134,1887,1886,1361,1882,1883],"series":[1896],"class_list":["post-11361","post","type-post","status-publish","format-standard","hentry","category-informatica","tag-domotica","tag-google","tag-ia","tag-kinect","tag-mediapipe","tag-mqtt","tag-openni","tag-perplexity","tag-python","tag-ros","tag-ubuntu","series-deteccion-de-movimientos-con-ia-como-control-domotico"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11361","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11361"}],"version-history":[{"count":2,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11361\/revisions"}],"predecessor-version":[{"id":11370,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11361\/revisions\/11370"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11361"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11361"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11361"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11361"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}