4
0

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で顔認識をおこないました。

今回の記事では、顔認識が実行されているか明確にするため、認識している顔に対して枠を表示する実装をおこないます。

こんな感じになりました。
スクリーンショット 2025-06-29 194222.png.png

準備

face.api.jsの準備は前回の記事を参考にしてください。

実装

今回も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 canvasRef = useRef<HTMLCanvasElement>(null); // 追加
  const navigate = useNavigate();
  const [disabled, setDisabled] = useState<boolean>(true);
  const videoConstraints: boolean | MediaTrackConstraints | undefined = {
    width: 640,
    height: 360,
    facingMode: "user",
  };

  const displaySize = {
    width: webCamRef.current?.video?.width || 640,
    height: webCamRef.current?.video?.height || 360,
  };

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

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

  useEffect(() => {
    const loadDetectModel = async () => {
      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;
      const canvas = canvasRef.current; // 追加
      
      const animationId: number = requestAnimationFrame(detectFace);

      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);
      }

      // 追加範囲始まり
      if (!canvas) {
        return;
      }

      // canvasのサイズをカメラ映像を表示しているサイズに合わせる
      faceapi.matchDimensions(canvas, displaySize);
      // 顔認識の座標を現在表示されているカメラ映像のサイズにリサイズする
      const resizeDetections = faceapi.resizeResults(faces, displaySize);
      // canvasにリサイズした顔認識結果を描画する
      faceapi.draw.drawDetections(canvas, resizeDetections);

      // 追加範囲終わり

      cancelAnimationFrame(animationId);
    };

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

  return (
    <div>
      <div>
        <h2>撮影画面</h2>
      </div>
      <div style={{ position: "relative", width: 640, height: 360 }}>
        <Webcam
          audio={false} 
          disablePictureInPicture={true} 
          screenshotFormat={"image/webp"} 
          onUserMediaError={goToErrorPage} 
          videoConstraints={videoConstraints}
          style={{ position: "absolute" }}
          ref={webCamRef}
        />

        {/* canvasを追加 */}
        <canvas
          style={{ position: "absolute" }}
          width={640}
          height={360}
          ref={canvasRef}
        ></canvas>
      </div>
      <div style={{ display: "flex", justifyContent: "center" }}>
        <JoyUiButton
          disabled={disabled}
          content={"撮影"}
          handleClick={onClickScreenShot}
        />
      </div>
    </div>
  );
};

【実装手順】

①枠線を描画するcanvasを配置する

一部抜粋
  const canvasRef = useRef<HTMLCanvasElement>(null);

  return (
   
      <div style={{ position: "relative", width: 640, height: 360 }}>
        <Webcam
          audio={false}
          disablePictureInPicture={true}
          screenshotFormat={"image/webp"}
          onUserMediaError={goToErrorPage}
          videoConstraints={videoConstraints}
          style={{ position: "absolute" }}
          ref={webCamRef}
        />

        <canvas
          style={{ position: "absolute" }}
          width={640}
          height={360}
          ref={canvasRef}
        ></canvas>
      </div>
  );
説明

Webcamコンポーネントを含むWrapperを作成したうえで、そこにcanvasを配置します。

最終的にWebcamのカメラ映像にcanvasを重ねて枠線を表示させたいので、Wrapperposition:relativeを指定し、子要素のWebcamcanvasposition:absoluteを指定します。

position:absoluteが適用された要素は、position:relativeをもつ一番近い要素の左上に配置されます。

また、canvasWebcam同様にrefでDOMを取得しておきます。

 const canvasRef = useRef<HTMLCanvasElement>(null);
canvasとは?

グラフィックやアニメーションを描画することが出来るHTML要素です。

②canvasのサイズを調整する

 const canvas = canvasRef.current;
 
 const displaySize = {
    width: webCamRef.current?.video?.width || 640,
    height: webCamRef.current?.video?.height || 360,
};

 faceapi.matchDimensions(canvas, displaySize);
説明

canvasに描画した枠線をカメラ映像上の顔の位置に表示するにあたり、映像サイズとcanvasサイズが異なっていると枠線が上手く顔の位置に表示されません。
そのため、canvasのサイズと映像のサイズを揃える必要があります。

実現するための機能はface-api.jsより提供されていますのでそれを使います。

 faceapi.matchDimensions(canvas, displaySize);

【第一引数】
サイズ調整をしたい対象を指定します。

【第二引数】
表示サイズを指定します。

③顔認識結果の座標をカメラ映像のサイズにリサイズする

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

const resizeDetections = faceapi.resizeResults(faces, displaySize);
説明

顔認識の枠線の座標情報を実際の映像サイズに合わせてリサイズします。
こちらもfaceapi.jsで提供されているのでそれを使います。

faceapi.resizeResults(faces, displaySize);

【第一引数】
顔認識の検出結果を指定します。
前回の記事でfaceapi.detectAllFacesを使って取得したものを使いました。

【第二引数】
box(顔に表示する枠線)をリサイズして表示したい映像サイズを指定します。
今回は、表示している映像サイズと一致させるようにしています。

【リサイズをおこなう理由】
顔認識の検出結果に含まれるboxの座標情報はあくまで元の映像サイズでの座標位置にすぎず、現在の映像サイズ上での座標位置としては不適切な可能性があるためです。

(参考)顔認識結果の返り値:Array<FaceDetection>

interface FaceDetection {
  score: number;
  box: Box; // 検出された顔の位置
  imageDims: IDimensions;
  getBox(): Box; 
  getScore(): number; 
  relativeBox: Box; 
}

④canvasにリサイズした顔認識結果を描画する

最後に、実際にcanvas上に枠線を描画しましょう!
こちらも提供されている機能を使います。

faceapi.draw.drawDetections(canvas, resizeDetections);
説明

【第一引数】
描画を行う対象を指定します。
HTMLCanvasElementが主に指定されることになります。

【第二引数】
FaceDetection型の顔認識情報を指定します。
今回のようにArrayでも渡すことが可能です。

これで実装は以上です。

最後に

無事に、顔認識されている対象上に枠線を画面に表示することが出来ました。
また、今回は公式ドキュメントを参考にして作成しましたが、一次情報を見ながら理解していくのは大切だと思ったので、引き続き公式ドキュメントから情報を取得する癖をつけていきたいと思います。

4
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?