Juega con la IA y….¿Mario?

BlogJuega-con-la-IA-y...¿Mario

Si alguna vez imaginaste controlar a Mario con tu propio cuerpo en lugar de un control tradicional, este proyecto te va a encantar.

La MaixCAM, una potente placa de desarrollo con arquitectura RISC-V e inteligencia artificial integrada, será el cerebro detrás de esta idea. Gracias a su cámara y capacidades de visión por computadora, puede detectar movimientos en tiempo real y convertirlos en acciones dentro de un emulador de Super Mario Bros.

¿Saltar en la vida real? Mario salta. ¿Inclinarte? Mario se mueve. Así de simple… y así de divertido. Este proyecto combina tecnología, creatividad y un toque de nostalgia gamer para transformar la forma en que interactuamos con los videojuegos clásicos y hoy te enseño a cómo hacerlo.

Introducción

Antes de empezar con este proyecto, se deben de comprender ciertos temas de importancia; esto ayudará a saber cómo funciona el sistema y si lo deseamos, hacer algunos cambios en este.

¿Qué es la MaixCAM?

Es una placa de desarrollo basada en arquitectura RISC-V que combina un procesador potente, un acelerador de IA (NPU) y periféricos como cámara, pantalla y conectividad inalámbrica, todo en un único módulo compacto. Esto la hace ideal para crear dispositivos con capacidades de visión inteligente y procesamiento de IA sin depender de un ordenador externo.

Imagen 1. MaixCAM.

Componentes Clave.

ComponenteDescripciónFunción principal
CPU RISC-V (C906 ~1 GHz)Procesador principal basado en arquitectura RISC-VEjecuta el sistema operativo y la lógica del programa
NPU (~1 TOPS)Unidad de Procesamiento Neuronal integradaAcelera modelos de IA como detección y clasificación de objetos
Memoria RAMMemoria integrada de alta velocidadManejo de datos, buffers de imagen y ejecución de modelos
Almacenamiento (MicroSD/TF)Soporte para tarjeta externaGuarda modelos, imágenes, videos y programas
Cámara (hasta ~5MP)Sensor de imagen compatibleCaptura video e imágenes para visión artificial
Pantalla (~2.3” táctil)Display integradoVisualización de resultados, interfaz gráfica y debugging
Wi-Fi 6Conectividad inalámbricaComunicación en red y envío de datos a la nube
Bluetooth LEComunicación inalámbrica de bajo consumoConexión con dispositivos móviles o periféricos
GPIO / Interfaces (UART, I2C, SPI, etc.)Pines de expansiónConexión de sensores, actuadores y módulos externos
Tabla 1. Características de la MaixCAM.

Usos.

La placa se emplea principalmente para desarrollar e implementar aplicaciones de inteligencia artificial en el borde (edge AI), es decir, que el procesamiento de IA se hace directamente en el dispositivo y no en la nube. Algunos ejemplos incluyen:

  • Reconocimiento y detección de objetos.
  • Clasificación o análisis de imágenes en tiempo real.
  • Proyectos de IoT inteligentes.
  • Robótica con visión integrada.
  • Sistemas de seguridad con IA.

¿Qué es YOLO?

YOLO Pose es una versión de la familia de modelos YOLO (You Only Look Once) diseñada para hacer estimación de pose humana en tiempo real. En lugar de solo detectar objetos (como personas, autos, etc.), YOLO Pose detecta personas y además localiza puntos clave del cuerpo (keypoints), como:

  • Cabeza.
  • Ojos.
  • Manos.
  • Rodillas.
  • Pies.

El programa que se usará es el de “pose”, que es una técnica de visión por computadora que identifica la posición de articulaciones del cuerpo humano en una imagen o video, generando un “esqueleto digital” sobre la persona detectada.

Entre los usos que les puedes dar está:

  • Control de videojuegos con el cuerpo.
  • Análisis de ejercicios y postura.
  • Seguimiento de movimientos en tiempo real.
  • Interacción humano-máquina.
  • Motion capture básico sin trajes especiales.

