Amazon Rekognitionでイメージの顔検出による感情分析を行う

2021.08.23

こんにちは、CX事業本部 IoT事業部の若槻です。

Amazon Rekognitionは、機械学習を利用した画像や動画の分析を簡単に実施できるAWSのサービスです。

今回は、Amazon Rekognitionでイメージの顔検出による感情分析を行ってみました。

顔検出による感情分析

Amazon Rekognitionで感情分析を行う場合は顔検出APIのDetectFacesを使用します。

DetectFacesのリクエストの構文は下記のようになります。

  • Imageパラメータには、Bytesで分析対象の画像データのバイトデータを直接指定、またはS3Objectで画像データのS3オブジェクトを指定します。
  • Attributesパラメータには、レスポンスデータに含みたい属性を指定します。既定では感情の属性はレスポンスに含まれませんが、ALLを指定することにより含めることが出来ます。
{
   "Image": { 
      "Bytes": blob,
      "S3Object": { 
         "Bucket": "string",
         "Name": "string",
         "Version": "string"
      }
   },
   "Attributes": [ "string" ]
}

DetectFacesのレスポンスのサンプルは下記のようになります(感情分析属性部分のみ抜き出しています)。Emotions属性に各タイプの感情データがリストで格納されています。

detectFacesResult.json

{
  "FaceDetails": [
    {
      "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
        }
      ]
    }
  ]
}

検出できる感情タイプ

DetectFacesで検出できる感情は下記の8タイプとなります。

感情分析結果から感情を推定

レスポンスのサンプルにあったように、感情分析の結果はどれか一つの感情タイプが検出されるのでなく、8種類の感情タイプそれぞれに対する信頼度(Confidential)が検出されたデータとなります。よって一つの感情タイプを推定したい場合は、感情検出結果のリストから信頼度の一番高い感情を1つ取得する必要があります。

下記は感情を推定するサンプルコード(TypeScript)です。感情分析結果のリストからConfidenceが最大のデータの感情を取得し、推定結果としています。

import Rekognition, {
  FaceDetailList,
  Emotions,
  Emotion,
  DetectFacesRequest,
} from 'aws-sdk/clients/rekognition';

/**
 * 顔検出での感情推定結果を取得する
 * @param imageData 画像データ(Base64)
 */
export const getEmotion = async (imageData: string): Promise<void> => {
  const rekognitionClient = new Rekognition({
    apiVersion: '2016-06-27',
  });

  const params: DetectFacesRequest = {
    Image: {
      Bytes: Buffer.from(
        imageData.replace('data:image/jpeg;base64,', ''),
        'base64',
      ),
    },
    Attributes: ['ALL'],
  };

  const rawResult = await rekognitionClient.detectFaces(params).promise();

  if (rawResult.FaceDetails?.length === 0) {
    console.log('顔は検出されませんでした。');
  } else {
    //感情検出結果のリストから信頼度の一番高い感情データを1つ取得
    const emotions = (rawResult.FaceDetails as FaceDetailList)[0]
      .Emotions as Emotions;
    const largestEmotionConfidence = Math.max(
      ...(emotions.map((d) => d.Confidence) as number[]),
    );
    const emotion = emotions.find(
      (d) => d.Confidence === largestEmotionConfidence,
    ) as Emotion;

    console.log(emotion);
  }
};

React(react-webcam)で感情推定してみる

先日投稿したエントリの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,
  Emotions,
  Emotion,
} 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 getEmotion = (rekognizeResult: DetectFacesResponse): string => {
  const emotions = (rekognizeResult.FaceDetails as FaceDetailList)[0]
    .Emotions as Emotions;
  const largestEmotionConfidence = Math.max(
    ...(emotions.map((d) => d.Confidence) as number[])
  );
  const emotion = emotions.find(
    (d) => d.Confidence === largestEmotionConfidence
  ) as Emotion;
  return emotion.Type as string;
};

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>{"Emotion: " + getEmotion(rekognizeResult)}</div>
            </div>
          )}
        </>
      )}
    </>
  );
};

export default App;
  • CALM(穏やか)

  • SURPRISED(驚き)

  • HAPPY(幸せ)

  • SAD(悲しみ)

  • CONFUSED(混乱)

狙った感情を分析結果で出すのがなかなか難しくてゲームみたいになりました。

参考

以上