Mano Robot por Gestos

Control de una mano mecánica mediante reconocimiento de gestos con inteligencia artificial

Video

Descripción del Proyecto

Este proyecto consiste en una mano robótica controlada mediante gestos de la mano captados por webcam. El sistema detecta en tiempo real la posición de cada dedo utilizando MediaPipe HandLandmarker y envía comandos a los servomotores que reproducen el movimiento.

La arquitectura del sistema tiene tres componentes:

Webcam (PC) ──TCP──▶ ESP32 ──Serie──▶ Arduino ──▶ Servomotores

Cada dedo (meñique, corazón, índice, pulgar) controla un servo independiente. Los movimientos son suaves y progresivos, adaptándose al gesto detectado.

Instrucciones de Uso

1. Material Necesario

2. Instalación del Software en la PC

Instala las dependencias de Python necesarias:

pip install opencv-python mediapipe

Asegúrate de que el archivo hand_landmarker.task esté en la misma carpeta que control_robot_gesto.py.

3. Configurar la ESP32

Crea un archivo config.h en la carpeta esp32/ con tus datos WiFi:

const char* ssid = "NOMBRE_DE_TU_RED";
const char* password = "CONTRASENA_DE_TU_RED";

En Arduino IDE, selecciona la placa ESP32C3 Dev Module y activa:

Sube el programa esp32.ino a la ESP32. Abre el monitor serie (115200 baud) y anota la IP que aparece al conectarse al WiFi.

4. Subir el Programa al Arduino

Selecciona la placa Arduino Uno y sube arduino.ino.

Para probar, abre el monitor serie a 9600 baud y escribe 1, 2, 3 o 4. Los servos deberían moverse.

5. Conexiones (Esquema Eléctrico)

ESP32 ↔ Arduino

ESP32 GPIO 6 (TX) ──── Arduino pin 2 (RX) ESP32 GPIO 5 (RX) ──── Arduino pin 3 (TX) ESP32 GND ──────────── Arduino GND

Arduino ↔ Servomotores

Servo 1 (Menique) ── cable amarillo ── Arduino pin 10 Servo 2 (Corazon) ── cable amarillo ── Arduino pin 11 Servo 3 (Indice) ── cable amarillo ── Arduino pin 12 Servo 4 (Pulgar) ── cable amarillo ── Arduino pin 13 Todos los cables rojos ── 5V del Arduino Todos los cables marrones ── GND del Arduino
Los servos consumen mucha corriente. Si los 4 se mueven a la vez y el Arduino se reinicia, alimenta los servos con una fuente externa de 5V (compartiendo GND con el Arduino).

6. Ejecutar el Programa

Con todo conectado y la ESP32 encendida (mostrando su IP en el monitor serie), ejecuta en la PC:

python control_robot_gesto.py IP_DE_LA_ESP32

Ejemplo:

python control_robot_gesto.py 192.168.1.100

Se abrirá una ventana con la webcam. Pon tu mano frente a la cámara y mueve los dedos:

Pulsa q para salir.

7. Solución de Problemas

import socket
import cv2
import mediapipe as mp
import sys

PUERTO = 1001
TIMEOUT_CONEXION = 5
MODEL_PATH = "hand_landmarker.task"
UMBRAL_PULGAR = 0.06

COMANDOS = {
    "menique": {"up": "1", "down": "01"},
    "corazon": {"up": "2", "down": "02"},
    "indice": {"up": "3", "down": "03"},
    "pulgar": {"up": "4", "down": "04"},
}

NOMBRES = {
    "menique": "Menique",
    "corazon": "Corazon",
    "indice": "Indice",
    "pulgar": "Pulgar",
}


def conectar(ip):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(TIMEOUT_CONEXION)
        sock.connect((ip, PUERTO))
        return sock
    except socket.error:
        return None


def enviar_comando(sock, comando):
    try:
        sock.send(comando.encode())
        return True
    except socket.error:
        return False


def detectar_dedos(landmarks):
    dx = landmarks[4].x - landmarks[5].x
    dy = landmarks[4].y - landmarks[5].y
    pulgar_extendido = (dx * dx + dy * dy) ** 0.5 > UMBRAL_PULGAR
    indice_extendido = landmarks[8].y < landmarks[6].y
    corazon_extendido = landmarks[12].y < landmarks[10].y
    menique_extendido = landmarks[20].y < landmarks[18].y
    return {
        "menique": menique_extendido,
        "corazon": corazon_extendido,
        "indice": indice_extendido,
        "pulgar": pulgar_extendido,
    }