Ahora que ya conocemos qué es la MaixCAM, sus componentes clave, así como una breve explicación de cómo funciona, además del conocimiento de los programas YOLOS, podemos poner manos a la obra para empezar a jugar con Mario.

Materiales

Requerimientos Previos.

Para usar la MaixCAM debemos cargar la imagen de su sistema; para ello podemos ver la entrada de nuestro blog Mis Primeros Pasos con la MaixCAM AI RISCV, se usará el software de BalenaEtcher para el grabado y MaixVision para el código en Python; sin embargo, para el emulador usaremos Mesen.

Instalar Mesen.

La instalación de este emulador es muy sencilla, primero debemos ir a su página oficial Mesen, se abrirá la siguiente página:

Imagen 2. Mesen.

Después de seleccionar el sistema operativo, tendremos la carpeta ZIP del programa, la cual se debe descomprimir; dentro de ella se tendrá un archivo .exe que se instalará como administrador.

Imagen 3. Instalación.

Se abrirá la ventana de la imagen 3; en ella solo configuraremos los controles de entrada como flechas y mando; realmente esto no tiene tanta importancia, ya que se puede cambiar desde el juego mismo, sin embargo, para practicidad activemos la casilla de “Crear acceso directo en el escritorio”.

Una vez terminado, solo hay que dar click en “CONFIRMAR”, y se abrirá el emulador teniendo el software ya instalado, como dato interesante, también se pueden cargar juegos y usarlos desde ese programa.

Imagen 4. Mesen Instalado.

Código de Programación

El código se realizó desde el software MaixVision, e igual se ejecuta desde ese IDE, a continuación, lo dejamos para que lo uses.

"""
========================================================================================================================
Nombre del archivo:     Version_Gama.py
Proyecto:               UNIT Electronics Blog - Juega con la IA y….¿Mario?
Área:                   Desarrollo
Autor:                  Gonzalo González
Fecha:                  19/02/2025
========================================================================================================================
Descripción:

Este código usa el módelo de IA YOLO Pose para detectar movimientos del cuerpo humano y clasificarlos para un Emulador de Juegos, estos datos se envían a través del puerto UART para funcionar como teclas HDI.
========================================================================================================================
Desarrollado para UNIT Electronics / Desarrollo / Blog UNIT Electronics
========================================================================================================================
"""

from maix import camera, display, image, nn, app, time, hid

SHOW_IMG = True
model_path = "/root/models/yolo11n_pose.mud"

KEY_LEFT = 80
KEY_RIGHT = 79
KEY_UP = 22
KEY_DOWN = 81
KEY_ATTACK = 29
KEY_RUN = 225


