Detección de rostros con TensorFlow.js
Esta guía presenta la integración de TensorFlow.js con Docker para realizar la detección de rostros. En esta guía, explorarás cómo:
- Ejecutar una aplicación de TensorFlow.js contenedorizada usando Docker.
- Implementar la detección de rostros en una aplicación web con TensorFlow.js.
- Construir un Dockerfile para una aplicación web de TensorFlow.js.
- Usar Docker Compose para el desarrollo y la actualización de aplicaciones en tiempo real.
- Compartir tu imagen de Docker en Docker Hub para facilitar el despliegue y ampliar el alcance.
Agradecimiento
Docker desea agradecer a Harsh Manvar por su contribución a esta guía.
Requisitos previos
- Has instalado la última versión de Docker Desktop.
- Tienes un cliente de Git. Los ejemplos de esta guía utilizan un cliente de Git basado en la línea de comandos, pero puedes utilizar cualquier cliente.
¿Qué es TensorFlow.js?
TensorFlow.js es una librería de JavaScript de código abierto para el aprendizaje automático (machine learning) con la que puedes entrenar y desplegar modelos de ML en el navegador o en Node.js. Soporta la creación de nuevos modelos desde cero o el uso de modelos preentrenados, facilitando una amplia gama de aplicaciones de ML directamente en entornos web. TensorFlow.js ofrece una computación eficiente, haciendo que las tareas complejas de ML sean accesibles para los desarrolladores web sin necesidad de tener una experiencia profunda en ML.
¿Por qué usar TensorFlow.js y Docker juntos?
- Consistencia del entorno y despliegue simplificado: Docker empaqueta las aplicaciones de TensorFlow.js y sus dependencias en contenedores, asegurando ejecuciones consistentes en todos los entornos y simplificando el despliegue.
- Desarrollo eficiente y escalado sencillo: Docker mejora la eficiencia del desarrollo con características como la recarga en caliente (hot reloading) y facilita el escalado sencillo de aplicaciones de TensorFlow.js utilizando herramientas de orquestación como Kubernetes.
- Aislamiento y seguridad mejorada: Docker aísla las aplicaciones de TensorFlow.js en entornos seguros, minimizando los conflictos y las vulnerabilidades de seguridad mientras ejecuta aplicaciones con permisos limitados.
Obtener y ejecutar la aplicación de ejemplo
En una terminal, clona la aplicación de ejemplo con el siguiente comando.
$ git clone https://github.com/harsh4870/TensorJS-Face-Detection
Después de clonar la aplicación, notarás que tiene un Dockerfile. Con este Dockerfile puedes construir y ejecutar la aplicación localmente sin necesidad de instalar nada más que Docker.
Antes de ejecutar la aplicación como un contenedor, debes construir la imagen. Ejecuta el siguiente comando dentro del directorio TensorJS-Face-Detection para construir una imagen llamada face-detection-tensorjs.
$ docker build -t face-detection-tensorjs .
El comando construye la aplicación en una imagen. Dependiendo de tu conexión de red, puede tardar varios minutos en descargar los componentes necesarios la primera vez que ejecutes el comando.
Para ejecutar la imagen como un contenedor, ejecuta el siguiente comando en una terminal.
$ docker run -p 80:80 face-detection-tensorjs
El comando ejecuta el contenedor y mapea el puerto 80 del contenedor al puerto 80 de tu sistema.
Una vez que la aplicación se esté ejecutando, abre un navegador web y accede a ella en http://localhost:80. Es posible que debas otorgar acceso a tu cámara web para la aplicación.
En la aplicación web, puedes cambiar el motor (backend) para usar uno de los siguientes:
- WASM
- WebGL
- CPU
Para detener la aplicación, presiona ctrl+c en la terminal.
Acerca de la aplicación
La aplicación de ejemplo realiza la detección de rostros en tiempo real utilizando MediaPipe, un framework completo para construir canalizaciones de aprendizaje automático multimodales. Utiliza específicamente el modelo BlazeFace, un modelo ligero para detectar rostros en imágenes.
En el contexto de TensorFlow.js o frameworks de aprendizaje automático basados en web similares, se pueden utilizar los motores WASM, WebGL y CPU para ejecutar operaciones. Cada uno de estos motores utiliza diferentes recursos y tecnologías disponibles en los navegadores modernos y tiene sus propias fortalezas y limitaciones. Las siguientes secciones son un breve desglose de los diferentes motores.
WASM
WebAssembly (WASM) es un lenguaje de bajo nivel, similar al ensamblador, con un formato binario compacto que se ejecuta a una velocidad cercana a la nativa en los navegadores web. Permite que el código escrito en lenguajes como C/C++ se compile en un binario que se puede ejecutar en el navegador.
Es una buena opción cuando se requiere un alto rendimiento y el motor WebGL no es compatible, o cuando deseas un rendimiento constante en todos los dispositivos sin depender de la GPU.
WebGL
WebGL es una API de navegador que permite el uso acelerado por GPU de la física y el procesamiento de imágenes y efectos como parte del canvas de la página web.
WebGL es muy adecuado para operaciones que se pueden paralelizar y que pueden beneficiarse significativamente de la aceleración por GPU, como las multiplicaciones de matrices y las convoluciones que se encuentran comúnmente en los modelos de aprendizaje profundo.
CPU
El motor CPU utiliza la ejecución pura de JavaScript, empleando la unidad central de procesamiento (CPU) del dispositivo. Este motor es el más compatible universalmente y sirve como alternativa cuando los motores WebGL o WASM no están disponibles o no son adecuados.
Explorar el código de la aplicación
Explora el propósito de cada archivo y su contenido en las siguientes secciones.
El archivo index.html
El archivo index.html sirve como frontend para la aplicación web que utiliza TensorFlow.js para la detección de rostros en tiempo real a partir de la señal de video de la cámara web. Incorpora varias tecnologías y librerías para facilitar el aprendizaje automático directamente en el navegador. Utiliza varias librerías de TensorFlow.js, incluyendo:
- tfjs-core y tfjs-converter para la funcionalidad principal de TensorFlow.js y la conversión de modelos.
- tfjs-backend-webgl, tfjs-backend-cpu y el script tf-backend-wasm para las diferentes opciones de motor de procesamiento que TensorFlow.js puede utilizar. Estos motores permiten que la aplicación realice tareas de aprendizaje automático de manera eficiente aprovechando las capacidades del hardware del usuario.
- La librería BlazeFace, un modelo de TensorFlow para la detección de rostros.
También utiliza las siguientes librerías adicionales:
- dat.GUI para crear una interfaz gráfica que interactúe con la configuración de la aplicación en tiempo real, como el cambio entre motores de TensorFlow.js.
- Stats.min.js para mostrar métricas de rendimiento (como FPS) para monitorear la eficiencia de la aplicación durante su funcionamiento.
<style>
body {
margin: 25px;
}
.true {
color: green;
}
.false {
color: red;
}
#main {
position: relative;
margin: 50px 0;
}
canvas {
position: absolute;
top: 0;
left: 0;
}
#description {
margin-top: 20px;
width: 600px;
}
#description-title {
font-weight: bold;
font-size: 18px;
}
</style>
<body>
<div id="main">
<video
id="video"
playsinline
style="
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
width: auto;
height: auto;
"
></video>
<canvas id="output"></canvas>
<video
id="video"
playsinline
style="
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
visibility: hidden;
width: auto;
height: auto;
"
></video>
</div>
</body>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-core.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-converter.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-webgl.js"></script>
<script src="https://unpkg.com/@tensorflow/[email protected]/dist/tf-backend-cpu.js"></script>
<script src="./tf-backend-wasm.js"></script>
<script src="https://unpkg.com/@tensorflow-models/[email protected]/dist/blazeface.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.6/dat.gui.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/r16/Stats.min.js"></script>
<script src="./index.js"></script>El archivo index.js
El archivo index.js gestiona la lógica de detección facial. Muestra varios conceptos avanzados en el desarrollo web y la integración del aprendizaje automático. A continuación se presenta un desglose de algunos de sus componentes y funcionalidades clave:
- Stats.js: El script comienza creando una instancia de Stats para monitorear y mostrar la tasa de fotogramas (FPS) de la aplicación en tiempo real. Esto es útil para el análisis de rendimiento, especialmente al probar el impacto de los diferentes motores de TensorFlow.js en la velocidad de la aplicación.
- TensorFlow.js: Con la interfaz gráfica proporcionada por dat.GUI, los usuarios pueden cambiar entre diferentes motores de procesamiento (wasm, webgl y cpu) para TensorFlow.js. Cambiar el motor puede afectar el rendimiento y la compatibilidad según el dispositivo y el navegador. La función
addFlagLabelscomprueba y muestra dinámicamente si SIMD (Single Instruction, Multiple Data) y el multiprocesamiento (multithreading) son compatibles, lo cual es relevante para la optimización del rendimiento en el motor WASM. - Función setupCamera: Inicializa la cámara web del usuario utilizando la API web MediaDevices. Configura el flujo de video para no incluir audio y utilizar la cámara frontal (facingMode: 'user'). Una vez cargados los metadatos del video, resuelve una promesa con el elemento de video, que luego se utiliza para la detección de rostros.
- BlazeFace: El núcleo de esta aplicación es la función
renderPrediction, que realiza la detección de rostros en tiempo real utilizando el modelo BlazeFace, un modelo ligero para detectar rostros en imágenes. La función llama amodel.estimateFacesen cada fotograma de animación para detectar rostros desde la señal de video. Para cada rostro detectado, dibuja un rectángulo rojo alrededor de la cara y puntos azules para los puntos de referencia faciales (landmarks) en un canvas superpuesto al video.
const stats = new Stats();
stats.showPanel(0);
document.body.prepend(stats.domElement);
let model, ctx, videoWidth, videoHeight, video, canvas;
const state = {
backend: "wasm",
};
const gui = new dat.GUI();
gui
.add(state, "backend", ["wasm", "webgl", "cpu"])
.onChange(async (backend) => {
await tf.setBackend(backend);
addFlagLabels();
});
async function addFlagLabels() {
if (!document.querySelector("#simd_supported")) {
const simdSupportLabel = document.createElement("div");
simdSupportLabel.id = "simd_supported";
simdSupportLabel.style = "font-weight: bold";
const simdSupported = await tf.env().getAsync("WASM_HAS_SIMD_SUPPORT");
simdSupportLabel.innerHTML = `SIMD supported: <span class=${simdSupported}>${simdSupported}<span>`;
document.querySelector("#description").appendChild(simdSupportLabel);
}
if (!document.querySelector("#threads_supported")) {
const threadSupportLabel = document.createElement("div");
threadSupportLabel.id = "threads_supported";
threadSupportLabel.style = "font-weight: bold";
const threadsSupported = await tf
.env()
.getAsync("WASM_HAS_MULTITHREAD_SUPPORT");
threadSupportLabel.innerHTML = `Threads supported: <span class=${threadsSupported}>${threadsSupported}</span>`;
document.querySelector("#description").appendChild(threadSupportLabel);
}
}
async function setupCamera() {
video = document.getElementById("video");
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: { facingMode: "user" },
});
video.srcObject = stream;
return new Promise((resolve) => {
video.onloadedmetadata = () => {
resolve(video);
};
});
}
const renderPrediction = async () => {
stats.begin();
const returnTensors = false;
const flipHorizontal = true;
const annotateBoxes = true;
const predictions = await model.estimateFaces(
video,
returnTensors,
flipHorizontal,
annotateBoxes,
);
if (predictions.length > 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < predictions.length; i++) {
if (returnTensors) {
predictions[i].topLeft = predictions[i].topLeft.arraySync();
predictions[i].bottomRight = predictions[i].bottomRight.arraySync();
if (annotateBoxes) {
predictions[i].landmarks = predictions[i].landmarks.arraySync();
}
}
const start = predictions[i].topLeft;
const end = predictions[i].bottomRight;
const size = [end[0] - start[0], end[1] - start[1]];
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
ctx.fillRect(start[0], start[1], size[0], size[1]);
if (annotateBoxes) {
const landmarks = predictions[i].landmarks;
ctx.fillStyle = "blue";
for (let j = 0; j < landmarks.length; j++) {
const x = landmarks[j][0];
const y = landmarks[j][1];
ctx.fillRect(x, y, 5, 5);
}
}
}
}
stats.end();
requestAnimationFrame(renderPrediction);
};
const setupPage = async () => {
await tf.setBackend(state.backend);
addFlagLabels();
await setupCamera();
video.play();
videoWidth = video.videoWidth;
videoHeight = video.videoHeight;
video.width = videoWidth;
video.height = videoHeight;
canvas = document.getElementById("output");
canvas.width = videoWidth;
canvas.height = videoHeight;
ctx = canvas.getContext("2d");
ctx.fillStyle = "rgba(255, 0, 0, 0.5)";
model = await blazeface.load();
renderPrediction();
};
setupPage();El archivo tf-backend-wasm.js
El archivo tf-backend-wasm.js forma parte de la librería TensorFlow.js. Contiene la lógica de inicialización para el motor WASM de TensorFlow.js, algunas utilidades para interactuar con los binarios WASM y funciones para establecer rutas personalizadas para los binarios WASM.
El archivo tfjs-backend-wasm-simd.wasm
El archivo tfjs-backend-wasm-simd.wasm forma parte de la librería TensorFlow.js. Es un binario WASM que se utiliza para el motor de WebAssembly, optimizado específicamente para utilizar instrucciones SIMD (Single Instruction, Multiple Data).
Explorar el Dockerfile
En un proyecto basado en Docker, el Dockerfile sirve como el recurso fundamental para construir el entorno de tu aplicación.
Un Dockerfile es un archivo de texto que indica a Docker cómo crear una imagen del entorno de tu aplicación. Una imagen contiene todo lo necesario para ejecutar la aplicación, como archivos, paquetes y herramientas.
El siguiente es el Dockerfile para este proyecto.
FROM nginx:stable-alpine3.17-slim
WORKDIR /usr/share/nginx/html
COPY . .Este Dockerfile define una imagen que sirve contenido estático utilizando Nginx a partir de una imagen base de Alpine Linux.
Desarrollar con Compose
Docker Compose es una herramienta para definir y ejecutar aplicaciones Docker de múltiples contenedores. Con Compose, utilizas un archivo YAML para configurar los servicios, redes y volúmenes de tu aplicación. En este caso, la aplicación no es de múltiples contenedores, pero Docker Compose tiene otras características útiles para el desarrollo, como Compose Watch.
La aplicación de ejemplo aún no tiene un archivo Compose. Para crear un archivo Compose, dentro del directorio TensorJS-Face-Detection, crea un archivo de texto llamado compose.yaml y añade el siguiente contenido.
services:
server:
build:
context: .
ports:
- 80:80
develop:
watch:
- action: sync
path: .
target: /usr/share/nginx/htmlEste archivo Compose define un servicio que se construye utilizando el Dockerfile en el mismo directorio. Mapea el puerto 80 del host al puerto 80 en el contenedor. También tiene una subsección develop con el atributo watch que define un conjunto de reglas que controlan las actualizaciones automáticas del servicio en función de los cambios en los archivos locales. Para obtener más detalles sobre las instrucciones de Compose, consulta la
referencia del archivo Compose.
Guarda los cambios en tu archivo compose.yaml y luego ejecuta el siguiente comando para poner en marcha la aplicación.
$ docker compose watch
Una vez que la aplicación se esté ejecutando, abre un navegador web y accede a ella en http://localhost:80. Es posible que debas otorgar acceso a tu cámara web para la aplicación.
Ahora puedes realizar cambios en el código fuente y ver cómo se reflejan automáticamente en el contenedor sin tener que reconstruir y reiniciar el contenedor.
Abre el archivo index.js y actualiza los puntos de referencia para que sean verdes en lugar de azules en la línea 83.
- ctx.fillStyle = "blue";
+ ctx.fillStyle = "green";
Guarda los cambios en el archivo index.js y actualiza la página del navegador. Los puntos de referencia ahora deberían aparecer de color verde.
Para detener la aplicación, presiona ctrl+c en la terminal.
Compartir tu imagen
Publicar tu imagen de Docker en Docker Hub simplifica los procesos de despliegue para otros, facilitando su integración en diversos proyectos. También promueve la adopción de tus soluciones contenedorizadas, ampliando su impacto en el ecosistema de desarrolladores. Para compartir tu imagen:
Regístrate o inicia sesión en Docker Hub.
Vuelve a construir la imagen para incluir los cambios en tu aplicación. Esta vez, añade tu Docker ID como prefijo al nombre de la imagen. Docker utiliza el nombre para determinar a qué repositorio debe subirla. Abre una terminal y ejecuta el siguiente comando en el directorio
TensorJS-Face-Detection. ReemplazaYOUR-USER-NAMEcon tu Docker ID.$ docker build -t YOUR-USER-NAME/face-detection-tensorjs .Ejecuta el siguiente comando
docker pushpara subir la imagen a Docker Hub. ReemplazaYOUR-USER-NAMEcon tu Docker ID.$ docker push YOUR-USER-NAME/face-detection-tensorjsVerifica que has subido la imagen a Docker Hub.
- Ve a Docker Hub.
- Selecciona My Hub > Repositories.
- Observa la hora de Last pushed para tu repositorio.
Otros usuarios ahora pueden descargar y ejecutar tu imagen utilizando el comando docker run. Deben reemplazar YOUR-USER-NAME con tu Docker ID.
$ docker run -p 80:80 YOUR-USER-NAME/face-detection-tensorjs
Resumen
Esta guía ha demostrado cómo aprovechar TensorFlow.js y Docker para la detección de rostros en aplicaciones web. Destacó la facilidad de ejecutar aplicaciones de TensorFlow.js contenedorizadas y el desarrollo con Docker Compose para realizar cambios de código en tiempo real. Además, cubrió cómo compartir tu imagen de Docker en Docker Hub puede simplificar el despliegue para otros, mejorando el alcance de la aplicación dentro de la comunidad de desarrolladores.
Información relacionada: