4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

face-api.jsを使って、ウェブカメラの映像に対して顔認識を行う!

4
Last updated at Posted at 2025-06-28

やったこと

前回はreact-webcamを使ってデバイスのカメラの映像のスクリーンショットを取得する実装を行いました。
react-webcamを使ってReactアプリで簡単にカメラ機能を利用しよう!

今回は 撮影画面に表示したカメラの映像に対して顔認識を行い、顔認識が一人のみの場合に撮影ボタンを押下することが出来る ような実装を行いました。

よろしければ前回の記事と併せてぜひご覧ください。

【誰も映っていない時】
image.png

【一人のみ映っているとき】
スクリーンショット 2025-06-28 200443.png.png

【複数人映っているとき】
スクリーンショット 2025-06-28 200640.png.png

※隣はフリー素材の方です。

face-api.jsとは

face-api.js は、JavaScript(ブラウザや Node.js)で顔検出・顔認識・顔特徴抽出ができるライブラリです。

(2025/08/31追記)今はこちらが標準のようです。使用感はほぼ変わりません。

導入

face-api.jsの導入

npm i face-api.js

package.jsonに追加されていればOKです。
image.png

モデルの導入

顔検出をするにあたり、学習モデルを読み込む必要があります。
公式には以下のように記載されています。

・モデルを読み込むには、対応するmanifest.jsonファイルとthe model weight filesが必要
・これらのファイルを、publicまたはassetsに配置する
・これらのファイルは、同じディレクトリ内、または同じルートからアクセスできる必要がある

これに倣って、public配下にmodelsフォルダを作成し、そこに配置します。
image.png

ちなみに、公式から用意されたファイルがgithubにあるので、今回はそちらを使用しました。

補足

モデルは三種類あり、それぞれ特徴があるので本格的な導入の際には留意する必要があるかもしれません。
今回は軽量かつ、小さな顔の認識が不要だったのでTiny Face Detectorを選択しました。

モデル名 精度 処理速度 サイズ モバイル向き 特徴
Tiny Face Detector 高速 190KB 軽量、速い、顔が小さいと検出に弱い
SSD Mobilenet V1 普通 5.4MB 高精度、小さい顔もOK、重い
MTCNN 中〜高 遅い 2MB ランドマーク検出も可能

実装

face-apiを用いた実装は、引き続きCaptureコンポーネントでおこないます。
実際のコードを見てみましょう。

