Filtro de Pokémon con Canvas y face detection
Resumen
¿Te imaginas convertir cualquier rostro en un Pokémon usando solo JavaScript, HTML y Canvas? Aprenderás cómo construir un filtro aleatorio que detecta caras y superpone imágenes PNG transparentes sobre ellas, ideal si estás reforzando proyectos con face detection en el navegador.
Esta solución parte del primer reto del proyecto: agregar un filtro propio al detector de rostros y entender cómo encajan el método draw, el contexto del canvas y la lógica para asignar imágenes aleatorias a cada cara detectada.
¿Cómo se estructura un filtro con face detector y canvas?
Todo filtro depende de un método central llamado draw, que recibe el rostro detectado, su index y el context del canvas donde se dibuja.
Dentro de ese método tomas los puntos que entrega el face detector y eliges los que necesitas. En este caso, lo más útil fue la caja del rostro, ese cuadro que enmarca la cara y te da posición y tamaño en una sola estructura.
Con esa caja puedes invocar drawImage del canvas y pasarle cuatro datos clave:
- La imagen PNG con transparencia que vas a superponer.
- La coordenada izquierda, que sale de la caja del rostro.
- La coordenada superior o top, también de la caja.
- El ancho y alto del rostro detectado.
¿Qué hace el método draw en un filtro de face detection? Es el método que se ejecuta por cada rostro detectado. Recibe el rostro, su index y el context del canvas, y ahí decides qué dibujar y dónde.
¿Por qué necesitas un offset al dibujar la imagen sobre el rostro?
Las cabezas no miden lo mismo en todas las personas, así que dibujar la imagen exactamente del tamaño de la caja se queda corto.
La solución fue crear un offset fijo de 80, un número mágico ajustado a ojo que agranda la imagen lo suficiente para tapar por completo el rostro. Funciona muy bien con la imagen elegida, pero si cambias el PNG es probable que necesites más o menos.
Existen formas más elegantes de calcular ese ajuste, como un porcentaje proporcional al ancho del rostro, pero para este ejemplo el valor fijo cumple su trabajo.
¿Cómo asignar una imagen aleatoria a cada rostro detectado?
Aquí viene lo interesante: si generas una imagen random dentro de draw, cada frame mostrará una cara distinta y el filtro parpadeará sin parar.
La solución fue crear un método llamado getImageForFace que recuerda qué imagen corresponde a cada posición. Como draw entrega un index, guardas la imagen del index 0 una sola vez, la del index 1 otra vez, y así con cada rostro adicional.
Para escoger la imagen aleatoria al inicio se usaron dos funciones combinadas:
Math.randompara generar un número pseudoaleatorio entre 0 y 1.Math.floorpara redondearlo hacia abajo y convertirlo en un índice válido del arreglo de imágenes.
Esa selección ocurre en el constructor de la clase, donde el DOM ya tiene precargadas todas las imágenes PNG de Pokémon disponibles.
¿Para qué sirve Math.floor con Math.random? Math.random te da un decimal entre 0 y 1, y Math.floor lo convierte en entero. Juntos te permiten elegir una posición aleatoria dentro de un arreglo.
¿Cómo se ve el flujo completo del filtro Random Pokémon?
Uniendo todas las piezas, el filtro funciona así:
- En el constructor, cargas múltiples imágenes PNG de Pokémon en el DOM.
- Eliges una imagen aleatoria con
Math.floor(Math.random()). - En
draw, usas el index del rostro para recordar qué imagen le corresponde. - Dibujas con
drawImageusando la caja del rostro más el offset de 80.
El resultado es un filtro estable, donde cada cara mantiene su Pokémon asignado mientras la persona se mueve frente a la cámara.
¿Cómo revisar el código en un pull request de GitHub?
Para estudiar la solución, lo ideal es abrir el pull request número uno del repositorio, llamado Agregar random Pokémon.
Ahí encontrarás 12 archivos cambiados, aunque la mayoría son las imágenes PNG con transparencia (entre 8 y 10 archivos de caras de Pokémon). El código relevante está en la clase del filtro, donde se concentra la lógica de draw, getImageForFace y la selección aleatoria.
Hacer checkout a ese branch te permite ver el filtro en acción y experimentar con tus propias imágenes para entender cómo cambia el comportamiento al ajustar el offset o agregar más PNGs al arreglo.
El siguiente paso del reto es ir más allá de las imágenes estáticas y trabajar con video, porque una imagen siempre se ve igual y puede volverse repetitiva. ¿Qué cambios harías tú para que el filtro funcione con video en lugar de PNG? Déjalo en los comentarios.