【Rekognition】【初心者コピペ20分】2つの写真の顔をローカルで比較しよう Part1
はじめに
お疲れ様です。
クワハラと申します。
顔検索機能を実装する機会があったのですが、
自前で機械学習用のサーバー立てて、学習させて、APIを用意して、
はかなりの時間がかかりそう、、、、、
そう思っていたのですが、AWS側でサービスとして提供しておりました。(白々しい)
今回は2回に渡り、Amazon Rekognitionを使ってシンプルにローカルで2つの写真から顔を切り取り、類似度を表示するように実装します。
Part1ではAmazon Rekognitionとの通信部分は作成せず、まずは画面の作成までを行います。

目次
Amazon Rekognitionとは
Amazon Rekognitionは画像や動画を分析して、顔認識や物体検出などを行うことができるサービスです。
様々なAPIを提供しておりマネージドコンソール上からも試すことが可能ですので、興味のある方はぜひ試してみてください。
今回はその中でも、2つの写真から顔を比較する CompareFaces
オペレーションを使用して、ローカルで顔の比較を行う方法を2回に分けて紹介します。
完成形のイメージ
前提条件
- Node.js がインストールされていること。
- インストールがまだの方は、下記を参考にしてみて下さい。
- 今回はRekognitionのCompareFacesオペレーションを使用することが目的のため、ソースコードの解説は最小限にとどめます。
- ファイルアップロード機能は作成せず、比較する写真は直接指定のフォルダ/指定のファイル名にしてアップロードします。
- 使用する2つの写真にはそれぞれ1つだけの顔が写っていることを前提とします。
- 顔が複数あるものはコードが複雑になるので今回は省略します。
実行環境
今回は以下のバージョンで実装を行いました。
他のバージョンで実行する場合は適宜調整してください。
- mac os:15.5
- Node.js: 22.14.0
- npm: 11.2.0
- React: 19.1.0
- Typescript: 5.8.3
- Vite: 6.3.5
- @aws-sdk/client-rekognition: 3.826.0
処理概要図
今回は以下のような処理を想定しています。
※★がついているところを今回は実装します。

顔の比較用の画面を作成する
ViteでReactのプロジェクトを作成
Viteを使用してReactのプロジェクトを作成します。
以下のコマンドを実行します。
% npm create vite@latest
画面の入力に従い順番に入力します。
※プロジェクト名は任意で設定できますが、ここではrekognition-demo
とします。
> npx
> create-vite
│
◇ Project name:
│ rekognition-demo
│
◇ Select a framework:
│ React
│
◇ Select a variant:
│ TypeScript + SWC
│
◇ Scaffolding project in /Users/username/rekognition-demo...
│
└ Done. Now run:
作成したプロジェクトのディレクトリに移動し、依存関係をインストールします。
% cd rekognition-demo
% npm install
以下のコマンドでViteを起動します。
% npm run dev
http://localhost:5173/
にアクセスしでVite×Reactの画面が表示されれば、Viteの起動は成功です。