Capture.tsx
import { useCallback, useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router";
import Webcam from "react-webcam";
import * as faceapi from "face-api.js";
import { JoyUiButton } from "./@joy-ui/Button/JoyUiButton";

export const Capture = () => {
  const webCamRef = useRef<Webcam>(null);
  const navigate = useNavigate();
  const [disabled, setDisabled] = useState<boolean>(true);
  const videoConstraints: boolean | MediaTrackConstraints | undefined = {
    width: 640,
    height: 360,
    facingMode: "user", 
  };

  const onClickScreenShot = useCallback(() => {
    const imageSrc = webCamRef.current?.getScreenshot();
    navigate("/result", { state: { capture: imageSrc } });
  }, []);

  const goToErrorPage = () => {
    navigate("/error");
  };

  useEffect(() => {
    const loadDetectModel = async () => {
      // modelsフォルダに配置したmodelを読み込む
      await faceapi.nets.tinyFaceDetector.loadFromUri("/models"); 
    };

    const detectFace = async () => {
      const ONLY_ONE_FACE_DETECTED = 1;
      const HAS_ENOUGH_DATA = 4;
      const realTimeVideo: HTMLVideoElement | undefined =
              webCamRef.current?.video || undefined;
      // animationIdを宣言してcancelAnimationFrameを呼び出さないと無限ループになる
      const animationId: number = requestAnimationFrame(detectFace);

      // videoの準備が出来ていなかったらreturnする
      if (!realTimeVideo || realTimeVideo.readyState !== HAS_ENOUGH_DATA) {
        return;
      }

      const faces = await faceapi.detectAllFaces(
        realTimeVideo,
        new faceapi.TinyFaceDetectorOptions()
      );

      if (faces.length === ONLY_ONE_FACE_DETECTED) {
        setDisabled(false);
      } else {
        setDisabled(true);
      }

      // animationIdをセットしてcancelAnimationFrameを呼び出さないと無限ループになる
      cancelAnimationFrame(animationId);
    };

    loadDetectModel();
    detectFace();
  }, []);

  return (
    <div>
      <div>
        <h2>撮影画面</h2>
      </div>
      <div>
        <Webcam
          audio={false} 
          disablePictureInPicture={true}
          screenshotFormat={"image/webp"}
          onUserMediaError={goToErrorPage}
          videoConstraints={videoConstraints}
          ref={webCamRef}
        />
      </div>
      <div style={{ justifyItems: "center" }}>
        <JoyUiButton
          disabled={disabled}
          content={"撮影"}
          handleClick={onClickScreenShot}
        />
      </div>
    </div>
  );
};

【実装手順】

①モデルファイルを読み込む

まずは先ほど配置したモデルファイルを読み込む必要があります。
初回レンダリング時に読み込むため、依存配列を指定しないuseEffectで実装しています。

  useEffect(() => {
    const loadDetectModel = async () => {
      // modelsフォルダに配置したmodelを読み込む
      await faceapi.nets.tinyFaceDetector.loadFromUri("/models"); 
    };
    
    loadDetectModel();
  }, []);

注意点として採用したモデルによってアスタリスク(***)で示した部分が異なります。

faceapi.nets.***.loadFromUri("/models");

②カメラの映像から顔認識情報を取得する

    const detectFace = async () => {
      const realTimeVideo: HTMLVideoElement | undefined =
              webCamRef.current?.video || undefined;

      // videoの準備が出来ていなかったらreturnする
      if (!realTimeVideo || realTimeVideo.readyState !== HAS_ENOUGH_DATA) {
        return;
      }

      const faces = await faceapi.detectAllFaces(
        realTimeVideo,
        new faceapi.TinyFaceDetectorOptions()
      );
    };
顔認識情報取得

以下のメソッドにより、画面で認識された全ての顔の情報がArrayとして返却されます。

faceapi.detectAllFaces("第一引数<HTMLVideElement>","第二引数<オプション>")

【第一引数】
画像や映像などの顔認識を行う対象を指定します。
今回はウェブカメラの映像であるHTMLVideoElement型のwebCamRef.current?.videoを指定しています。

【第二引数】
オプションを指定します。
ここでは顔検証モデルを指定することが出来、最初に読み込んだモデル以外も指定することが可能です。
デフォルトはSSD Mobilenet V1なので、今回は読み込んだモデルに合わせる形でnew faceapi.TinyFaceDetectorOptions()としてTiny Face Detectorを指定しています。

その他
     const HAS_ENOUGH_DATA = 4;

      // videoの準備が出来ていなかったらreturnする
      if (!realTimeVideo || realTimeVideo.readyState !== HAS_ENOUGH_DATA) {
        return;
      }

この部分はrealTimeVideofalseであったり、realTimeVideo.readyStateが十分なデータを利用出来ない時を示す場合は早期にreturnするという処理を実装しています。

HTMLVideoElementHTMLMediaElementを継承しておりreadyStateというプロパティを持ちます。
これらは以下の値を持ちメディアの準備状態を表します。

{
  "0": {
    "name": "HAVE_NOTHING",
    "description": "このメディアリソースに関する情報がありません。"
  },
  "1": {
    "name": "HAVE_METADATA",
    "description": "メタデータ属性を初期化するのに十分なメディアリソースが取得されました。シークしても例外が発生しません。"
  },
  "2": {
    "name": "HAVE_CURRENT_DATA",
    "description": "現在の再生位置にデータがありますが、実際には複数のフレームを再生するのに十分ではありません。"
  },
  "3": {
    "name": "HAVE_FUTURE_DATA",
    "description": "現在の再生位置と将来までの少なくともほんの少しの時間のデータ(例: 2 フレーム以上)が利用可能です。"
  },
  "4": {
    "name": "HAVE_ENOUGH_DATA",
    "description": "十分なデータが利用可能であり、ダウンロードレートが十分に高いため、メディアを中断することなく最後まで再生できます。"
  }
}

③画面表示中に繰り返し顔認識を行うようにする

    const detectFace = async () => {
      const ONLY_ONE_FACE_DETECTED = 1;
      const HAS_ENOUGH_DATA = 4;
      const realTimeVideo: HTMLVideoElement | undefined =
              webCamRef.current?.video || undefined;
      // animationIdを宣言してcancelAnimationFrameを呼び出さないと無限ループになる
      const animationId: number = requestAnimationFrame(detectFace);

     {//* ーー省略ーー *//}

      // animationIdをセットしてcancelAnimationFrameを呼び出さないと無限ループになる
      cancelAnimationFrame(animationId);
    };

    loadDetectModel();
    detectFace();
繰り返し処理

以下を用いて実装しました。
この関数は引数に指定したコールバック関数を再描画の前に呼び出すものです。

requestAnimationFrame(コールバック関数)

【メリット】
requestAnimationFrame() が「次の画面更新のタイミングでこの関数を実行してね」とブラウザに予約してくれるので、今後実装予定の「顔認識されている人の顔に枠線を表示する」などの描画処理が発生すると、再描画前のタイミングでコールバック関数を実行してくれます

【注意】
detectFace関数を再帰的に呼び出す実装であるため、適切にアンマウント処理を実装しなければメモリリークに陥る可能性 があります。
そのため、コールバックをリクエストしたときに返されるID値をanimationIDとして取得し、cancelAnimationFrame(animationId)とすることで、コールバックリクエストを適宜キャンセルすることが重要です。

// animationIdを宣言してcancelAnimationFrameを呼び出さないと無限ループになる
const animationId: number = requestAnimationFrame(detectFace);

cancelAnimationFrame(animationId);

④顔認証が一人のみの時に撮影ボタンを活性にする

const [disabled, setDisabled] = useState<boolean>(true);

    {//* ーー省略ーー *//}

  useEffect(() => {
  
     const ONLY_ONE_FACE_DETECTED = 1;
  
    {//* ーー省略ーー *//}
    
     const faces = await faceapi.detectAllFaces(
        realTimeVideo,
        new faceapi.TinyFaceDetectorOptions()
      );

      if (faces.length === ONLY_ONE_FACE_DETECTED) {
        setDisabled(false);
      } else {
        setDisabled(true);
      }

    {//* ーー省略ーー *//}
  
  }, []);

  return (
  
    {//* ーー省略ーー *//}
     
      <div>
        <JoyUiButton
          disabled={disabled}
          content={"撮影"}
          handleClick={onClickScreenShot}
        />
      </div>
    </div>
  );
};

顔認識の結果facesArrayとして取得されているので、それを利用してボタンのdisableを制御しています。

以上で実装完了です。

おわり

今回はface-api.jsを使って、カメラの映像から顔認識をする処理を実装してみました。
次は、顔認識が出来ている対象の顔に枠線を表示する実装をしようと思っています。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?