【うわっ...私の表情、硬すぎ...？】face-api.jsで顔検出して感情と年齢を判定する

# はじめに
コロナ禍が収まってきたとはいえ、オンラインでミーティングする機会は多いですね。  
ZOOMやGoogleMeetでミーティングを行う際、自分の顔を写している人も多いのではないでしょうか？  

ミーティング中、画面に映される自分の無表情の顔。  
**なんか疲れてない？**  
![hyoujou_shinda_me_woman.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/9ab38f76-3345-d360-b721-e64393da0376.png)

ミーティング中、相槌を打ちながら微笑む自分の顔。  
**なんかうまく笑えてなくない？**  
![nihil_smile_man.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/fe151677-696e-21b3-9dcb-f5327dc8c8b7.png)

そもそも自分の顔。  
**なんか老けてない？**  
![pose_unzari_woman.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/5ed79b46-b411-99bd-b517-39c696fc6e68.png)

「無表情でいるつもりでも他人からしたら怒って見えているかも。。。」  
「笑っているつもりでも他人から見たら変な顔に見えてるかも。。。」  
そんなことを考え始めると自分の表情が他人にどう見えているのか気になって夜も眠れません。  

そこで考えました。  
**自分の顔をAIに認識させてどんな感情かを判定してもらえば面白いのでは?**  
**ついでに年齢も何歳に見えるか判定してもらえば良いのでは？**  

今回のこの記事では**face-api.js**を利用して、PCのインカメラで撮影した自分の顔を解析してもらいます。  

**最終的に作成したものがこちらになります↓**  
**https://merry-paprenjak-7d004b.netlify.app/** 

共同執筆者: @sarahlovesplum @ymmt1089

# そもそもface-api.jsって何？
**face-api.js** は、顔検出・顔認識を行うためのJavaScriptAPIです。  
コア部分はtensorflow.js（tensorflow/tfjs-core）上に実装されています。
[公式のデモページ](https://justadudewhohacks.github.io/face-api.js/face_and_landmark_detection)  

# 手順

## プロジェクトのディレクトリ構成
📂project
 ├ 📂models(学習モデル)
 ├ 📄face-api.mim.js(face-apiライブラリ)
 ├ 📄index.html
 └ 📄script.js

## 学習モデルのダウンロード
まず、**fase-api** のGithubから学習モデルをダウンロードしておきます。  
以下リンクからモデルをダウンロードしてmodels配下に配置します。  
https://github.com/justadudewhohacks/face-api.js/tree/master/weights

### 学習モデルの種類と説明
ダウンロードした学習モデルについて説明します。
画像は公式から引用: https://justadudewhohacks.github.io/face-api.js/docs/index.html

* **ssdMobilenetv**
  ![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/ded7aa29-2e24-82a9-e432-8346b104d9a3.gif)
  精度が高い顔検出。バウンディングボックスと精度を取得。

* **tinyFaceDetector**
  detectSingleFace()で検出した顔の中で、もっとも信頼度が高い顔を1つだけ検出します。複数人を検出したい場合は、tinyFaceDetectorのdetectAllFaces()で検出可能です。  
  推論が高速な顔検出。バウンディングボックスと精度を取得。

* **faceLandmark68Net**
  ![2.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/56e7c795-8922-7a2b-6cd9-dead048f4ca8.jpeg)
  顔ランドマークを検出。68個の顔のポイントを取得。

* **faceRecognitionNet**
  ![5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/0c469234-cf24-0c35-87f1-bf19c63e4293.gif)
  2つの顔の類似性を判断。

* **faceExpressionNet**
  ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/76559e4b-9a71-f906-c73a-6a30ca08f1e9.png)
  顔の表情の分類。以下の表情で分類されます。  
  neutral, happy, sad, angry, fearful, disgusted, surprised

* **ageGenderNet**
  ![4.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/1dde1702-0f1c-94fd-3b97-4eb5e5f33708.jpeg)
  年齢と性別を推定。

## face-api.min.jsのダウンロード
face-api.jsのライブラリ本体もダウンロードしておきます。  
以下のリンクからface-api.min.jsをダウンロードしてprojectファイル直下に配置します。  
https://github.com/justadudewhohacks/face-api.js/tree/master/dist

## index.htmlの作成
index.htmlを作成します。  
必要なscriptを読み込んで、bodyにvideoタグを追加します。  

