Control de una mano mecánica mediante reconocimiento de gestos con inteligencia artificial
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:
Cada dedo (meñique, corazón, índice, pulgar) controla un servo independiente. Los movimientos son suaves y progresivos, adaptándose al gesto detectado.
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.
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:
esp32.ino a la ESP32. Abre el monitor serie (115200 baud) y anota la IP que aparece al conectarse al WiFi.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.
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.
ANGULO_MAX en el código del Arduino.pip install opencv-python mediapipe.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();
}