página web “Partículas Interactivas con Manos”
El HTML usa MediaPipe Hands para detectar la mano en la webcam y, a partir de sus “landmarks”, calcula apertura (mano abierta/cerrada) y posición (x,y) de la mano para controlar el sistema de partículas hecho con Three.js. [pueden verse ejemplos parecidos en fritz y youtube]
Librerías que intervienen
three (import map): se usa para toda la parte 3D (escena, cámara, partículas). ver fuente realacionada con HTML en developer.mozilla
@mediapipe/camera_utils: da la clase Camera que gestiona la captura de vídeo de la webcam y llama a onFrame. ver fuente realacionada en fritz
@mediapipe/hands: da la clase Hands, que ejecuta el modelo de IA de detección de manos y entrega multiHandLandmarks. ver fuente realacionada en youtube y fritz
@mediapipe/control_utils y drawing_utils podrían usarse para UI y dibujo, pero en este código solo se cargan; la lógica de gestos la implementas tú en onResults.ver fuente realacionada en fritz
Variables que representan el gesto
En la sección // --- 5. MEDIAPIPE HAND TRACKING --- se declaran las variables de estado que resumen el gesto:
handOpenness: valor en [0,1] que representa qué tan abierta está la mano (0 cerrada, 1 abierta).
handX, handY: posición de la mano normalizada al sistema de coordenadas de Three.js, de −1 a 1.
isHandDetected: indica si hay una mano detectada (para cambiar estado de la UI).
Estas variables son las que luego se usan en el bucle de animación para escalar, agitar y rotar las partículas.
Función onResults(results): interpretar landmarks
Esta función es el “callback” que MediaPipe Hands llama cada vez que procesa un frame:
- Comprobación de mano detectada
- Si
results.multiHandLandmarks tiene al menos una mano, se marca isHandDetected = true, se pone el punto de estado en verde y el texto “Mano detectada”.
- Si no hay manos, se vuelve a “Buscando mano…” y se relaja la apertura hacia 1 (mano abierta por defecto).
- Lectura de landmarks
landmarks = results.multiHandLandmarks[0] toma la primera mano.
- Usa índices estándar de MediaPipe:
landmarks[4]: punta del pulgar (thumbTip).
landmarks[8]: punta del índice (indexTip).
landmarks[0]: muñeca (wrist).fritz
- Cálculo de apertura de mano (
handOpenness)- Calcula la distancia euclidiana en 2D entre pulgar e índice: js
const distance = Math.sqrt( Math.pow(thumbTip.x - indexTip.x, 2) + Math.pow(thumbTip.y - indexTip.y, 2) ); - Normaliza esa distancia a un rango 0–1: js
let openness = (distance - 0.05) * 5; openness = Math.max(0, Math.min(1, openness)); - Suaviza la transición con un lerp para evitar jitter: js
handOpenness += (openness - handOpenness) * 0.1;
Resultado: si pulgar e índice están cerca, distance es pequeña → mano “cerrada” (handOpenness bajo); si están separados, mano “abierta”.fritz
- Cálculo de posición de mano (
handX, handY)- Toma la muñeca como punto de referencia de la mano:
wrist. - Convierte coordenadas MediaPipe [0,1] a sistema Three.js [−1,1] e invierte Y: js
const targetX = (wrist.x - 0.5) * 2; // 0→-1, 1→1 const targetY = -(wrist.y - 0.5) * 2; // invierte eje vertical - Aplica también suavizado: js
handX += (targetX - handX) * 0.1; handY += (targetY - handY) * 0.1;
Esto da una coordenada “suave” de la mano en el espacio de cámara que luego se usa para rotar la nube de partículas.
Objeto Hands y cámara MediaPipe
Debajo se instancia y configura el detector de manos:
const hands = new Hands({ locateFile: (file) => ... }): indica a MediaPipe dónde cargar los archivos del modelo desde el CDN.fritz
hands.setOptions({ maxNumHands: 1, modelComplexity: 1, ... }): limita a 1 mano y fija umbrales de detección y seguimiento.
hands.onResults(onResults): registra la función anterior como callback de resultados.
La cámara se configura con Camera de camera_utils:
jsconst cameraUtils = new Camera(videoElement, {
onFrame: async () => {
await hands.send({ image: videoElement });
},
width: 640,
height: 480
});
cameraUtils.start();
Camera captura de la webcam al <video> oculto (#input-video).
- En cada frame llama a
onFrame, que a su vez envía la imagen actual a hands.send, disparando el pipeline de MediaPipe y finalmente onResults.fritz
Uso de handOpenness, handX, handY en las partículas
En el bucle animate() se conectan las variables de gesto con la animación:
baseScale = 0.2 + (handOpenness * 0.8):
- Mano abierta (
handOpenness≈1) → escala grande, la figura se expande.
- Mano cerrada (
handOpenness≈0) → escala pequeña, la figura se contrae.
noiseAmplitude = (1.0 - handOpenness) * 0.3:
- Mano cerrada → ruido alto → partículas vibran, efecto “energía”.
- Mano abierta → ruido bajo → partículas más estables.
- Rotación según posición: js
particles.rotation.y = handX * 1.5 + time * 0.1; particles.rotation.x = handY * 1.5; Mover la mano horizontal/verticalmente rota el conjunto de partículas, y se suma una pequeña rotación automática con el tiempo.
- En el bucle por partícula, se hace “morphing” suave hacia
targetPositions escaladas por baseScale, y se suma ruido aleatorio proporcional a noiseAmplitude cuando la mano está cerrada.
Con todo esto, el flujo queda así: webcam → Camera → Hands (MediaPipe) → onResults (landmarks → handOpenness, handX, handY) → animate() (aplica esos valores a escala, ruido y rotación de las partículas).youtubefritz
El siguiente paso podría ser añadir otra dimensión al gesto (por ejemplo, usar la altura de la mano o el número de dedos extendidos) para cambiar entre formas (corazón/flor/Saturno/fuegos) sin tocar el ratón.
- https://fritz.ai/introduction-to-hand-detection-in-the-browser-with-handtrack-js-and-tensorflow/
- https://www.youtube.com/watch?v=eI-d5yuPeJw
- https://developer.mozilla.org/es/docs/Learn_web_development/Core/Structuring_content/Basic_HTML_syntax
- VirtualWorlds.html
Aunque creo que la verdadera continuación de este capítulo podría estar en GitHub – Viral-Doshi/Gesture-Controlled-Virtual-Mouse: Virtually controlling computer using hand-gestures and voice commands. Using MediaPipe, OpenCV Python.
Recuerda que MediaPipe es un framework de Google para machine learning en el navegador que permite procesar video en tiempo real con JavaScript, detectando gestos, rostros, poses y objetos sin necesidad de servidores potentes. Funciona con la cámara web o videos, usando modelos preentrenados que corren directamente en el navegador gracias a WebGL y WebAssembly.
Instalación básica
Para empezar, crea un proyecto con npm e instala los paquetes necesarios. Por ejemplo, para detección de rostros o manos:
bashnpm install @mediapipe/tasks-vision @mediapipe/camera_utils @mediapipe/drawing_utils
Estos paquetes incluyen utilidades para la cámara, dibujo de landmarks (puntos clave) y soluciones de visión. Puedes usar CDN para prototipos rápidos sin instalar nada.
Ejemplo: Detección de manos
Imagina una app que dibuja los puntos clave de tus manos en un canvas mientras mueves los dedos frente a la cámara. El flujo es: accede a la cámara, envía frames al modelo de MediaPipe y dibuja los resultados en cada frame.
xml<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"></script>
</head>
<body>
<video id="video"></video>
<canvas id="canvas"></canvas>
<script>
const video = document.getElementById('video');
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const hands = new Hands({locateFile: (file) => `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`});
hands.setOptions({maxNumHands: 2});
hands.onResults((results) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (results.multiHandLandmarks) {
for (const landmarks of results.multiHandLandmarks) {
drawConnectors(ctx, landmarks, HAND_CONNECTIONS);
drawLandmarks(ctx, landmarks);
}
}
});
new Camera(video, {onFrame: () => hands.send({image: video})}).start();
</script>
</body>
</html>
Este código inicia la cámara, detecta hasta 2 manos por frame y dibuja líneas/conexiones entre los 21 puntos clave de cada mano.
Usos educativos y prácticos
Úsalo para interfaces gestuales (controla slides con manos), accesibilidad (seguimiento ocular para discapacidades), AR simple (filtros faciales) o análisis de pose en e-sports. En educación, crea demos interactivas para enseñar visión por computadora sin hardware especial. Prueba ejemplos en MediaPipe Studio para experimentar sin código.
MediaPipe Holistic combina tres modelos en uno: detección facial (478 puntos), seguimiento de manos (21 puntos cada una) y estimación de pose (33 puntos corporales). Procesa video en tiempo real en el navegador, ideal para análisis completo del cuerpo, gestos y expresiones faciales.google+1
Paso 1: Preparar el entorno
Crea un archivo HTML básico con un video y canvas para mostrar la cámara y los resultados.
xml<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/camera_utils/camera_utils.js"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/drawing_utils/drawing_utils.js"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@mediapipe/holistic/holistic.js"
crossorigin="anonymous"></script>
</head>
<body>
<video id="video" style="display:none;"></video>
<canvas id="canvas" width="640" height="480"></canvas>
</body>
</html>
Usa CDN para prototipos rápidos. Para producción, instala via npm: npm i @mediapipe/holistic @mediapipe/camera_utils @mediapipe/drawing_utils.google+1
Paso 2: Inicializar el modelo Holistic
En un <script> al final del body, crea la instancia y configura opciones.
javascriptconst videoElement = document.getElementById('video');
const canvasElement = document.getElementById('canvas');
const canvasCtx = canvasElement.getContext('2d');
// Crear instancia de Holistic
const holistic = new Holistic({
locateFile: (file) => {
return `https://cdn.jsdelivr.net/npm/@mediapipe/holistic/${file}`;
}
});
// Configurar opciones (precisión media para mejor rendimiento)
holistic.setOptions({
modelComplexity: 1,
smoothLandmarks: true,
enableSegmentation: true,
smoothSegmentation: true,
refineFaceLandmarks: true,
minDetectionConfidence: 0.5,
minTrackingConfidence: 0.5
});
modelComplexity: 1 equilibra precisión y velocidad. refineFaceLandmarks: true añade 468 puntos extra en la cara para iris y labios.mediapipe.readthedocs+1
Paso 3: Manejar resultados y dibujar
Define la función que procesa cada frame y dibuja los landmarks.
javascriptholistic.onResults(onResults);
function onResults(results) {
// Limpiar canvas
canvasCtx.save();
canvasCtx.clearRect(0, 0, canvasElement.width, canvasElement.height);
canvasCtx.drawImage(results.image, 0, 0, canvasElement.width, canvasElement.height);
// Dibujar pose (cuerpo)
if (results.poseLandmarks) {
drawConnectors(canvasCtx, results.poseLandmarks, POSE_CONNECTIONS, {color: '#00FF00', lineWidth: 4});
drawLandmarks(canvasCtx, results.poseLandmarks, {color: '#FF0000', lineWidth: 2});
}
// Dibujar manos izquierda y derecha
if (results.leftHandLandmarks) {
drawConnectors(canvasCtx, results.leftHandLandmarks, HAND_CONNECTIONS, {color: '#CC0000', lineWidth: 5});
drawLandmarks(canvasCtx, results.leftHandLandmarks, {color: '#00FF00', lineWidth: 1});
}
if (results.rightHandLandmarks) {
drawConnectors(canvasCtx, results.rightHandLandmarks, HAND_CONNECTIONS, {color: '#00CC00', lineWidth: 5});
drawLandmarks(canvasCtx, results.rightHandLandmarks, {color: '#FF0000', lineWidth: 1});
}
// Dibujar rostro
if (results.faceLandmarks) {
drawConnectors(canvasCtx, results.faceLandmarks, FACEMESH_TESSELATION, {color: '#C0C0C070', lineWidth: 1});
drawConnectors(canvasCtx, results.faceLandmarks, FACEMESH_RIGHT_EYE, {color: '#FF3030', lineWidth: 1});
drawConnectors(canvasCtx, results.faceLandmarks, FACEMESH_RIGHT_IRIS, {color: '#FF3030', lineWidth: 1});
}
canvasCtx.restore();
}
Los constantes como POSE_CONNECTIONS, HAND_CONNECTIONS, FACEMESH_TESSELATION vienen incluidas en @mediapipe/drawing_utils.google+1
Paso 4: Iniciar la cámara y bucle de procesamiento
Conecta la cámara web y envía frames continuamente.
javascriptconst camera = new Camera(videoElement, {
onFrame: async () => {
await holistic.send({image: videoElement});
},
width: 640,
height: 480
});
camera.start();
Ejecuta todo el código y abre en el navegador. MediaPipe detectará automáticamente tu pose, manos y rostro en tiempo real, dibujando conexiones de colores diferentes para cada parte.mediapipe.readthedocs+1
Qué mas podemos hacer
- Accede a coordenadas:
results.poseLandmarks[11].x (hombro derecho).
- Añade segmentación: usa
results.segmentationMask para fondos virtuales.
- Optimiza: baja
modelComplexity a 0 para móviles.
Prueba en MediaPipe Studio primero para ver resultados sin código.google+1
- https://ai.google.dev/edge/mediapipe/solutions/guide?hl=es-419
- https://ai.google.dev/edge/mediapipe/solutions/setup_web
- https://mediapipe.readthedocs.io/en/latest/getting_started/javascript.html
- https://ai.google.dev/edge/mediapipe/solutions/studio?hl=es-419