うさみちゃん会議中の顔怖っ! (face-api.jsで表情検知)

## はじめに
[Ateam Lifestyle Inc. Advent Calendar 2020](https://qiita.com/advent-calendar/2020/ateam-lifestyle) 4日目は
株式会社エイチームライフスタイルの @engabesi が担当します。

弊社は情勢に合わせ在宅と出社を任意で選択できる勤務体制になっています。(2020/12/03時点)
その際、Web会議中の参加者が皆真顔で怖い時があるという声を聞きました。

画面越しとはいえ、真顔で囲まれると緊張しますよね。

というわけで今回は自分がその囲む側にならないように、
真顔で数秒間いると戒めとして写真と時刻、回数を表示するものを作りました。

## 成果物
github: https://github.com/engabesi/serious-face-detector
githubPages: https://engabesi.github.io/serious-face-detector/
(試すにはカメラの許可とハードウェアアクセラレーションをオンにする必要があります)

## 環境 & 使用したもの
* Windows 10 2004
* Logicoolの1600円のウェブカメラ
* `face-api.js`
    * `face-api.js`に関しては前回の記事にて記載しましたのでそちらを御覧下さい。
    * https://qiita.com/engabesi/items/e08752d1341b7b2c6010

## やること
* 土台(カメラと顔矩形取得)は[前回のニコ○ス](https://qiita.com/engabesi/items/e08752d1341b7b2c6010)を流用
* 表情取得
* 撮影処理
* 実際に使用

### 土台作成
前回のリポジトリをimportし、不要になるニコ○ス成分を削除
(prettierの設定違いにより他の変更も含まれてます)
https://github.com/engabesi/serious-face-detector/commit/66db596368dd6d1b21c220642dd84d4fb28a70a9

### 表情
表情の値を取得します。
ロードするモデルを追加と呼び出すメソッドを1つ追加するだけです。

```javascript:docs\index.js
// ===中略===
const loadModels = async () => {
  await Promise.all([
    faceapi.nets.tinyFaceDetector.loadFromUri(`${gitPagesPath}/js/lib/models`),
+   faceapi.nets.faceExpressionNet.loadFromUri(`${gitPagesPath}/js/lib/models`),
  ]);
};

(async () => {
// ===中略===
    setInterval(async () => {
      const results = await faceapi
        .detectAllFaces(
          video,
          new faceapi.TinyFaceDetectorOptions(tinyFaceDetectorOption)
        )
+       .withFaceExpressions();
// ===中略===
```

先程の記述を追加すると、`result`の中に表情とその割合がobjectで含まれます。
これを`asSortedArray()`で割合順にソートし、一番%が高い表情によってポイントを増減させます。
このポイントが100になったら撮影等の処理を走らせます。
https://github.com/engabesi/serious-face-detector/commit/e27e58933f90ac03d0688bad4fcae78e9ec48bad

```javascript:docs\index.js
// ===中略===
+ const calcExpressionPoint = (expression) => {
+   switch (expression) {
+     case "neutral":
+       return 1;
+     case "angry":
+     case "disgusted":
+       return 2;
+     case "happy":
+       return -5;
+     default:
+       return 0;
+   }
+ };

  (async () => {
// ===中略===
+     // 真顔ポイント
+     let seriousPoint = 0;

      setInterval(async () => {
// ===中略===
        const resizedResults = faceapi.resizeResults(results, displaySize);
        resizedResults.forEach((result) => {
+         const expression = result.expressions.asSortedArray()[0].expression;
+         seriousPoint += calcExpressionPoint(expression);
+         if (seriousPoint >= 100) {
+           // 画像出力
+           seriousPoint = 0;
+         }
        });
```

### 撮影処理
ポイントが100になった時の撮影処理を実装します。
canvasを隣に設置、100になった際に描画します。
https://github.com/engabesi/serious-face-detector/commit/30ea477895a29edbbef25ff064f62d3143f52899

htmlにcanvasと撮影時間表示用のlabelを設置。
次にjsに撮影処理を書きます。

```javascript:index.js
+     let seriousCount = 0;

+     const pictureCanvas = document.querySelector("canvas");
+     const timestampLabel = document.querySelector("#timestamp");
      setInterval(async () => {
// ===中略===
          if (seriousPoint >= 100) {
            // 画像出力
+           pictureCanvas
+             .getContext("2d")
+             .drawImage(video, 0, 0, pictureCanvas.width, pictureCanvas.height);
            seriousPoint = 0;
+           seriousCount++;
+           timestampLabel.textContent = `${seriousCount}回目 ${new Date()}`;
          }
```

### 効果音追加
これだけじゃスッと自分の真顔が映し出されるだけで味気無いので効果音も足します。
https://github.com/engabesi/serious-face-detector/commit/006fc9d7fef26b0490287b112d33774c07aca873

* index.htmlに`audio`と`source`追加
* `audio/warning.mp3`追加
* jsの撮影処理時に`play()`で音を鳴らす

これで完成です

## 実際に使ってみた
15分の会議中に自分の顔を映して何回アラートが鳴るか試してみました。
![sono1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469399/8ed5a7b5-e25c-beac-6311-fbbe76c275fd.png)

結構意識してるつもりなのですがそれでもこの回数。
大体10秒真顔で1回なので、単純計算で630秒 = 10分30秒ぐらい真顔の時間が。
実際には笑顔でポイントを稼いでいるところがあるので更に長くなります。
頷いたりして負担を和らげるように心がけてはいますが…。気をつけます。

## 〆
自分が思ってる以上に怖い顔をしている時間が長いということを改めて思い知らされました。
皆さんもWeb会議問わず気をつけましょう。

明日は @dayamaguchi1 さんです。
ECS on EC2の記事を書いてくださるそうなのでお楽しみに！