使用 TensorFlow.js 进行人脸检测

本指南介绍了 TensorFlow.js 与 Docker 的无缝集成以进行人脸检测。在本指南中,您将学习如何:

  • 使用 Docker 运行容器化的 TensorFlow.js 应用程序。
  • 使用 TensorFlow.js 在 Web 应用程序中实现人脸检测。
  • 为 TensorFlow.js Web 应用程序构建 Dockerfile。
  • 使用 Docker Compose 进行实时应用程序开发和更新。
  • 在 Docker Hub 上共享您的 Docker 镜像,以方便部署和扩展范围。

致谢

Docker 感谢 Harsh Manvar 为其对本指南的贡献。

前提条件

  • 您已安装最新版本的 Docker Desktop
  • 您拥有一个 Git 客户端。本指南中的示例使用基于命令行的 Git 客户端,但您可以使用任何客户端。

什么是 TensorFlow.js?

TensorFlow.js 是一个开源的 JavaScript 机器学习库,使您能够在浏览器或 Node.js 中训练和部署机器学习模型。它支持从头开始创建新模型或使用预训练模型,从而方便直接在 Web 环境中进行各种机器学习应用程序。TensorFlow.js 提供高效的计算,使复杂的机器学习任务能够被 Web 开发人员访问,而无需深入的机器学习专业知识。

为什么将 TensorFlow.js 和 Docker 结合使用?

  • 环境一致性和简化的部署:Docker 将 TensorFlow.js 应用程序及其依赖项打包到容器中,确保所有环境中的一致运行并简化部署。
  • 高效的开发和轻松扩展:Docker 通过热重载等功能提高了开发效率,并使用 Kubernetes 等编排工具方便了 TensorFlow.js 应用程序的轻松扩展。
  • 隔离和增强的安全性:Docker 将 TensorFlow.js 应用程序隔离在安全的环境中,最大限度地减少冲突和安全漏洞,同时以有限的权限运行应用程序。

获取并运行示例应用程序

在终端中,使用以下命令克隆示例应用程序。

$ git clone https://github.com/harsh4870/TensorJS-Face-Detection

克隆应用程序后,您会注意到应用程序有一个 `Dockerfile`。此 `Dockerfile` 使您能够仅使用 Docker 在本地构建和运行应用程序。

在将应用程序作为容器运行之前,必须将其构建为镜像。在 `TensorJS-Face-Detection` 目录内运行以下命令以构建名为 `face-detection-tensorjs` 的镜像。

$ docker build -t face-detection-tensorjs .

该命令将应用程序构建为镜像。根据您的网络连接,第一次运行该命令时可能需要几分钟才能下载必要的组件。

要将镜像作为容器运行,请在终端中运行以下命令。

$ docker run -p 80:80 face-detection-tensorjs

该命令运行容器并将容器中的端口 80 映射到系统上的端口 80。

应用程序运行后,打开 Web 浏览器并访问 http://localhost:80 上的应用程序。您可能需要授予应用程序访问您网络摄像头的权限。

在 Web 应用程序中,您可以更改后端以使用以下其中一种:

  • WASM
  • WebGL
  • CPU

要停止应用程序,请在终端中按 `ctrl` + `c`。

关于应用程序

示例应用程序使用 MediaPipe 执行实时人脸检测,这是一个用于构建多模态机器学习管道的综合框架。它特别使用了 BlazeFace 模型,这是一个用于检测图像中人脸的轻量级模型。

在 TensorFlow.js 或类似的基于 Web 的机器学习框架的上下文中,可以使用 WASM、WebGL 和 CPU 后端来执行操作。这些后端中的每一个都利用现代浏览器中可用的不同资源和技术,并具有其自身的优点和局限性。以下部分简要介绍了不同的后端。

WASM

WebAssembly (WASM) 是一种低级、类似汇编语言的语言,具有紧凑的二进制格式,可以在 Web 浏览器中以接近本机的速度运行。它允许用 C/C++ 等语言编写的代码编译成可以在浏览器中执行的二进制代码。

当需要高性能时,这是一个不错的选择,或者 WebGL 后端不受支持,或者您希望在所有设备上获得一致的性能,而无需依赖 GPU。

WebGL

