やったこと
前回の記事ではface-api.js
で顔認識をおこないました。
今回の記事では、顔認識が実行されているか明確にするため、認識している顔に対して枠を表示する実装をおこないます。
準備
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
を重ねて枠線を表示させたいので、Wrapper
にposition:relative
を指定し、子要素のWebcam
とcanvas
にposition:absolute
を指定します。
position:absolute
が適用された要素は、position:relative
をもつ一番近い要素の左上に配置されます。
また、canvas
もWebcam
同様に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
でも渡すことが可能です。
これで実装は以上です。
最後に
無事に、顔認識されている対象上に枠線を画面に表示することが出来ました。
また、今回は公式ドキュメントを参考にして作成しましたが、一次情報を見ながら理解していくのは大切だと思ったので、引き続き公式ドキュメントから情報を取得する癖をつけていきたいと思います。