```html: index.html
<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <script defer src="face-api.min.js"></script>
    <script defer src="script.js"></script>
    <style>
      body {
        margin: 0;
        padding: 0;
        width: 100vw;
        height: 100vh;
        display: flex;
        justify-content: center;
        align-items: center;
        background-color: yellow;
      }

      canvas {
        position: absolute;
      }
    </style>
  </head>
  <body>
    <video id="video" width="720" height="560" autoplay muted></video>
  </body>
</html>

```

## script.jsの作成
scriptで使用したい学習モデルを読み込んで、実際に使用しています。  
簡単な説明をコードの横に記載しています。  

```js: script.js
const video = document.getElementById("video");

Promise.all([
  faceapi.nets.tinyFaceDetector.loadFromUri("./models"), //カメラの中の顔を探すmodule
  faceapi.nets.faceLandmark68Net.loadFromUri("./models"), //目、鼻、口を探すmodule
  faceapi.nets.faceRecognitionNet.loadFromUri("./models"), //顔付きボックス
  faceapi.nets.faceExpressionNet.loadFromUri("./models"), //表情を判断するmodule
  faceapi.nets.ageGenderNet.loadFromUri("./models"), //年齢性別を判断するmodule
]).then(startVideo);

function startVideo() {
  navigator.mediaDevices
    .getUserMedia({ video: true })
    .then(function (stream) {
      video.srcObject = stream;
    })
    .catch(function (err) {
      console.error(err);
    });
}

video.addEventListener("play", () => {
  const canvas = faceapi.createCanvasFromMedia(video);
  document.body.append(canvas);
  const displaySize = { width: video.width, height: video.height };
  faceapi.matchDimensions(canvas, displaySize);
  setInterval(async () => {
    const detections = await faceapi
      .detectAllFaces(video, new faceapi.TinyFaceDetectorOptions()) //カメラの中にいる顔をすべて認識
      .withFaceLandmarks() //目、鼻、口を探す
      .withFaceExpressions() ////表情を判断する
      .withAgeAndGender(); //年齢性別を判断する
    const resizedDetections = faceapi.resizeResults(detections, displaySize);
    canvas.getContext("2d").clearRect(0, 0, canvas.width, canvas.height); //顔に付いて回るボックス
    faceapi.draw.drawDetections(canvas, resizedDetections); //顔に箱付きの表現
    faceapi.draw.drawFaceLandmarks(canvas, resizedDetections); //目鼻口点線表現
    faceapi.draw.drawFaceExpressions(canvas, resizedDetections); //感情情報表現
    resizedDetections.forEach((detection) => {
      //年齢、性別表現ボックス
      const box = detection.detection.box;
      const drawBox = new faceapi.draw.DrawBox(box, {
        label: Math.round(detection.age) + " year old " + detection.gender,
      });
      drawBox.draw(canvas);
    });
  }, 100);
});

```

## VSCodeで動作確認するために便利な拡張機能
動作確認するためにはローカルサーバーを立ち上げる必要がありますが、VSCodeの場合は拡張機能の[LiveServer](https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer)を活用することで、ワンクリックでローカルサーバーを立ち上げることができます。  

使用方法は以下参照
https://webdesign-trends.net/entry/14461

# 動作確認
![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/1b2a6bd1-0327-ee23-b81d-2d2b6cd5dcb9.gif)

実際に動かしてみると、顔を青枠で認識して、枠上部に年齢、枠下部に感情とパーセンテージが表示されます。　　
しっかり顔を検出できていますね。  

happy
![happy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/16eb6a73-5db8-1ffc-7a3c-5e2f2b8110d3.png)

angry
![angry.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/8d9b3771-baa6-ff25-5814-d1a90673f66b.png)

disgusted
![disgusted.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/064516dd-6a06-9c59-c38d-9750c6657b7d.png)

suprised
![suprised.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/506401/017faf6b-c5ab-e5a1-9cca-8bf4d64aeb0d.png)

# おわりに
face-api.jsのライブラリを読み込ませるだけで顔認識から表情た年齢を割り出すことができました。  
これで他人からみて自分の表情がどう見えているかがわかりますね。これで夜も安心して眠れそうです。    
しかも検出された年齢が実年齢より低めなのもいいですね。テンション上がります。  
年齢が低めに表示されるのは、大元の学習モデルにアジア系の学習データが少ないからかもしれません(アジア系の人は欧米系の人より年齢が若く見られるみたいなので)  

今回は動画から顔を検出して感情や年齢を出しましたが、画像から検出したり他の学習モデルを追加することで色々なことができそうです。  

# 参考
* [fase-api.js](https://justadudewhohacks.github.io/face-api.js/docs/index.html)
* [FaceAPIであっさり顔認識](https://zenn.dev/sdkfz181tiger/articles/3b03385165d4f2)