react-webcamで撮影した写真をAmazon Rekognitionで顔分析する

2021.08.19

この記事は公開されてから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"]を指定します。これによりAgeRangeEyeglassesSunglassesなどの属性を取得可能となります。

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
    }
  ]
}

参考

以上