この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは、CX事業本部 IoT事業部の若槻です。
Amazon Rekognitionは、機械学習を使用した画像や動画の分析を簡単に実施できるAWSのサービスです。
今回は、Reactのカメラ撮影ライブラリreact-webcamで撮影した写真を、ローカルで起動したアプリからAmazon Rekognitionを使用して顔分析してみました。
構成
コード概要
Reactアプリケーションのコードです。
src/App.tsx
import { useRef, useState, useCallback } from "react";
import Webcam from "react-webcam";
import { makeStyles } from "@material-ui/core/styles";
import {
DetectFacesRequest,
DetectFacesResponse,
FaceDetailList,
} from "aws-sdk/clients/rekognition";
import AWS from "aws-sdk";
const useStyles = makeStyles(() => ({
webcam: {
position: "absolute",
top: "0px",
left: "0px",
visibility: "hidden",
},
rekognizeResult: {
flex: 1,
width: "100%",
flexDirection: "row",
justifyContent: "flex-start",
},
}));
const videoConstraints = {
width: 720,
height: 360,
facingMode: "user",
};
AWS.config.update({
accessKeyId: process.env.REACT_APP_AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.REACT_APP_AWS_SECRET_ACCESS_KEY,
region: process.env.REACT_APP_AWS_REGION,
});
const rekognitionClient = new AWS.Rekognition({
apiVersion: "2016-06-27",
});
//Amazon Rekognitionによる顔分析
const detectFaces = async (imageData: string): Promise<DetectFacesResponse> => {
const params: DetectFacesRequest = {
Image: {
Bytes: Buffer.from(
imageData.replace("data:image/jpeg;base64,", ""),
"base64"
),
},
Attributes: ["ALL"],
};
return await rekognitionClient.detectFaces(params).promise();
};
//分析結果からConfidence(分析結果の信頼度)取得
const getConfidence = (rekognizeResult: DetectFacesResponse): number => {
return (rekognizeResult.FaceDetails as FaceDetailList)[0].Confidence!;
};
//分析結果からLowAge(推測される年齢範囲の加減)取得
const getLowAge = (rekognizeResult: DetectFacesResponse): number => {
return (rekognizeResult.FaceDetails as FaceDetailList)[0].AgeRange?.Low!;
};
//分析結果からHighAge(推測される年齢範囲の上限)取得
const getHighAge = (rekognizeResult: DetectFacesResponse): number => {
return (rekognizeResult.FaceDetails as FaceDetailList)[0].AgeRange?.High!;
};
//分析結果からEyeglasses(眼鏡を掛けているか)取得
const getIsWearingEyeGlasses = (
rekognizeResult: DetectFacesResponse
): boolean => {
return (rekognizeResult.FaceDetails as FaceDetailList)[0].Eyeglasses?.Value!;
};
//分析結果からEyeglasses(サングラスを掛けているか)取得
const getIsWearingSunGlasses = (
rekognizeResult: DetectFacesResponse
): boolean => {
return (rekognizeResult.FaceDetails as FaceDetailList)[0].Sunglasses?.Value!;
};
const App = () => {
const classes = useStyles();
const [isCaptureEnable, setCaptureEnable] = useState<boolean>(false);
const webcamRef = useRef<Webcam>(null);
const [url, setUrl] = useState<string | null>(null);
const capture = useCallback(() => {
const imageSrc = webcamRef.current?.getScreenshot();
if (imageSrc) {
setUrl(imageSrc);
setRekognizeResult(undefined);
}
}, [webcamRef]);
const [rekognizeResult, setRekognizeResult] = useState<DetectFacesResponse>();
const rekognizeHandler = async () => {
const result: DetectFacesResponse = await detectFaces(url as string);
setRekognizeResult(result);
console.log(result);
};
return (
<>
<header>
<h1>カメラアプリ(顔分析付き)</h1>
</header>
{isCaptureEnable || (
<button onClick={() => setCaptureEnable(true)}>開始</button>
)}
{isCaptureEnable && (
<>
<div>
<button onClick={() => setCaptureEnable(false)}>終了</button>
</div>
<div>
<Webcam
audio={false}
width={540}
height={360}
ref={webcamRef}
screenshotFormat="image/jpeg"
videoConstraints={videoConstraints}
className={classes.webcam}
/>
</div>
<button onClick={capture}>キャプチャ</button>
</>
)}
{url && (
<>
<div>
<button
onClick={() => {
setUrl(null);
setRekognizeResult(undefined);
}}
>
削除
</button>
<button onClick={() => rekognizeHandler()}>分析</button>
</div>
<div>
<img src={url} alt="Screenshot" />
</div>
{typeof rekognizeResult !== "undefined" && (
<div className={classes.rekognizeResult}>
<div>{"Confidence: " + getConfidence(rekognizeResult)}</div>
<div>
{"AgeRange: " +
getLowAge(rekognizeResult) +
" ~ " +
getHighAge(rekognizeResult)}
</div>
<div>
{"Eyeglasses: " + getIsWearingEyeGlasses(rekognizeResult)}
</div>
<div>
{"Sunglasses: " + getIsWearingSunGlasses(rekognizeResult)}
</div>
</div>
)}
</>
)}
</>
);
};
export default App;
- react-webcamによる撮影部分は前回の記事の実装を踏襲しています。
- ローカルでの起動前提のため、アプリケーションからAWS SDKを使用して直接Amazon Rekognitionを叩き結果を取得します。
- react-webcamで取得したキャプチャデータをBase64デコードしてByteデータに変換した上で
detectFaces
に渡し、分析結果を取得しています。 - 分析結果にすべての属性を含めたい場合は、パラメータに
Attributes: ["ALL"]
を指定します。これによりAgeRange
、Eyeglasses
、Sunglasses
などの属性を取得可能となります。
IAMアクセスキーの発行
ReactアプリからのRekognitionの使用はIAMアクセスキーを用いて行います。
IAMユーザーに下記の顔検出(DetectFaces)を実施可能とするポリシーをアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "rekognition:DetectFaces",
"Resource": "*"
}
]
}
ユーザーのアクセスキーを発行します。
動作確認
発行したアクセスキーのクレデンシャル情報を環境変数に指定します。
$ export AWS_ACCESS_KEY_ID="<アクセスキーID>"
$ export AWS_SECRET_ACCESS_KEY="<アクセスシークレットキー>"
$ export AWS_REGION="<AWSリージョン>"
アプリをローカルで起動します。
$ REACT_APP_AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
REACT_APP_AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
REACT_APP_AWS_REGION=${AWS_REGION} \
npm run start
[開始]をクリックするとカメラが起動します。
[キャプチャ]をクリックします。
カメラで取得された画像が表示されます。[分析]をクリックします。
Rekognitionの分析結果が取得できました。年齢の範囲が12〜22歳とのこと。眼鏡を掛けていることをちゃんと検知できています。
裸眼。眼鏡もサングラスも掛けていないことを検知できています。
サングラスを掛けていることを検知できています。Confidenceに大きな違いはありませんが、年齢の上限が少し上がりました。
不審者スタイル。これもConfidenceに大きな違いはありませんが、年齢の下限と上限が10歳近く上がりました。顔が隠れていると子供とは判定されないようです。
detectFacesの取得結果のRawデータ(サンプル)
最後に、detectFacesの取得結果のRawデータは下記のようになります。検出した人の顔の数だけFaceDetails
配下にデータが入ります。BoundingBox
は画像内での人の顔の位置情報です。これにより検出された人の顔を画像上で矩形で囲むなど出来ます。またEmotions
により感情も取得することができます。
detectFacesResult.json
{
"FaceDetails": [
{
"BoundingBox": {
"Width": 0.20062141120433807,
"Height": 0.40441733598709106,
"Left": 0.15825751423835754,
"Top": 0.1316724270582199
},
"AgeRange": {
"Low": 21,
"High": 33
},
"Smile": {
"Value": true,
"Confidence": 92.25345611572266
},
"Eyeglasses": {
"Value": true,
"Confidence": 99.9189682006836
},
"Sunglasses": {
"Value": true,
"Confidence": 99.63880157470703
},
"Gender": {
"Value": "Female",
"Confidence": 99.3447036743164
},
"Beard": {
"Value": false,
"Confidence": 99.50582122802734
},
"Mustache": {
"Value": false,
"Confidence": 99.86498260498047
},
"EyesOpen": {
"Value": true,
"Confidence": 99.99996948242188
},
"MouthOpen": {
"Value": true,
"Confidence": 99.09195709228516
},
"Emotions": [
{
"Type": "HAPPY",
"Confidence": 97.60749053955078
},
{
"Type": "SURPRISED",
"Confidence": 0.7658454179763794
},
{
"Type": "CONFUSED",
"Confidence": 0.5557805299758911
},
{
"Type": "ANGRY",
"Confidence": 0.3515094518661499
},
{
"Type": "DISGUSTED",
"Confidence": 0.34102049469947815
},
{
"Type": "CALM",
"Confidence": 0.19356855750083923
},
{
"Type": "FEAR",
"Confidence": 0.13799776136875153
},
{
"Type": "SAD",
"Confidence": 0.046782251447439194
}
],
"Landmarks": [
{
"Type": "eyeLeft",
"X": 0.2314133197069168,
"Y": 0.28564053773880005
},
{
"Type": "eyeRight",
"X": 0.32332608103752136,
"Y": 0.2762904763221741
},
{
"Type": "mouthLeft",
"X": 0.24602198600769043,
"Y": 0.4382118284702301
},
{
"Type": "mouthRight",
"X": 0.3227347433567047,
"Y": 0.42984768748283386
},
{
"Type": "nose",
"X": 0.29952266812324524,
"Y": 0.3556353449821472
},
{
"Type": "leftEyeBrowLeft",
"X": 0.18859225511550903,
"Y": 0.2570442855358124
},
{
"Type": "leftEyeBrowRight",
"X": 0.2242126166820526,
"Y": 0.23221562802791595
},
{
"Type": "leftEyeBrowUp",
"X": 0.25469353795051575,
"Y": 0.23624512553215027
},
{
"Type": "rightEyeBrowLeft",
"X": 0.30750370025634766,
"Y": 0.23125456273555756
},
{
"Type": "rightEyeBrowRight",
"X": 0.33047568798065186,
"Y": 0.2219356745481491
},
{
"Type": "rightEyeBrowUp",
"X": 0.34847506880760193,
"Y": 0.2408193200826645
},
{
"Type": "leftEyeLeft",
"X": 0.21244211494922638,
"Y": 0.2877286374568939
},
{
"Type": "leftEyeRight",
"X": 0.24936801195144653,
"Y": 0.2853952646255493
},
{
"Type": "leftEyeUp",
"X": 0.23162560164928436,
"Y": 0.2777021527290344
},
{
"Type": "leftEyeDown",
"X": 0.2320021092891693,
"Y": 0.2925541400909424
},
{
"Type": "rightEyeLeft",
"X": 0.30489200353622437,
"Y": 0.27982351183891296
},
{
"Type": "rightEyeRight",
"X": 0.3370468020439148,
"Y": 0.27489253878593445
},
{
"Type": "rightEyeUp",
"X": 0.3239697217941284,
"Y": 0.268352746963501
},
{
"Type": "rightEyeDown",
"X": 0.3230580687522888,
"Y": 0.28313031792640686
},
{
"Type": "noseLeft",
"X": 0.2710249722003937,
"Y": 0.37860503792762756
},
{
"Type": "noseRight",
"X": 0.3051494359970093,
"Y": 0.3749985694885254
},
{
"Type": "mouthUp",
"X": 0.29134562611579895,
"Y": 0.4123360216617584
},
{
"Type": "mouthDown",
"X": 0.2906356453895569,
"Y": 0.4593968093395233
},
{
"Type": "leftPupil",
"X": 0.2314133197069168,
"Y": 0.28564053773880005
},
{
"Type": "rightPupil",
"X": 0.32332608103752136,
"Y": 0.2762904763221741
},
{
"Type": "upperJawlineLeft",
"X": 0.14750327169895172,
"Y": 0.305320143699646
},
{
"Type": "midJawlineLeft",
"X": 0.17698229849338531,
"Y": 0.46644243597984314
},
{
"Type": "chinBottom",
"X": 0.28578469157218933,
"Y": 0.5409565567970276
},
{
"Type": "midJawlineRight",
"X": 0.34007012844085693,
"Y": 0.4494510591030121
},
{
"Type": "upperJawlineRight",
"X": 0.34829065203666687,
"Y": 0.2851712107658386
}
],
"Pose": {
"Roll": -1.3549495935440063,
"Yaw": 19.644981384277344,
"Pitch": 8.039366722106934
},
"Quality": {
"Brightness": 70.20365142822266,
"Sharpness": 95.51618957519531
},
"Confidence": 99.99923706054688
}
]
}
参考
- イメージ内の顔を検出するには - Amazon Rekognition
- /javascript
- 【React Native】これを読めば誰でも分かるFlexBoxの基本 | プログラマーになった 「中卒」 男のブログ
- Reactでちょっとだけ環境変数を使いたい
以上