なお、この時点でのフォルダ構成は以下となっているはずです。
.
├── eslint.config.js
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── README.md
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
SDKをインストール
今回はAWS SDK for JavaScriptのv3を使用しますので、以下のコマンドで必要なパッケージをインストールします。
% npm install @aws-sdk/client-rekognition
画面を作成
続いて画面の作成を行います。
任意の画像をsrc/assets/originalSrc/compare1.png
とsrc/assets/targetSrc/compare2.png
に保存します。
※後述するコードでは、compare1.png
が比較元の画像、compare2.png
が比較対象の画像として使用予定ですので、必要に応じてパスやファイル名を調整してください。
.
├── ・・・
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ ├── originalSrc
│ │ │ └── compare1.png ★追加
│ │ ├── react.svg
│ │ └── targetSrc
│ │ └── compare2.png ★追加
│ ├── index.css
│ ├── main.tsx
│ └── vite-env.d.ts
└── ・・・
src/App.css
を開き、中身を全て以下のコードに書き換えます。
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.compare {
height: 20em;
padding: 1.5em;
}
src/App.tsx
を開き、中身を全て以下のコードに書き換えます。
// 比較元の画像パスを設定してください
import originalSrc from "./assets/originalSrc/compare1.png";
// 比較対象の画像パスを設定してください
import targetSrc from "./assets/targetSrc/compare2.png";
import "./App.css";
import { useFaceSearch } from "./hooks/useFaceSearch";
import { useState } from "react";
function App() {
const [initFlg, setInitFlg] = useState(true);
const [compareFlg, setCompareFlg] = useState(false);
const { sourceFace, targetFace, similarity } = useFaceSearch(
originalSrc,
targetSrc,
initFlg,
compareFlg
);
return (
<>
<h1>Face Compare</h1>
<div style={{ display: "flex", gap: "20px", marginBottom: "20px" }}>
<div style={{ position: "relative", display: "inline-block" }}>
<h2>比較元 Original Image</h2>
<img
src={originalSrc}
className="compare"
style={{ display: "block", width: "400px" }}
/>
<div
style={{
display: "flex",
gap: "10px",
alignItems: "center",
justifyContent: "center",
marginTop: "10px",
}}
>
{sourceFace && (
<div style={{ textAlign: "center" }}>
<img
src={sourceFace}
alt="Source Face"
style={{
border: "2px solid blue",
borderRadius: "8px",
maxWidth: "150px",
maxHeight: "150px",
}}
/>
</div>
)}
</div>
</div>
<div
style={{
position: "relative",
display: "inline-block",
}}
>
<h2>比較対象 Target Image</h2>
<img
src={targetSrc}
className="compare"
style={{ display: "block", width: "400px" }}
/>
<div
style={{
display: "flex",
gap: "10px",
alignItems: "center",
justifyContent: "center",
marginTop: "10px",
}}
>
{targetFace && (
<div style={{ textAlign: "center" }}>
<img
src={targetFace}
alt="Target Face"
style={{
border: "2px solid red",
borderRadius: "8px",
maxWidth: "150px",
maxHeight: "150px",
}}
/>
</div>
)}
</div>
</div>
</div>
<button
onClick={() => {
setInitFlg(false);
setCompareFlg((prev) => !prev);
}}
style={{
padding: "10px 20px",
fontSize: "16px",
cursor: "pointer",
backgroundColor: "#4CAF50",
color: "white",
border: "none",
borderRadius: "5px",
}}
>
比較
</button>
<h1>Similarity: {similarity}%</h1>
</>
);
}
export default App;
続いて以下の新規ファイルを作成します。
・src/hooks/useFaceSearch.ts
・src/utils/index.ts
・src/api/index.ts
.
├── ・・・
├── src
│ ├── api
│ │ └── index.ts ★追加
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ ├── originalSrc
│ │ │ └── compare1.png
│ │ ├── react.svg
│ │ └── targetSrc
│ │ └── compare2.png
│ ├── hooks
│ │ └── useFaceSearch.ts ★追加
│ ├── index.css
│ ├── main.tsx
│ ├── utils
│ │ └── index.ts ★追加
│ └── vite-env.d.ts
└── ・・・
src/hooks/useFaceSearch.ts
に、以下のコードを記述します。
import { useState, useEffect } from "react";
import { extractFaceFromImage } from "../utils";
import { executeCompareFace } from "../api";
export const useFaceSearch = (
originalSrc: string,
targetSrc: string,
initFlg: boolean = true,
compareSwitch = false
) => {
const [sourceFace, setSourceFace] = useState("");
const [targetFace, setTargetFace] = useState("");
const [similarity, setSimilarity] = useState("0.00");
useEffect(() => {
if (initFlg) return;
const fetcher = async () => {
try {
const res = await executeCompareFace(originalSrc, targetSrc);
if (!res.SourceImageFace?.BoundingBox) {
console.error("No face matches found.");
setSourceFace("");
setTargetFace("");
setSimilarity("顔が検出されませんでした。");
return;
}
// 比較元画像から顔を切り取り
extractFaceFromImage(
originalSrc,
res.SourceImageFace.BoundingBox,
(faceData) => {
setSourceFace(faceData);
}
);
const faceMatches = res.FaceMatches || [];
// 比較対象画像から顔を切り取り
// MEMO: 比較対象の画像では配列でレスポンスされるので、最初の顔を使用
extractFaceFromImage(
targetSrc,
faceMatches[0]?.Face?.BoundingBox,
(faceData) => {
setTargetFace(faceData);
}
);
// 類似度を取得
const similar =
faceMatches.length > 0 ? faceMatches[0]?.Similarity || 0 : 0;
// 類似度を小数点2桁に統一
setSimilarity(similar.toFixed(2));
} catch (error) {
const e = error as Error;
const msg = e.message || "不明なエラーが発生しました。";
console.error("Error during face comparison:", error);
setSourceFace("");
setTargetFace("");
setSimilarity(`エラーが発生しました。${msg}`);
}
};
fetcher();
}, [originalSrc, targetSrc, initFlg, compareSwitch]);
return {
sourceFace,
targetFace,
similarity,
};
};
src/utils/index.ts
に、以下のコードを記述します。
import type { BoundingBox } from "@aws-sdk/client-rekognition";
// 顔を切り取る関数
export const extractFaceFromImage = (
imageSrc: string,
boundingBox: BoundingBox = {},
callback: (faceImageData: string) => void
) => {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) return;
const _width = boundingBox.Width || 0;
const _height = boundingBox.Height || 0;
const _left = boundingBox.Left || 0;
const _top = boundingBox.Top || 0;
// 切り取り領域のサイズを計算
const faceWidth = _width * img.width;
const faceHeight = _height * img.height;
const faceLeft = _left * img.width;
const faceTop = _top * img.height;
// キャンバスサイズを設定
canvas.width = faceWidth;
canvas.height = faceHeight;
// 顔部分を切り取って描画
ctx.drawImage(
img,
faceLeft,
faceTop,
faceWidth,
faceHeight, // 切り取り元の位置とサイズ
0,
0,
faceWidth,
faceHeight // 描画先の位置とサイズ
);
// データURLとして返す
const faceImageData = canvas.toDataURL("image/png");
callback(faceImageData);
};
img.src = imageSrc;
};
src/api/index.ts
に、以下のコードを記述します。
※Part1ではレスポンスをMockの値を返し、Part2で実際のCompareFacesオペレーションを実装していきます。
(レスポンスについて)
- FaceMatches: 比較元の顔と閾値以上にマッチした比較対象の顔情報を提供します。複数の顔を認識します。
- SourceImageFace: 比較元の顔の情報を提供します。複数の顔がある場合は、その中で一番大きな顔1つのみを抽出します。
- BoundingBox: 顔の位置情報を割合で示しています。
- Similarity: 比較元との顔の類似度を割合で示しています。
const mockResponse = {
FaceMatches: [
{
Face: {
BoundingBox: {
Width: 0.5521978139877319,
Top: 0.1203877404332161,
Left: 0.23626373708248138,
Height: 0.3126954436302185,
},
},
Similarity: 99.98,
},
],
SourceImageFace: {
BoundingBox: {
Width: 0.5521978139877319,
Top: 0.1203877404332161,
Left: 0.23626373708248138,
Height: 0.3126954436302185,
},
},
};
export const executeCompareFace = async (
originalSrc: string,
targetSrc: string
) => {
console.log("originalSrc:", originalSrc);
console.log("targetSrc:", targetSrc);
// とりあえず、Mockのレスポンスを返す
return mockResponse;
};
動作確認
一旦ここまでのコードを実装して、ブラウザでhttp://localhost:5173/
にアクセスしてみてください。
以下のような動作ができれば成功です。
- 画面上に比較元の画像と比較対象の画像が表示されます。
- また比較ボタンをクリックすると、Mockのレスポンスが返され、類似度が表示されるはずです。
※Mockですので、まだ顔の切り取り位置が正確ではないですね。
まとめ
いかがでしたでしょうか。
今回は、Viteを使用してReactのプロジェクトを作成し、画面を作成しました。
次は、実際にRekognitionのCompareFacesをSDKから呼び出し、顔の位置と類似度の取得を実装していきます。
以上です。
最後までお読みいただき、ありがとうございました。