
¿Te imaginas poder controlar las frecuencias de tu música simplemente moviendo las manos frente a la cámara? No es magia, es una combinación de visión por computador y procesamiento de audio. En esta guía vas a construir paso a paso un ecualizador gráfico que responde a los gestos de tus manos en tiempo real. Usaremos Python, MediaPipe para la detección de manos y PyAudio para manipular el sonido. El resultado: una interfaz táctil sin contacto que puede ser la base de un performance musical o una instalación interactiva.
¿Por qué MediaPipe y PyAudio?
MediaPipe es una librería de Google que incluye modelos ligeros de machine learning para detectar puntos clave del cuerpo humano, incluyendo manos y dedos. Es rápida y precisa, incluso con una webcam normal. Con pocas líneas de código podemos saber dónde están la punta del índice o la base de la muñeca. PyAudio, por otro lado, nos da acceso al flujo de audio en tiempo real: podemos leer la entrada del micrófono, aplicarle filtros y reproducir el resultado. La idea es asociar la posición vertical de cada dedo índice (derecho e izquierdo) a una banda de ecualización, como si estuvieras tocando un ecualizador físico.
Lo que necesitas
Antes de ponerte a escribir código, asegúrate de tener esto:

- Python 3.8 o superior instalado.
- Una webcam (la integrada del portátil funciona).
- Un micrófono (puede ser el mismo del portátil o uno externo).
- Conocimientos básicos de Python (funciones, bucles, instalación de paquetes).
También necesitarás instalar las dependencias. Vamos a ello.
Instalación de librerías
Abre tu terminal y ejecuta:
pip install opencv-python mediapipe numpy pyaudio scipyExplicación rápida:
opencv-python: para capturar el vídeo de la webcam y mostrar gráficos.mediapipe: detección de manos.numpy: manipulación de arrays (muestras de audio).pyaudio: interfaz con el sistema de audio.scipy: nos da el filtro paso bandabutterylfilterpara ecualizar.
Estructura del proyecto
El flujo es el siguiente: la webcam captura frames; MediaPipe detecta las manos y extrae coordenadas de los dedos índices; esas coordenadas se mapean a ganancias de dos bandas de frecuencia (graves y agudos, por ejemplo); el audio capturado por el micrófono se filtra con esas ganancias y se reproduce al instante. Todo en un bucle principal que se ejecuta hasta que presiones ‘q’.
Construiremos tres secciones de código:
- Configuración de audio (callback de PyAudio).
- Procesado de gestos (MediaPipe).
- Bucle principal que une ambas.
1. Configuración del audio en tiempo real con PyAudio
Crear un flujo de audio con callback nos permite modificar el audio sobre la marcha. Definimos dos bandas: low (graves) y high (agudos). La ganancia de cada banda vendrá de los gestos. Para simplificar, usaremos dos filtros Butterworth pasa banda, pero como queremos ecualizar, realmente aplicaríamos filtros que atenúen o amplifiquen. Sin embargo, para este ejemplo supongamos que la ganancia se aplica directamente a la mezcla de la señal original con las versiones filtradas. Vamos a implementar algo funcional aunque simplificado.
Primero el código base del stream:
import pyaudio
import numpy as np
import scipy.signal as sig
# Parámetros de audio
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 1024
p = pyaudio.PyAudio()
# Variables globales para las ganancias (controladas por gestos)
gain_low = 1.0 # 1.0 = sin cambio
gain_high = 1.0
def audio_callback(in_data, frame_count, time_info, status):
audio_data = np.frombuffer(in_data, dtype=np.int16).astype(np.float32)
# Separamos la señal en bandas (simplificado: solo dos filtros butterworth)
# Filtro paso bajo para graves (0-300 Hz)
b_low, a_low = sig.butter(4, 300/(RATE/2), btype='low')
low_signal = sig.lfilter(b_low, a_low, audio_data)
# Filtro paso alto para agudos (3000 Hz - Nyquist)
b_high, a_high = sig.butter(4, 3000/(RATE/2), btype='high')
high_signal = sig.lfilter(b_high, a_high, audio_data)
# Mezcla con ganancias
mixed = audio_data * 0.5 + gain_low * low_signal + gain_high * high_signal
# Recortar para evitar distorsión
mixed = np.clip(mixed, -32768, 32767)
return (mixed.astype(np.int16).tobytes(), pyaudio.paContinue)
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
frames_per_buffer=CHUNK,
stream_callback=audio_callback)
stream.start_stream()Esto captura el micrófono, aplica los filtros y reproduce. Las ganancias cambiarán con los gestos.
2. Detección de manos con MediaPipe
Usaremos la solución Hands de MediaPipe, que devuelve 21 landmarks por mano. Solo nos interesan las puntas de los índices (landmark 8). La altura de la mano determinará la ganancia. Para mayor intuición, mapearemos la coordenada y de la punta del índice a un rango de 0.5 a 2.0 (atenuar o amplificar).
import cv2
import mediapipe as mp
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils
cap = cv2.VideoCapture(0)Dentro de un bucle procesamos cada frame:
ret, frame = cap.read()
if not ret:
break
frame_rgb = cv2.cvtColor(cv2.flip(frame, 1), cv2.COLOR_BGR2RGB)
results = hands.process(frame_rgb)
if results.multi_hand_landmarks:
for idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
# Obtener coordenada y de la punta del índice (landmark 8)
index_finger_tip = hand_landmarks.landmark[8]
y = index_finger_tip.y # Valor entre 0 (arriba) y 1 (abajo)
# Determinar mano izquierda o derecha
if idx == 0: # primera mano detectada -> graves
gain_low = 2.0 - y * 1.5 # Mapeo: cuando y=0, gain=2.0; y=1, gain=0.5
else: # segunda mano -> agudos
gain_high = 2.0 - y * 1.5
mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)
cv2.imshow('Equalizador gestual', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
breakNota: el mapeo lineal simple funciona, pero puedes experimentar con curvas exponenciales para un control más fino.
3. Juntando todo: el código completo
Ahora combinamos audio y vídeo en un solo script. El truco está en ejecutar el bucle de OpenCV y dejar que el stream de audio corra en segundo plano con el callback. No olvides inicializar el stream antes del bucle. Aquí tienes el código completo:
import cv2
import mediapipe as mp
import numpy as np
import pyaudio
import scipy.signal as sig
import threading
# Variables globales
gain_low = 1.0
gain_high = 1.0
# Audio
FORMAT = pyaudio.paInt16
CHANNELS = 1
RATE = 44100
CHUNK = 1024
p = pyaudio.PyAudio()
def audio_callback(in_data, frame_count, time_info, status):
global gain_low, gain_high
audio_data = np.frombuffer(in_data, dtype=np.int16).astype(np.float32)
# Filtros
b_low, a_low = sig.butter(4, 300/(RATE/2), btype='low')
low_signal = sig.lfilter(b_low, a_low, audio_data)
b_high, a_high = sig.butter(4, 3000/(RATE/2), btype='high')
high_signal = sig.lfilter(b_high, a_high, audio_data)
mixed = audio_data * 0.5 + gain_low * low_signal + gain_high * high_signal
mixed = np.clip(mixed, -32768, 32767)
return (mixed.astype(np.int16).tobytes(), pyaudio.paContinue)
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
output=True,
frames_per_buffer=CHUNK,
stream_callback=audio_callback)
stream.start_stream()
# MediaPipe y OpenCV
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(
static_image_mode=False,
max_num_hands=2,
min_detection_confidence=0.7,
min_tracking_confidence=0.5)
mp_drawing = mp.solutions.drawing_utils
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
frame = cv2.flip(frame, 1)
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = hands.process(frame_rgb)
if results.multi_hand_landmarks:
for idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
index_tip = hand_landmarks.landmark[8]
y = index_tip.y
gain = 2.0 - y * 1.5
if idx == 0:
gain_low = gain
else:
gain_high = gain
mp_drawing.draw_landmarks(frame, hand_landmarks, mp_hands.HAND_CONNECTIONS)
# Mostrar valores en pantalla
cv2.putText(frame, f'Low: {gain_low:.2f} High: {gain_high:.2f}', (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0,255,0), 2)
cv2.imshow('Gesture EQ', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# Limpieza
cap.release()
cv2.destroyAllWindows()
stream.stop_stream()
stream.close()
p.terminate()Ajustes y posibles mejoras
Este prototipo te da un control básico con dos manos. Puedes extenderlo a más bandas usando más dedos o gestos diferentes. Por ejemplo, MediaPipe detecta hasta 21 puntos por mano; podrías mapear cada dedo a una banda (graves, medios, agudos) usando alturas relativas. O incluso usar gestos de pinza para cambiar la frecuencia central de los filtros.
Un punto a tener en cuenta: el procesamiento de audio debe ser rápido. Si el callback tarda más que el tamaño del chunk, tendrás cortes. Para evitar eso, mantén los filtros lo más simple posible y evita cálculos pesados dentro del callback. Aquí, los filtros butter se diseñan cada vez, lo cual es ineficiente. Lo suyo es calcular los coeficientes una vez fuera del callback y solo aplicar el filtro.
# Mejor: calcular coeficientes una vez
b_low, a_low = sig.butter(4, 300/(RATE/2), btype='low')
b_high, a_high = sig.butter(4, 3000/(RATE/2), btype='high')
def audio_callback(in_data, frame_count, time_info, status):
# ...
¿Pérdida de calidad?
Al mezclar señales filtradas siempre existe el riesgo de recorte (clipping) si las ganancias son muy elevadas. Hemos incluido un np.clip para evitarlo, pero puedes añadir un control de ganancia automático. También ten en cuenta que la latencia es inevitable, pero trabajando con chunks pequeños (1024 muestras a 44100 Hz son unos 23 ms) se siente casi instantáneo.
Conclusión
Acabas de construir un ecualizador gráfico que puedes controlar con las manos. Este proyecto es la puerta de entrada a interfaces musicales gestuales: imagina un theremin visual, un mezclador de DJ sin mandos, o una instalación artística donde los visitantes modifiquen el paisaje sonoro con sus movimientos. Te animo a experimentar con más bandas, distintos tipos de filtros o incluso visualizaciones más llamativas con OpenCV. Si te quedas atascado, revisa la documentación de MediaPipe y PyAudio, y sobre todo, ¡diviértete moldeando el sonido en el aire!
Preguntas frecuentes
¿Necesito una tarjeta de sonido especial?
No. Con el audio integrado de tu placa base o portátil es suficiente. Solo asegúrate de que los drivers estén actualizados.
¿Funciona con manos muy rápidas o poca luz?
El modelo de MediaPipe es robusto, pero la iluminación influye. Procura un fondo contrastado y buena luz para una detección estable.
¿Puedo usar esto en directo?
Sí, la latencia es lo bastante baja para presentaciones en vivo. Configura un chunk más pequeño (256 o 512) si tu máquina puede manejarlo para reducir aún más el retardo.
¿Qué hago si el audio suena distorsionado?
Baja las ganancias o ajusta el mapeo de la coordenada Y a un rango menor. También puedes modificar el factor de mezcla 0.5 para la señal original.