WebGL 是一个浏览器 API,允许将物理和图像处理以及效果作为网页画布的一部分进行 GPU 加速使用。

WebGL 非常适合可以并行化并可以从 GPU 加速中显著受益的操作,例如深度学习模型中常见的矩阵乘法和卷积。

CPU

CPU 后端使用纯 JavaScript 执行,利用设备的中央处理单元 (CPU)。此后端是最通用的兼容后端,并且当 WebGL 或 WASM 后端不可用或不适用时,可用作后备。

探索应用程序的代码

在以下部分中,探索每个文件及其内容的目的。

index.html 文件

`index.html` 文件作为 Web 应用程序的前端,该应用程序利用 TensorFlow.js 从网络摄像头视频提要中进行实时人脸检测。它结合了几种技术和库,以便直接在浏览器中进行机器学习。它使用几个 TensorFlow.js 库,包括:

  • tfjs-core 和 tfjs-converter 用于核心 TensorFlow.js 功能和模型转换。
  • tfjs-backend-webgl、tfjs-backend-cpu 和 tf-backend-wasm 脚本用于 TensorFlow.js 可以用来进行处理的不同计算后端选项。这些后端允许应用程序通过利用用户的硬件功能来高效地执行机器学习任务。
  • BlazeFace 库,一个用于人脸检测的 TensorFlow 模型。

它还使用以下其他库:

  • 使用 dat.GUI 创建图形界面,实时交互应用程序设置,例如在 TensorFlow.js 后端之间切换。
  • 使用 Stats.min.js 显示性能指标(例如 FPS),以监控应用程序运行期间的效率。
<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/tfjs-core@2.1.0/dist/tf-core.js"></script>
<script src="https://unpkg.com/@tensorflow/tfjs-converter@2.1.0/dist/tf-converter.js"></script>

<script src="https://unpkg.com/@tensorflow/tfjs-backend-webgl@2.1.0/dist/tf-backend-webgl.js"></script>
<script src="https://unpkg.com/@tensorflow/tfjs-backend-cpu@2.1.0/dist/tf-backend-cpu.js"></script>
<script src="./tf-backend-wasm.js"></script>

<script src="https://unpkg.com/@tensorflow-models/blazeface@0.0.5/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>

index.js 文件

index.js 文件执行人脸检测逻辑。它演示了 Web 开发和机器学习集成中的一些高级概念。以下是其一些关键组件和功能的细分。

  • Stats.js:脚本首先创建一个 Stats 实例,以实时监控和显示应用程序的帧率 (FPS)。这有助于进行性能分析,尤其是在测试不同 TensorFlow.js 后端对应用程序速度的影响时。
  • TensorFlow.js:应用程序允许用户通过 dat.GUI 提供的图形界面在 TensorFlow.js 的不同计算后端 (wasm、webgl 和 cpu) 之间切换。根据设备和浏览器,更改后端可能会影响性能和兼容性。addFlagLabels 函数动态检查并显示 SIMD(单指令多数据)和多线程是否受支持,这与 wasm 后端的性能优化相关。
  • setupCamera 函数:使用 MediaDevices Web API 初始化用户的网络摄像头。它将视频流配置为不包含音频并使用前置摄像头 (facingMode: 'user')。视频元数据加载后,它将使用视频元素解析 Promise,然后将其用于人脸检测。
  • BlazeFace:此应用程序的核心是 renderPrediction 函数,它使用 BlazeFace 模型(一种用于检测图像中人脸的轻量级模型)执行实时人脸检测。该函数在每个动画帧上调用 model.estimateFaces 以从视频馈送中检测人脸。对于检测到的每个人脸,它会在视频叠加的画布上绘制一个红色矩形包围人脸,并在人脸上绘制蓝色点作为面部特征点。
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);
    addFlagLables();
  });

async function addFlagLables() {
  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);
  addFlagLables();
  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();

tf-backend-wasm.js 文件

tf-backend-wasm.js 文件是 TensorFlow.js 库的一部分。它包含 TensorFlow.js WASM 后端的初始化逻辑,一些与 WASM 二进制文件交互的实用程序以及用于设置 WASM 二进制文件的自定义路径的函数。

tfjs-backend-wasm-simd.wasm 文件

