¿Dónde empieza lo real? Cuando un gesto activa una máquina

Levanto la mano frente a la cámara. El dedo índice apunta. Y entonces, la banda transportadora virtual comienza a moverse. Parece magia. Pero no lo es.

Ese gesto –tan simple, tan humano– es el resultado de semanas de pruebas, errores y ajustes. De líneas de código que no responden, de modelos que no entienden, de señales que no llegan. Lo que hoy parece fluido, antes fue una secuencia caótica de fallos intermitentes y frustraciones técnicas.

La idea era clara desde el inicio: construir un puente entre lo humano y lo automatizado. Que un gesto pudiera activar una máquina. Que la intención bastara para que algo se moviera, sin botones, sin contacto físico, solo con una señal emitida por el cuerpo.

Lo que desarrollé fue una arquitectura distribuida:

  • Python con visión artificial usando MediaPipe detecta los gestos.
  • Unity simula la banda transportadora y los sensores.
  • Una ESP32 recibe comandos por red para activar indicadores físicos y lo envía a un PLC

En el papel, todo estaba claro. En la práctica, fue otra historia.
Los gestos no se reconocían.
Los sockets se caían.
La ESP o python no recibía.
Unity se congelaba.
Y yo… repetía la prueba.

Una y otra vez. Cambié el código, ajusté retardos, probé diferentes protocolos, capturé logs. Las preguntas se acumulaban:

  • ¿Dónde está el cuello de botella?
  • ¿Qué tan rápido es lo “rápido suficiente”?
  • ¿Cómo evito una activación falsa sin perder sensibilidad?
  • ¿Puede coexistir el control visual con botones físicos?
  • ¿Qué significa que el sistema “entienda” mi gesto?

Fueron muchas horas de repetición obsesiva. Días enteros donde solo conseguía que el sistema hiciera “casi lo que debía”. Pero en medio de cada error, se afianzaba una idea: lo real no empieza cuando todo funciona, sino cuando algo cobra sentido.

Hoy puedo decir que el sistema responde. Que un gesto puede iniciar el flujo. Que el cuerpo puede ser interfaz. No porque sea perfecto, sino porque se adaptó. Y porque yo también me adapté a él.

Cuando el gesto se vuelve comando, la máquina obedece. Pero más importante aún: la tecnología se convierte en extensión de la intención humana.

Y ahí, justo ahí, es donde para mí empieza lo real.

Me queda claro a partir de todos mis errores hacía donde puedo ir y como siempre me quedo con todo lo aprendido :).

Cómo diseñé un sistema de ojos animados controlados por visión artificial

Hace un tiempo que vengo experimentando la combinación de hardware básico con visión artificial. La idea, era construir un sistema simple donde una matriz de LEDs representara dos ojos que se abrieran o cerraran dependiendo del parpadeo detectado en tiempo real mediante una cámara.

El primer intento consistió en recorrer la matriz LED de 8×32 pixel por pixel, encendiendo o apagando cada LED individualmente según el patrón deseado. Aunque técnicamente funcionaba, el resultado no era satisfactorio: el dibujo tardaba en actualizarse y el cambio de estado de los ojos se percibía lento.

Buscando optimizar el rendimiento, cambié el enfoque. En lugar de manejar cada LED de forma independiente, diseñé patrones binarios donde cada fila de la matriz correspondía a un byte de información. Con esto, el sistema podía actualizar toda la figura del ojo de manera instantánea, reduciendo los tiempos de actualización perceptibles.

Una vez que la animación en la matriz fue optimizada, el siguiente paso fue integrar la detección de parpadeo. Utilicé Python junto con OpenCV y MediaPipe para capturar video en tiempo real y analizar las posiciones de los ojos. Calcule el Eye Aspect Ratio (EAR) para determinar el grado de apertura de los párpados. Si el EAR bajaba por debajo de un umbral definido, se consideraba que el ojo estaba cerrado.

Cada evento detectado en Python enviaba un comando simple por comunicación serial al microcontrolador: «OPEN» si los ojos estaban abiertos, o «CLOSE» si se detectaba parpadeo. Arduino recibía estos comandos y actualizaba la matriz de LEDs en consecuencia.

