やったこと
前回はreact-webcamを使ってデバイスのカメラの映像のスクリーンショットを取得する実装を行いました。
react-webcamを使ってReactアプリで簡単にカメラ機能を利用しよう!
今回は 撮影画面に表示したカメラの映像に対して顔認識を行い、顔認識が一人のみの場合に撮影ボタンを押下することが出来る ような実装を行いました。
よろしければ前回の記事と併せてぜひご覧ください。
※隣はフリー素材の方です。
face-api.jsとは
face-api.js は、JavaScript(ブラウザや Node.js)で顔検出・顔認識・顔特徴抽出ができるライブラリです。
(2025/08/31追記)今はこちらが標準のようです。使用感はほぼ変わりません。
導入
face-api.jsの導入
npm i face-api.js
モデルの導入
顔検出をするにあたり、学習モデルを読み込む必要があります。
公式には以下のように記載されています。
・モデルを読み込むには、対応する
manifest.jsonファイルとthe model weight filesが必要
・これらのファイルを、publicまたはassetsに配置する
・これらのファイルは、同じディレクトリ内、または同じルートからアクセスできる必要がある
これに倣って、public配下にmodelsフォルダを作成し、そこに配置します。

ちなみに、公式から用意されたファイルがgithubにあるので、今回はそちらを使用しました。
補足
モデルは三種類あり、それぞれ特徴があるので本格的な導入の際には留意する必要があるかもしれません。
今回は軽量かつ、小さな顔の認識が不要だったのでTiny Face Detectorを選択しました。
| モデル名 | 精度 | 処理速度 | サイズ | モバイル向き | 特徴 |
|---|---|---|---|---|---|
| Tiny Face Detector | 中 | 高速 | 190KB | ◎ | 軽量、速い、顔が小さいと検出に弱い |
| SSD Mobilenet V1 | 高 | 普通 | 5.4MB | △ | 高精度、小さい顔もOK、重い |
| MTCNN | 中〜高 | 遅い | 2MB | △ | ランドマーク検出も可能 |
実装
face-apiを用いた実装は、引き続きCaptureコンポーネントでおこないます。
実際のコードを見てみましょう。
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;
}
この部分はrealTimeVideoがfalseであったり、realTimeVideo.readyStateが十分なデータを利用出来ない時を示す場合は早期にreturnするという処理を実装しています。
HTMLVideoElementはHTMLMediaElementを継承しており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>
);
};
顔認識の結果facesがArrayとして取得されているので、それを利用してボタンのdisableを制御しています。
以上で実装完了です。
おわり
今回はface-api.jsを使って、カメラの映像から顔認識をする処理を実装してみました。
次は、顔認識が出来ている対象の顔に枠線を表示する実装をしようと思っています。