tfjs-backend-wasm-simd.wasm 文件是 TensorFlow.js 库的一部分。它是一个用于 WebAssembly 后端的 WASM 二进制文件,专门针对利用 SIMD(单指令多数据)指令进行了优化。

探索 Dockerfile

在基于 Docker 的项目中,Dockerfile 作为构建应用程序环境的基础资源。

Dockerfile 是一个文本文件,指示 Docker 如何创建应用程序环境的镜像。镜像包含运行应用程序时所需的一切,例如文件、包和工具。

以下是此项目的 Dockerfile。

FROM nginx:stable-alpine3.17-slim
WORKDIR /usr/share/nginx/html
COPY . .

此 Dockerfile 定义一个镜像,该镜像使用来自 Alpine Linux 基础镜像的 Nginx 提供静态内容。

使用 Compose 进行开发

Docker Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。使用 Compose,您可以使用 YAML 文件配置应用程序的服务、网络和卷。在本例中,应用程序不是多容器应用程序,但 Docker Compose 具有其他对开发有用的功能,例如 Compose Watch

示例应用程序尚无 Compose 文件。要创建 Compose 文件,请在 TensorJS-Face-Detection 目录中创建一个名为 compose.yaml 的文本文件,然后添加以下内容。

services:
  server:
    build:
      context: .
    ports:
      - 80:80
    develop:
      watch:
        - action: sync
          path: .
          target: /usr/share/nginx/html

此 Compose 文件定义了一个使用同一目录中的 Dockerfile 构建的服务。它将主机上的端口 80 映射到容器中的端口 80。它还有一个 develop 部分,其中包含 watch 属性,该属性定义了一组规则,这些规则根据本地文件更改控制服务的自动更新。有关 Compose 指令的更多详细信息,请参阅 Compose 文件参考

保存对 compose.yaml 文件的更改,然后运行以下命令来运行应用程序。

$ docker compose watch

应用程序运行后,打开 Web 浏览器并访问 http://localhost:80 上的应用程序。您可能需要授予应用程序访问您网络摄像头的权限。

现在,您可以更改源代码,并看到更改会自动反映在容器中,而无需重新构建和重新运行容器。

打开 index.js 文件,并将第 83 行的面部特征点更新为绿色而不是蓝色。

-        ctx.fillStyle = "blue";
+        ctx.fillStyle = "green";

保存对 index.js 文件的更改,然后刷新浏览器页面。面部特征点现在应该显示为绿色。

要停止应用程序,请在终端中按 `ctrl` + `c`。

共享您的镜像

在 Docker Hub 上发布您的 Docker 镜像可以简化他人的部署流程,从而实现与各种项目的无缝集成。它还可以促进您容器化解决方案的采用,从而扩大其在开发人员生态系统中的影响。要共享您的镜像

  1. 注册 或登录 Docker Hub

  2. 重新构建您的镜像以包含对应用程序的更改。这次,请在镜像名称前加上您的 Docker ID。Docker 使用该名称来确定要将其推送到哪个存储库。打开终端,然后在 TensorJS-Face-Detection 目录中运行以下命令。将 YOUR-USER-NAME 替换为您的 Docker ID。

    $ docker build -t YOUR-USER-NAME/face-detection-tensorjs .
    
  3. 运行以下 docker push 命令将镜像推送到 Docker Hub。将 YOUR-USER-NAME 替换为您的 Docker ID。

    $ docker push YOUR-USER-NAME/face-detection-tensorjs
    
  4. 验证您是否已将镜像推送到 Docker Hub。

    1. 转到 Docker Hub
    2. 选择**存储库**。
    3. 查看存储库的**上次推送**时间。

其他用户现在可以使用 docker run 命令下载并运行您的镜像。他们需要将 YOUR-USER-NAME 替换为您的 Docker ID。

$ docker run -p 80:80 YOUR-USER-NAME/face-detection-tensorjs

总结

本指南演示了如何在 Web 应用程序中利用 TensorFlow.js 和 Docker 进行人脸检测。它强调了运行容器化 TensorFlow.js 应用程序的便捷性,以及使用 Docker Compose 进行实时代码更改的开发。此外,它还介绍了如何在 Docker Hub 上共享您的 Docker 镜像可以简化他人的部署,从而增强应用程序在开发人员社区中的影响力。

相关信息