Durante el proceso surgieron varios problemas adicionales, como la necesidad de afinar el umbral del EAR para evitar falsos positivos, optimizar la lectura de comandos seriales para evitar retrasos, y mejorar la fluidez de las animaciones para que los cambios de estado se sintieran naturales.

El resultado final fue un sistema que detecta el parpadeo de una persona en tiempo real y actualiza una matriz de LEDs para representar esa acción. Además, si no detecta eventos durante cierto tiempo, el sistema genera parpadeos automáticos programados, simulando actividad natural.

Esta idea fue una buena práctica para integrar visión computacional con control físico de hardware en tiempo real. También sirvió para explorar la importancia de optimizar flujos de procesamiento en sistemas de recursos limitados, aún hay muchas cosas que mejorar pero es una base con la que puedo seguir explorando nuevas ideas.

Preparando un servicio que retorna texto a partir de un audio

Pues hoy tocó preparar el entorno para ejecutar en FastApi un servicio que reciba un audio y regrese un texto.

Como es necesario procesar el audio que se graba en web y se envía al servidor, se necesitan algunas librerias para convertir entre formatos de audio

en mi caso utilicé home brew para instalar la librería ffmpeg para que funcione correctamente algunos módulos de python, el detalle es que al tener varias versiones de python tuve algunos problemas por lo que finalmente encontré una solución, que fue, instalar las versiones compiladas de las librerías que se pueden descargar aquí.

Para la prueba utilicé vue, en el cuál existe un método para iniciar y detener la grabación y finalmente el método que envía a la api.

y bueno este es parte del código de la api, como se observa ya que instalé ffmpeg y ffprobe con una versión compilada, los ejecutables los pasé al directorio /Applications para que python los pudiera encontrar.

En otras pruebas se pueden ver los resultados

ahora a probar con modelos preentrenados que se ejecuten localmente.

¿Qué relación tiene un mapa conceptual y un chatbot?

Una alternativa que tenemos para construir un chatbot son los mapas conceptuales, que nos ayudan a conectar conceptos y definir relaciones entre ellos. Esta práctica fue parte de la temática expuesta en la materia de Inteligencia Artificial, enfocada en la aplicación de modelos de representación del conocimiento en el desarrollo de asistentes virtuales.

Los mapas conceptuales o grafos de conocimiento permiten modelar información en forma de nodos y relaciones, facilitando la organización y el acceso a los datos. En este contexto, podemos crear un chatbot que responda preguntas basadas en la estructura de un grafo. Los conceptos utilizados son referentes a la temática de Inteligencia Artificial, aprovechando los temas propios de la materia de IA que imparto, en donde utilicé el ejemplo para explicar la relación.

El primer ejercicio consistió en construir un grafo dirigido que representa la relación entre distintos subcampos de la Inteligencia Artificial. Con la biblioteca networkx, al ejecutar el código se puede observar el grafo.

import networkx as nx
import matplotlib.pyplot as plt

G = nx.DiGraph()
G.add_edges_from([
    ("IA", "Machine Learning"),
    ("IA", "Deep Learning"),
    ("Machine Learning", "Redes Neuronales"),
    ("Machine Learning", "Árboles de Decisión"),
    ("Deep Learning", "CNN"),
    ("Deep Learning", "RNN")
])

plt.figure(figsize=(8,6))
nx.draw(G, with_labels=True, node_color='lightblue', edge_color='gray', node_size=3000, font_size=10)
plt.show()

Este ejercicio ayudó a visualizar cómo los conceptos de IA están conectados y cómo se pueden representar jerárquicamente mediante grafos.

y el chat bot?

Ahora toca integrar el mapa en un chatbot que pueda responder preguntas. Para esto se implementó un sistema de preguntas y respuestas que interpreta consultas sobre conceptos en el grafo y devuelve respuestas basadas en las conexiones definidas.