class PoseRecognizer:

    POSE_LEFT = 1
    POSE_RIGHT = 2
    POSE_UP = 3
    POSE_DOWN = 4
    POSE_ATTACK = 5
    POSE_RUN = 6

    def __init__(self, img_w, img_h):

        self.img_w = img_w
        self.img_h = img_h

        # ========= AUTOCALIBRACIÓN =========
        self.calibrating = True
        self.calibration_start = time.ticks_ms()
        self.calibration_samples = []
        self.neutral_mid_y = 0

        # ========= MOVIMIENTO L/R =========
        self.lr_state = 0
        self.lr_enter = 0.18
        self.lr_exit = 0.10

        # ========= SALTO PRO =========
        self.jump_threshold = 5
        self.last_jump_time = 0
        self.jump_hold_until = 0
        self.base_jump_hold = 380
        self.max_jump_hold = 650
        self.y_points = []

        # ========= DOWN PROFESIONAL =========
        self.down_state = False
        self.down_enter = 35
        self.down_exit = 20
        self.down_hold_until = 0
        self.min_down_hold = 120

        # ========= RUN =========
        self.wrist_history = []

        # ========= ESTABILIDAD =========
        self.pose_buffer = {}
        self.pose_stable_frames = 1

    # ===============================
    # FILTRO ESTABLE
    # ===============================
    def stabilize_pose(self, poses):

        stable = []

        for p in poses:
            if p not in self.pose_buffer:
                self.pose_buffer[p] = 1
            else:
                self.pose_buffer[p] += 1

        for p in list(self.pose_buffer.keys()):
            if p not in poses:
                self.pose_buffer[p] = 0

        for p, count in self.pose_buffer.items():
            if count >= self.pose_stable_frames:
                stable.append(p)

        return stable

    # ===============================
    # DETECCIÓN PRINCIPAL
    # ===============================
    def run(self, obj):

        res = []

        shoulder_l = [obj.points[10], obj.points[11]]
        shoulder_r = [obj.points[12], obj.points[13]]
        wrist_l = [obj.points[18], obj.points[19]]
        wrist_r = [obj.points[20], obj.points[21]]
        hip_l = [obj.points[22], obj.points[23]]
        hip_r = [obj.points[24], obj.points[25]]

        shoulder_len = abs(shoulder_l[0] - shoulder_r[0])
        if shoulder_len < 20:
            return False, res

        shoulder_mid = [
            (shoulder_l[0] + shoulder_r[0]) * 0.5,
            (shoulder_l[1] + shoulder_r[1]) * 0.5
        ]

        hip_mid = [
            (hip_l[0] + hip_r[0]) * 0.5,
            (hip_l[1] + hip_r[1]) * 0.5
        ]

        mid_y = (shoulder_mid[1] + hip_mid[1]) * 0.5

        # ========= AUTOCALIBRACIÓN =========
        if self.calibrating:

            self.calibration_samples.append(mid_y)

            if time.ticks_ms() - self.calibration_start > 2000:
                self.neutral_mid_y = sum(self.calibration_samples) / len(self.calibration_samples)
                self.calibrating = False
                print("CALIBRADO ✓")

            return False, []

        # ========= IZQUIERDA / DERECHA =========
        v = (shoulder_mid[0] - hip_mid[0]) / shoulder_len

        if self.lr_state == 0:
            if v > self.lr_enter:
                self.lr_state = 1
            elif v < -self.lr_enter:
                self.lr_state = -1
        elif self.lr_state == 1 and v < self.lr_exit:
            self.lr_state = 0
        elif self.lr_state == -1 and v > -self.lr_exit:
            self.lr_state = 0

        if self.lr_state == 1:
            res.append(self.POSE_RIGHT)
        elif self.lr_state == -1:
            res.append(self.POSE_LEFT)

        # ========= DOWN PROFESIONAL =========
        now = time.ticks_ms()
        delta_down = mid_y - self.neutral_mid_y

        if not self.down_state and delta_down > self.down_enter:
            self.down_state = True
            self.down_hold_until = now + self.min_down_hold

        elif self.down_state and delta_down < self.down_exit:
            if now > self.down_hold_until:
                self.down_state = False

        if self.down_state:
            res.append(self.POSE_DOWN)

        # ========= SALTO DINÁMICO LARGO =========
        self.y_points.append(mid_y)
        if len(self.y_points) > 4:
            self.y_points.pop(0)

        if len(self.y_points) >= 3:

            velocity = (self.y_points[-3] - self.y_points[-2]) + \
                       (self.y_points[-2] - self.y_points[-1])

            if velocity > self.jump_threshold and now - self.last_jump_time > 600:

                hold = self.base_jump_hold + int((velocity ** 1.1) * 25)
                hold = min(hold, self.max_jump_hold)

                self.jump_hold_until = now + hold
                self.last_jump_time = now

        if now < self.jump_hold_until:
            res.append(self.POSE_UP)

        # ========= RUN =========
        wrist_diff = wrist_l[0] - wrist_r[0]
        self.wrist_history.append(wrist_diff)

        if len(self.wrist_history) > 6:
            self.wrist_history.pop(0)

        if len(self.wrist_history) >= 4:
            changes = 0
            for i in range(1, len(self.wrist_history)):
                if self.wrist_history[i] * self.wrist_history[i-1] < 0:
                    changes += 1
            if changes >= 2:
                res.append(self.POSE_RUN)

        # ========= ATTACK =========
        if wrist_l[1] < shoulder_l[1] or wrist_r[1] < shoulder_r[1]:
            res.append(self.POSE_ATTACK)

        return True, res


