使用 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 中訓練和部署 ML 模型。它支援從頭開始建立新模型或使用預訓練模型,從而促進直接在 Web 環境中進行各種機器學習應用。TensorFlow.js 提供高效的運算能力,讓 Web 開發人員無需具備深厚的 ML 專業知識,也能輕鬆處理複雜的機器學習任務。

為什麼要同時使用 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 瀏覽器並存取 https://: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,允許在網頁畫布 (canvas) 中進行 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 函式庫的一部分。這是一個 WASM 二進位檔案,用於 WebAssembly 後端,特別優化為利用 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 連接埠。它還包含一個帶有 watch 屬性的 develop 子區段,該子區段定義了一系列規則,這些規則根據本地檔案變更控制自動服務更新。有關 Compose 指令的更多詳細資訊,請參閱 Compose 檔案參考

儲存對 compose.yaml 檔案的變更,然後執行以下命令以執行應用程式。

$ docker compose watch

應用程式執行後,請開啟 Web 瀏覽器並存取 https://: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. 選擇 我的 Hub > 儲存庫
    3. 查看您儲存庫的 最後推送 (Last pushed) 時間。

其他使用者現在可以使用 docker run 命令下載並執行您的映像檔。他們需要將 YOUR-USER-NAME 取代為您的 Docker ID。

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

總結

本指南演示了如何利用 TensorFlow.js 和 Docker 進行 Web 應用程式中的人臉偵測。它強調了執行容器化 TensorFlow.js 應用程式的便利性,以及使用 Docker Compose 進行即時程式碼變更開發的優勢。此外,它還涵蓋了如何在 Docker Hub 上分享您的 Docker 映像檔,以簡化他人的部署流程,增強應用程式在開發者社群中的影響力。

相關資訊

© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.