TensorFlow.js로 얼굴 감지

TensorFlow.js로 얼굴 감지

이 가이드는 Docker 컨테이너에서 TensorFlow.js를 실행하는 방법을 설명해요.

출처: 문서

본문

이 가이드는 TensorFlow.js와 Docker의 원활한 통합을 통해 얼굴 감지를 수행하는 방법을 소개해요. 이 가이드에서 탐구할 내용:

  • Docker로 컨테이너화된 TensorFlow.js 어플리케이션 실행하기
  • TensorFlow.js로 웹 어플리케이션에서 얼굴 감지 구현하기
  • TensorFlow.js 웹 어플리케이션용 Dockerfile 구성하기
  • 실시간 어플리케이션 개발·업데이트를 위해 Docker Compose 사용하기
  • Docker Hub에 내 Docker 이미지 공유해 배포를 용이하게 하고 도달 범위를 확장하기

감사의 말: Docker는 이 가이드에 기여한 Harsh Manvar 님께 감사를 전해요.

사전 요구사항

  • 최신 버전의 Docker Desktop 을 설치했어야 해요.
  • Git 클라이언트 가 있어야 해요. 이 가이드의 예제는 명령줄 기반 Git 클라이언트를 사용하지만, 어떤 클라이언트든 사용할 수 있어요.

TensorFlow.js란?

TensorFlow.js 는 브라우저나 Node.js에서 ML 모델을 훈련·배포할 수 있게 해주는 머신러닝용 오픈소스 JavaScript 라이브러리예요. 처음부터 새 모델을 만들거나 사전 훈련된 모델을 사용하는 것을 지원해, 웹 환경에서 직접 광범위한 ML 어플리케이션을 가능하게 해요. TensorFlow.js는 효율적인 계산을 제공해, 깊은 ML 전문 지식 없이도 정교한 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에 매핑해요.

어플리케이션이 실행되면 웹 브라우저를 열고 http://localhost:80 에서 어플리케이션에 접근해주세요. 어플리케이션에 웹캠 접근 권한을 부여해야 할 수도 있어요.

웹 어플리케이션에서 백엔드를 다음 중 하나로 변경할 수 있어요:

  • WASM
  • WebGL
  • CPU

어플리케이션을 중지하려면 터미널에서 ctrl + c 를 누르세요.

어플리케이션 소개

샘플 어플리케이션은 멀티모달 머신러닝 파이프라인 구축용 포괄적인 프레임워크인 MediaPipe 를 사용해 실시간 얼굴 감지를 수행해요. 특히 이미지에서 얼굴을 감지하는 경량 모델인 BlazeFace 모델을 사용해요.

TensorFlow.js나 유사한 웹 기반 머신러닝 프레임워크의 맥락에서 WASM, WebGL, CPU 백엔드를 사용해 작업을 실행할 수 있어요. 이 백엔드들은 각각 현대 브라우저에서 사용 가능한 서로 다른 리소스와 기술을 활용하며, 각각 강점과 한계가 있어요. 다음 섹션들은 서로 다른 백엔드에 대한 간략한 설명이에요.

  • WASM: WebAssembly(WASM)는 컴팩트한 이진 형식을 가진 저수준 어셈블리류 언어로, 브라우저에서 네이티브에 가까운 속도로 실행돼요. C/C++ 같은 언어로 작성된 코드를 브라우저에서 실행할 수 있는 이진으로 컴파일할 수 있게 해줘요. 높은 성능이 필요하고 WebGL 백엔드가 지원되지 않거나, GPU에 의존하지 않고 모든 기기에서 일관된 성능을 원할 때 좋은 선택이에요.
  • WebGL: WebGL은 웹 페이지 캔버스의 일부로 물리, 이미지 처리, 효과의 GPU 가속 사용을 허용하는 브라우저 API예요. 딥러닝 모델에서 흔히 볼 수 있는 행렬 곱셈과 합성곱처럼 병렬화 가능하고 GPU 가속의 혜택을 크게 받을 수 있는 작업에 잘 맞아요.
  • CPU: CPU 백엔드는 순수 JavaScript 실행을 사용하며 기기의 중앙 처리 장치(CPU)를 활용해요. 이 백엔드는 가장 보편적으로 호환되며 WebGL이나 WASM 백엔드가 없거나 적합하지 않을 때 폴백 역할을 해요.

어플리케이션 코드 탐구

다음 섹션에서 각 파일의 목적과 내용을 탐구할 거예요.

index.html 파일