# ===============================
# HID
# ===============================
last_keys = set()

def send_keys(poses, keyboard):

    global last_keys

    key_map = {
        PoseRecognizer.POSE_LEFT: KEY_LEFT,
        PoseRecognizer.POSE_RIGHT: KEY_RIGHT,
        PoseRecognizer.POSE_UP: KEY_UP,
        PoseRecognizer.POSE_DOWN: KEY_DOWN,
        PoseRecognizer.POSE_ATTACK: KEY_ATTACK,
        PoseRecognizer.POSE_RUN: KEY_RUN,
    }

    current = set()

    for p in poses:
        current.add(key_map[p])

    if current == last_keys:
        return

    cmd = [0, 0] + list(current)
    cmd += [0] * (8 - len(cmd))

    keyboard.write(cmd)
    last_keys = current


# ===============================
# OVERLAY VISUAL
# ===============================
def draw_overlay(img, recognizer, poses):

    if recognizer.calibrating:
        img.draw_string(10, 10, "CALIBRANDO...", image.COLOR_RED, scale=2)
        return

    y = 10

    labels = {
        PoseRecognizer.POSE_LEFT: ("LEFT", image.COLOR_BLUE),
        PoseRecognizer.POSE_RIGHT: ("RIGHT", image.COLOR_BLUE),
        PoseRecognizer.POSE_UP: ("JUMP", image.COLOR_GREEN),
        PoseRecognizer.POSE_DOWN: ("DOWN", image.COLOR_YELLOW),
        PoseRecognizer.POSE_ATTACK: ("ATTACK", image.COLOR_RED),
        PoseRecognizer.POSE_RUN: ("RUN", image.COLOR_PURPLE),
    }

    for p in poses:
        text, color = labels[p]
        img.draw_string(10, y, text, color, scale=2)
        y += 25


# ===============================
# MAIN
# ===============================
def main():

    detector = nn.YOLO11(model=model_path, dual_buff=True)

    cam = camera.Camera(detector.input_width(),
                        detector.input_height(),
                        detector.input_format())

    cam.hmirror(1)

    recognizer = PoseRecognizer(cam.width(), cam.height())
    keyboard = hid.Hid(hid.DeviceType.DEVICE_KEYBOARD)
    disp = display.Display()

    while not app.need_exit():

        img = cam.read()
        objs = detector.detect(img, conf_th=0.45, iou_th=0.45)

        poses = []

        if len(objs) > 0:
            obj = max(objs, key=lambda o: o.w * o.h)
            valid, poses = recognizer.run(obj)

            if valid:
                poses = recognizer.stabilize_pose(poses)
                send_keys(poses, keyboard)
                detector.draw_pose(img, obj.points, 4, image.COLOR_GREEN)
        else:
            send_keys([], keyboard)

        draw_overlay(img, recognizer, poses)
        disp.show(img)


if __name__ == '__main__':
    main()

Con este código podrás:

  • Mover a Mario a la izquierda/derecha inclinando tu cuerpo.
  • Saltar.
  • Agacharte (debes agacharte por completo).
  • Correr (balanceando los brazos).
  • Atacar (levantando una mano).

Funcionamiento

Ya tenemos todo para hacer funcionar nuestra IA con Mario, así que empecemos a configurar todo.

Configuración del Emulador Mesen.

Como primer paso, deberás descargar el juego de Super Mario Bros en formato .NES en cualquier sitio web, posteriormente, se tendrá que abrir o cargar, para ello debes presionar el apartado de “File > Open” y buscarlo en donde esté descargado, una vez que lo hayas hecho, hay que configurar las teclas o botones.