def mostrar_ayuda():
    print("Controles por gesto:")
    print("  Menique arriba/abajo  -> Servo 1 a 180/0")
    print("  Corazon arriba/abajo  -> Servo 2 a 180/0")
    print("  Indice arriba/abajo   -> Servo 3 a 180/0")
    print("  Pulgar arriba/abajo   -> Servo 4 a 180/0")
    print("  Presiona 'q' para salir")


def main():
    print("===== Control de Mano Robot por Gesto =====")

    if len(sys.argv) > 1:
        ip = sys.argv[1]
    else:
        ip = input("Ingrese la IP de la ESP32: ").strip()

    if not ip:
        print("Error: La IP no puede estar vacia")
        return

    print(f"Conectando a {ip}:{PUERTO}...")
    sock = conectar(ip)

    if not sock:
        print("Error: No se pudo conectar a la ESP32")
        return

    print("Conexion exitosa!")
    mostrar_ayuda()

    BaseOptions = mp.tasks.BaseOptions
    HandLandmarker = mp.tasks.vision.HandLandmarker
    HandLandmarkerOptions = mp.tasks.vision.HandLandmarkerOptions
    VisionRunningMode = mp.tasks.vision.RunningMode

    options = HandLandmarkerOptions(
        base_options=BaseOptions(model_asset_path=MODEL_PATH),
        running_mode=VisionRunningMode.IMAGE,
        num_hands=1,
    )

    landmarker = HandLandmarker.create_from_options(options)

    cap = cv2.VideoCapture(0)
    estado_anterior = None

    try:
        while True:
            ret, frame = cap.read()
            if not ret:
                break

            frame = cv2.flip(frame, 1)
            rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)

            results = landmarker.detect(mp_image)

            if results.hand_landmarks:
                for hand_landmarks in results.hand_landmarks:
                    h, w, _ = frame.shape

                    for landmark in hand_landmarks:
                        x = int(landmark.x * w)
                        y = int(landmark.y * h)
                        cv2.circle(frame, (x, y), 5, (0, 255, 0), -1)

                    for connection in mp.tasks.vision.HandLandmarksConnections.HAND_CONNECTIONS:
                        start = hand_landmarks[connection.start]
                        end = hand_landmarks[connection.end]
                        cv2.line(
                            frame,
                            (int(start.x * w), int(start.y * h)),
                            (int(end.x * w), int(end.y * h)),
                            (0, 255, 0),
                            2,
                        )

                    dedos = detectar_dedos(hand_landmarks)

                    if estado_anterior is not None:
                        for dedo, extendido in dedos.items():
                            if extendido != estado_anterior[dedo]:
                                comando = COMANDOS[dedo]["up"] if extendido else COMANDOS[dedo]["down"]
                                if enviar_comando(sock, comando):
                                    accion = "arriba" if extendido else "abajo"
                                    print(f"{NOMBRES[dedo]} {accion} - Servo enviado")
                                else:
                                    print("Error: Conexion perdida")
                                    cap.release()
                                    cv2.destroyAllWindows()
                                    sock.close()
                                    return

                    estado_anterior = dedos.copy()

                    y_offset = 30
                    for dedo in ["menique", "corazon", "indice", "pulgar"]:
                        extendido = dedos[dedo]
                        color = (0, 255, 0) if extendido else (0, 0, 255)
                        estado = "Arriba" if extendido else "Abajo"
                        cv2.putText(
                            frame,
                            f"{NOMBRES[dedo]}: {estado}",
                            (10, y_offset),
                            cv2.FONT_HERSHEY_SIMPLEX,
                            0.7,
                            color,
                            2,
                        )
                        y_offset += 30
            else:
                estado_anterior = None

            cv2.imshow("Control Mano Robot por Gesto", frame)

            if cv2.waitKey(1) & 0xFF == ord("q"):
                break
    except KeyboardInterrupt:
        print("\nInterrumpido por Ctrl+C")
    finally:
        enviar_comando(sock, "x")
        cap.release()
        cv2.destroyAllWindows()
        sock.close()
        print("Conexion cerrada")


if __name__ == "__main__":
    main()
#include <WiFi.h>
#include "config.h"

#define RX_PIN 5
#define TX_PIN 6
#define BAUD_RATE 9600
#define TCP_PORT 1001
#define TCP_TIMEOUT 5000

HardwareSerial mySerial(1);
WiFiServer server(TCP_PORT);
WiFiClient client;

void setup() {
    Serial.begin(115200);
    mySerial.begin(BAUD_RATE, SERIAL_8N1, RX_PIN, TX_PIN);

    Serial.println("Conectando a WiFi...");
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println();
    Serial.println("WiFi conectado");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());

    server.begin();
    Serial.println("Servidor TCP iniciado en puerto 1001");
}

void loop() {
    if (WiFi.status() != WL_CONNECTED) {
        Serial.println("WiFi desconectado, reconectando...");
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED) {
            delay(500);
        }
        Serial.println("WiFi reconectado");
    }

    if (!client || !client.connected()) {
        client = server.available();
        if (client) {
            Serial.println("Cliente conectado");
            client.setTimeout(TCP_TIMEOUT);
        }
    }

    if (client && client.connected()) {
        if (client.available()) {
            char comando = client.read();
            mySerial.print(comando);
            Serial.print("Enviado: ");
            Serial.println(comando);
        }

        if (!client.connected()) {
            Serial.println("Cliente desconectado");
            client.stop();
        }
    }
}
#define SERVO1_PIN 10
#define SERVO2_PIN 11
#define SERVO3_PIN 12
#define SERVO4_PIN 13
#define BAUD_RATE 9600
#define SERIAL1_RX 2
#define SERIAL1_TX 3
#define ANGULO_MIN 20
#define ANGULO_MAX 150
#define ANGULO2_MAX 45
#define ANGULO3_MIN 160
#define ANGULO3_MAX 140
#define ANGULO4_MIN 160
#define ANGULO4_MAX 100
#define PASO_ANGULO 1
#define INTERVALO_PASO 15

#include <SoftwareSerial.h>
#include <Servo.h>

SoftwareSerial serialESP(SERIAL1_RX, SERIAL1_TX);

Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;

int anguloActual[4] = {ANGULO_MIN, ANGULO_MIN, ANGULO_MIN, ANGULO_MIN};
int anguloObjetivo[4] = {ANGULO_MIN, ANGULO_MIN, ANGULO_MIN, ANGULO_MIN};
bool esperandoNumero = false;
unsigned long ultimoPaso = 0;

int anguloMaxServo[4] = {ANGULO_MAX, ANGULO_MIN, ANGULO3_MAX, ANGULO4_MAX};
int anguloMinServo[4] = {ANGULO_MIN, ANGULO2_MAX, ANGULO3_MIN, ANGULO4_MIN};

void mover_servo(int numero, int angulo) {
    Serial.print("Servo ");
    Serial.print(numero);
    Serial.print(" -> ");
    Serial.println(angulo);
    anguloObjetivo[numero - 1] = angulo;
}

void procesar_comando(char comando) {
    Serial.print("Comando: ");
    Serial.println(comando);
    if (!esperandoNumero) {
        if (comando >= '1' && comando <= '4') {
            mover_servo(comando - '0', anguloMaxServo[comando - '0' - 1]);
        } else if (comando == '0') {
            esperandoNumero = true;
        }
    } else {
        if (comando >= '1' && comando <= '4') {
            mover_servo(comando - '0', anguloMinServo[comando - '0' - 1]);
        }
        esperandoNumero = false;
    }
}

void actualizar_servos() {
    if (millis() - ultimoPaso < INTERVALO_PASO) return;
    ultimoPaso = millis();

    for (int i = 0; i < 4; i++) {
        if (anguloActual[i] < anguloObjetivo[i]) {
            anguloActual[i] += PASO_ANGULO;
            if (anguloActual[i] > anguloObjetivo[i]) {
                anguloActual[i] = anguloObjetivo[i];
            }
        } else if (anguloActual[i] > anguloObjetivo[i]) {
            anguloActual[i] -= PASO_ANGULO;
            if (anguloActual[i] < anguloObjetivo[i]) {
                anguloActual[i] = anguloObjetivo[i];
            }
        }
    }

    servo1.write(anguloActual[0]);
    servo2.write(anguloActual[1]);
    servo3.write(anguloActual[2]);
    servo4.write(anguloActual[3]);
}

void setup() {
    servo1.attach(SERVO1_PIN);
    servo2.attach(SERVO2_PIN);
    servo3.attach(SERVO3_PIN);
    servo4.attach(SERVO4_PIN);

    servo1.write(anguloMinServo[0]);
    servo2.write(anguloMinServo[1]);
    servo3.write(anguloMinServo[2]);
    servo4.write(anguloMinServo[3]);

    anguloActual[0] = anguloMinServo[0];
    anguloActual[1] = anguloMinServo[1];
    anguloActual[2] = anguloMinServo[2];
    anguloActual[3] = anguloMinServo[3];

    anguloObjetivo[0] = anguloMinServo[0];
    anguloObjetivo[1] = anguloMinServo[1];
    anguloObjetivo[2] = anguloMinServo[2];
    anguloObjetivo[3] = anguloMinServo[3];

    Serial.begin(BAUD_RATE);
    serialESP.begin(BAUD_RATE);
    delay(100);
    Serial.println("Arduino listo");
}

void loop() {
    if (Serial.available()) {
        procesar_comando(Serial.read());
    }
    if (serialESP.available()) {
        procesar_comando(serialESP.read());
    }
    actualizar_servos();
}