def responder_pregunta(pregunta):
    pregunta = pregunta.lower()
    
    # Pregunta: ¿Qué es <concepto>?
    for nodo in G.nodes:
        if f"qué es {nodo.lower()}" in pregunta:
            subtemas = list(G.successors(nodo))
            if subtemas:
                return f"{nodo} es un área de la Inteligencia Artificial que incluye conceptos como: {', '.join(subtemas)}."
            else:
                return f"{nodo} es un concepto en Inteligencia Artificial, pero no tiene subcategorías en este modelo."
    
    # Pregunta: ¿Qué relación tiene <concepto1> con <concepto2>?
    conceptos = [nodo for nodo in G.nodes if nodo.lower() in pregunta]
    
    if len(conceptos) == 2:
        nodo1, nodo2 = conceptos
        if G.has_edge(nodo1, nodo2):
            return f"{nodo1} es una subcategoría de {nodo2}."
        elif G.has_edge(nodo2, nodo1):
            return f"{nodo2} es una subcategoría de {nodo1}."
        else:
            return f"{nodo1} y {nodo2} están relacionadas con Inteligencia Artificial, pero no directamente en este grafo."
    
    return "Lo siento, no tengo información sobre eso."

Aquí algunas de las preguntas y la salida.

qué sigue?

El reto no termina aquí. Ahora se espera que los estudiantes puedan aplicar lo aprendido en otro contexto, utilizando otro tipo de conocimientos. La idea es ver los resultados más adelante y explorar cómo se pueden extender estos modelos a otras áreas de conocimiento.

!de vuelta!

¡Estamos de vuelta en e-icus.net!

Han pasado varios años desde que inicié este blog bajo el nombre de «abdielicus», y con el tiempo evolucionó a «e-icus.net». Sin embargo, por diversas razones, el proyecto quedó en pausa. Hoy, con energía renovada y muchas ideas en mente, es momento de retomarlo.

En esta nueva etapa, quiero compartir contenido sobre temas que siempre me han apasionado: educación, programación y actividades diarias. Desde artículos técnicos y guías de desarrollo hasta reflexiones sobre el aprendizaje y la tecnología, este espacio será un punto de encuentro para el conocimiento y la experimentación.

Es momento de cambiar la esencia del blog, me propongo mejorar la frecuencia de publicaciones y la interacción con quienes se sumen a esta comunidad. Leeré sus opiniones, sugerencias y temas de interés para hacer de este sitio un espacio más enriquecedor para todos.

Nos leemos pronto.

Problemas con Google Chrome, resultados de búsqueda mal intencionados

Hace unos días noté que los resultados de búsqueda que hacía desde google Chrome se veían distintos, de manera regular aparece resultados de las páginas oficiales, si busco algún término por ejemplo sale wikipedia e imágenes a la derecha, digamos que el comportamiento normal.

El comportamiento anómalo, se da al momento de hacer una búsqueda, al inicio aparece los resultados comunes pero después de 1 o 2 segundos se insertan resultados con anuncios y páginas que no suelo ver.

El primer intento para corregir el error fue borrando los datos de navegación, cookies, etc; esto no funcionó.

El segundo intento fue desinstalar el programa, al hacerlo y volverlo a instalar el resultado de la misma manera no funcionó, la desinstalación no elimina varios archivos que son los responsables de dicho comportamiento, de alguna manera una cookie, o algún script pudo vulnerar los archivos de Crhome.

La solución

Por lo que me di a la tarea de eliminar los siguientes elementos en modo consola.


rm -r ~/Library/Application\ Support/Google/Chrome/
rm ~/Library/Preferences/com.google.Chrome*
rm ~/Library/Application\ Support/CrashReporter/Google\ Chrome*
rm -r ~/Library/Caches/com.google.Chrome*
rm ~/Library/Preferences/Google\ Chrome*
rm -r ~/Library/Saved\ Application\ State/com.google.Chrome.savedState/
rm ~/Library/Google/GoogleSoftwareUpdate/Actives/com.google.Chrome
rm ~/Library/Google/Google\ Chrome*
rm -r /Applications/Google\ Chrome.app/

A lo mejor le pueda servir a alguien o si me vuelve a ocurrir, aquí tengo la solución.

Luz

There is a crack in everthing, that’s how the light gets in»

Leonard Cohen