Nos dirigiremos a “Settings > Input”, se abrirá una ventana como está:

Imagen 5. Configuración NES.

Una vez abierta la ventana, daremos click en “Input” de la parte superior del menú:

Imagen 6. Input NES.

Ahora click en “Setup”:

Imagen 7. Setup NES.

Abrirá una ventana para la selección de teclas, la tendremos que configurar de la siguiente manera:

Imagen 8. Configuración del Control.

Se deben seleccionar las teclas, los botones A/B y la parte media; para hacerlo, solo se da click en cada recuadro y posteriormente presionamos la tecla, muy sencillo, una vez hechos los cambios, se dará click en “Ok” en todas las ventanas hasta dejar únicamente el juego.

Configuración de la MaixCAM.

Teniendo el Emulador configurado hay que hacer algo parecido a la MaixCAM, hay que hacer que está funcione como un periférico de PC, es decir, como un componente HID, esto es muy rápido.

Hay que conectarla a nuestra PC a través de su cable USB C, una vez que corra la imagen del sistema, nos iremos a “Settings > USB Settinggs” en este apartado tendremos que bajar hasta que veamos un botón titulado “HID Mouse” ese no lo toques o actives; vamos a activar el que está al lado, “HID Keyboard”, así como se ve en la siguiente imagen:

Imagen 9. Selección de Teclado.

Como recordatorio, la MaixCAM y el ordenador/PC que usaremos deben estar conectados a la misma red local; de lo contrario, no funcionará correctamente el programa.

Prueba

Abriremos el software MaixVision, y crearemos un nuevo archivo, copiaremos el código y conectaremos la MaixCAM a través de la IP que tenga:

Imagen 10. Conexión IP.

Una vez conectados, solo daremos click en “RUN”, ahora bien, una vez que cargué el programa, primero se calibrará por lo que el jugador ya debe estar posicionado a una distancia de 1.8 a 2 metros de la MaixCAM, además que estará deberá estar a una altura considerable para que tenga buen campo de visión; otro punto importante es que no olvides enfocar bien lente de lo contrario tendrás una imagen borrosa y nada nítida, por último no olvides seleccionar la ventana del Emulador de lo contrario no se mandarán los comandos detectados.

Imagen 11. Prueba

Así se verá el software de MaixCAM cuando hagamos los gestos y se reconozcan, imprimiendo en pantalla lo que se esté detectando, mientras que del otro lado tendremos a Mario interactuando como si lo estuviéramos controlando con un teclado físico:

Imagen 12. Mario.

¿Quieres ver cómo funciona? Te dejo un video demostrativo:

Video 1: Demostración.

Conclusiones

Este proyecto demuestra que la inteligencia artificial ya no es una tecnología lejana o exclusiva de grandes industrias, sino una herramienta accesible que podemos integrar en proyectos creativos y divertidos. Al utilizar la MaixCAM con arquitectura RISC-V y el modelo YOLO Pose, logramos transformar movimientos reales del cuerpo en acciones dentro de un videojuego clásico como Super Mario Bros.

La combinación de visión por computadora, procesamiento en el borde (Edge AI) y emulación nos permitió crear una nueva forma de interacción humano-máquina, donde el teclado tradicional es reemplazado por gestos naturales. Esto no solo abre la puerta a nuevas experiencias de juego, sino también a aplicaciones en educación, rehabilitación, deporte y robótica.

Además, el desarrollo mostró la importancia de comprender tanto el hardware como el software: desde la configuración del emulador y el modo HID, hasta la calibración y optimización del reconocimiento de poses para reducir latencia y mejorar la experiencia de usuario.

En conclusión, integrar IA con creatividad y nostalgia gamer nos permite reinventar la manera en que interactuamos con la tecnología. Y lo mejor de todo: este es solo el comienzo de lo que se puede lograr cuando combinamos imaginación, código y un poco de espíritu maker.

Deja un comentario

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