index.html 파일은 웹캠 비디오 피드에서 실시간 얼굴 감지를 위해 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/[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>

index.js 파일

index.js 파일은 얼굴 감지 로직을 수행해요. 웹 개발과 머신러닝 통합의 여러 고급 개념을 보여줘요. 핵심 구성 요소와 기능의 일부는 다음과 같아요:

  • Stats.js: 스크립트는 어플리케이션의 프레임 레이트(FPS)를 실시간으로 모니터링하고 표시하기 위해 Stats 인스턴스를 만드는 것으로 시작해요. 이는 특히 서로 다른 TensorFlow.js 백엔드가 어플리케이션 속도에 미치는 영향을 테스트할 때 성능 분석에 유용해요.
  • TensorFlow.js: 어플리케이션은 dat.GUI가 제공하는 그래픽 인터페이스를 통해 TensorFlow.js용 서로 다른 계산 백엔드(wasm, webgl, cpu)를 전환할 수 있게 해줘요. 백엔드 변경은 기기와 브라우저에 따라 성능과 호환성에 영향을 줄 수 있어요. addFlagLabels 함수는 SIMD(Single Instruction, Multiple Data)와 멀티스레딩이 지원되는지 동적으로 확인하고 표시하는데, 이는 wasm 백엔드의 성능 최적화와 관련이 있어요.
  • setupCamera 함수: MediaDevices Web API를 사용해 사용자의 웹캠을 초기화해요. 비디오 스트림이 오디오를 포함하지 않고 전면 카메라(facingMode: 'user')를 사용하도록 구성해요. 비디오 메타데이터가 로드되면 비디오 요소와 함께 프로미스를 해석해 얼굴 감지에 사용해요.
  • 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);
  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();

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(Single Instruction, Multiple Data) 명령을 활용하도록 최적화돼 있어요.

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

어플리케이션이 실행되면 웹 브라우저를 열고 http://localhost:80 에서 어플리케이션에 접근해주세요. 어플리케이션에 웹캠 접근 권한을 부여해야 할 수도 있어요.

이제 소스 코드를 변경하면 컨테이너를 재빌드·재실행하지 않고도 변경 사항이 컨테이너에 자동으로 반영되는 것을 볼 수 있어요.

index.js 파일을 열고 83행의 랜드마크 점을 파란색 대신 초록색으로 업데이트해주세요.

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

index.js 파일에 변경 사항을 저장한 다음 브라우저 페이지를 새로고침해주세요. 이제 랜드마크 점이 초록색으로 보여야 해요.

어플리케이션을 중지하려면 터미널에서 ctrl + c 를 누르세요.

내 이미지 공유

Docker Hub에 내 Docker 이미지를 게시하면 다른 사람들의 배포 과정을 간소화해 다양한 프로젝트에 원활하게 통합할 수 있게 해줘요. 또한 컨테이너화된 솔루션의 채택을 촉진해 개발자 생태계 전반에 그 영향력을 넓혀요. 이미지를 공유하려면:

Docker Hub 에 가입 하거나 로그인 해주세요.

어플리케이션 변경 사항을 포함하도록 이미지를 재빌드해주세요. 이번에는 이미지 이름에 내 Docker ID를 접두어로 붙이세요. Docker는 이름을 사용해 푸시할 리포지토리를 결정해요. 터미널을 열고 TensorJS-Face-Detection 디렉터리에서 다음 명령을 실행해주세요. YOUR-USER-NAME 을 내 Docker ID로 바꾸세요.

$ docker build -t YOUR-USER-NAME/face-detection-tensorjs .

다음 docker push 명령을 실행해 이미지를 Docker Hub에 푸시해주세요. YOUR-USER-NAME 을 내 Docker ID로 바꾸세요.

$ docker push YOUR-USER-NAME/face-detection-tensorjs

이미지를 Docker Hub에 푸시했는지 확인해주세요.

Docker Hub 로 이동해주세요.

My Hub > Repositories 를 선택해주세요.

리포지토리의 Last pushed 시간을 확인해주세요.

이제 다른 사용자가 docker run 명령으로 내 이미지를 다운로드·실행할 수 있어요. YOUR-USER-NAME 을 내 Docker ID로 바꿔야 해요.

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

요약

이 가이드는 웹 어플리케이션에서 얼굴 감지를 위해 TensorFlow.js와 Docker를 활용하는 방법을 보여줬어요. 컨테이너화된 TensorFlow.js 어플리케이션 실행의 용이성과 실시간 코드 변경을 위한 Docker Compose 개발을 강조했어요. 또한 Docker Hub에 내 Docker 이미지를 공유하면 다른 사람들의 배포를 간소화해 개발자 커뮤니티 내에서 어플리케이션의 도달 범위를 넓힐 수 있는 방법도 다뤘어요.

관련 정보:

  • TensorFlow.js 웹사이트
  • MediaPipe 웹사이트
  • Dockerfile 참조
  • Compose 파일 참조
  • Docker CLI 참조

더 알아보기 (Learn more)

  • TensorFlow.js
  • MediaPipe
  • Docker Compose