TensorFlow.js と obniz で作る、笑顔あふれる職場

## 笑顔の効果
心理学者ジェレミー・ディーンは、笑顔は洞察力を高め、信頼を勝ちとると言及し、
また、マザーテレサは、単なる笑顔であっても、私たちには想像できないほどの可能性がある、という言葉を残した。

職場をよりよくすることをミッションとしている私は、今回このテーマに向き合うことにした。

---

## 笑顔になれる環境づくりの必要はなく、強制的に笑顔にさせればよい
オット・フォン・ゲーリケ・マグデブルグ大学のミュンテ博士は、笑顔を作るから楽しいという逆因果を見いだしている。
なので、近年はティール組織が話題であるが、真逆の組織レベルである、レッド組織的アプローチ、つまり圧倒的な力で支配する形で笑顔を強制していこうと思う。

---

## 笑顔でなければ痛みをともなうソリューションの設計
当初笑わなければ電撃を食らわす方法を検討していたが、識者に確認したところ死ぬ可能性があり、レッド組織どころではなくなるので見送り。
最終的に以下のアーキテクチャでおさめた。

![smilelint-arch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142668/acff2e83-0c48-116b-7b47-57c8671b94fa.png)


次は実装をみていく。

---

## ハリセンデバイスを作る

---

### 使うもの
- [obniz（オブナイズ）](https://obniz.io) 
- [シングルギヤボックス（４速タイプ）](https://www.tamiya.com/japan/products/70167/index.html)
- [トルクチューン2モーター](https://www.tamiya.com/japan/products/15484/index.html)

---

### obniz を動かしてみる
公式の[クイックスタート](https://obniz.io/ja/lessons/lessons/quickstart)を参考に動作確認。RPiと比べたら一瞬で終わって驚き。

次に、[部品を使う：モーターを動かしてみよう](https://obniz.io/ja/lessons/block_programming/lessons_block_motor)を見ながら、シングルギヤボックスをつなげてモーターを回してみる。

![obniz.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142668/1ad7c6d6-1f68-b4ba-a672-7c327bf60858.gif)


もうできた。

---

そして obniz はこのブロックプログラム形式だけでなく、ブラウザや Node.js の JavaScript で実装する形式があり、上記はこんなコードになる

```js
(async function(){
  var obniz, button, dcmotor;


  obniz = new Obniz('OBNIZ_ID_HERE');
  await obniz.connectWait();
  button = new ObnizUI.Button('text');
  dcmotor = obniz.wired("DCMotor",{"forward":0, "back":1});
  while (true) {
  await ObnizUI.Util.wait(0);
    if (button.isTouching()) {
      dcmotor.move(true);
    } else {
      dcmotor.stop();
    }
  }

})();
```

---

## 笑顔認識クライアントを作る

---

### 使うもの
- Chrome

---

### 実装する
[TensorFlow.js搭載chrome拡張で誰でも笑顔体験](https://qiita.com/water_resistant/items/ec2e91c5e4e221556c5f)にやりたいことが書いてあったので参考にさせていただき、そのままだとできなかった部分を変更。

- tracking.jsが動かなかったので、[Face Detection API](https://wicg.github.io/shape-detection-api/#face-detection-api)を使用
  - Face Detection API を使えるようにするために [Experimental Web Platform features](chrome://flags/#enable-experimental-web-platform-features) を Enabled にする
  - なぜか `new FaceDetector({ fastMode: true })` としないと顔検出しなかった
- TensorFlow.js v1.0 の [Breaking changes](https://github.com/tensorflow/tfjs/releases/tag/v1.0.0) で動かなかくなったところを変更

```js
      (async function() {
        const width =
          innerHeight / innerWidth > 0.75
            ? innerWidth
            : Math.floor((innerHeight / 3) * 4);
        const height =
          innerHeight / innerWidth > 0.75
            ? Math.floor((innerWidth / 4) * 3)
            : innerHeight;

        const faceCanvas = document.createElement("canvas");
        const faceContext = faceCanvas.getContext("2d");
        const frameCanvas = document.getElementById("frame");
        frameCanvas.setAttribute("width", width);
        frameCanvas.setAttribute("height", height);
        const frameContext = frameCanvas.getContext("2d");

        const camera = document.getElementById("camera");
        camera.setAttribute("width", width);
        camera.setAttribute("height", height);
        camera.srcObject = await navigator.mediaDevices.getUserMedia({
          video: { width, height },
          audio: false
        });

        const faceDetector = new FaceDetector({ fastMode: true });

        const model = await tf.loadLayersModel("output/model.json");

        setInterval(async () => {
          const face = (await faceDetector.detect(camera))[0];

          if (!face) {
            return;
          }

          faceContext.drawImage(
            camera,
            face.boundingBox.x,
            face.boundingBox.y,
            face.boundingBox.width,
            face.boundingBox.height,
            0,
            0,
            face.boundingBox.width,
            face.boundingBox.height
          );

          const tensor = tf.browser
            .fromPixels(faceCanvas, 1)
            .resizeNearestNeighbor([64, 64])
            .toFloat()
            .div(tf.scalar(255))
            .expandDims();

          const score = (await model.predict(tensor).data())[3];

          document.getElementById("score").innerText =
            "Smile Score: " + Math.round(score * 100);

          frameContext.clearRect(0, 0, frameCanvas.width, frameCanvas.height);
          if (score > 0.5) {
            frameContext.strokeStyle = "green";
          } else {
            frameContext.strokeStyle = "red";
          }
          frameContext.lineWidth = 4;
          frameContext.strokeRect(
            face.boundingBox.x,
            face.boundingBox.y,
            face.boundingBox.width,
            face.boundingBox.height
          );
        }, 1000);
      })();
```

---

こんな感じ
![tensor.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142668/8439fdb2-1d59-4c8d-ae3e-3e3594098f2f.gif)

---

## 結合
笑顔でない時にモーターを動かすようにするだけ

```diff
          if (score > 0.5) {
            frameContext.strokeStyle = "green";
+           motor.stop();
          } else {
            frameContext.strokeStyle = "red";
+           motor.move(true);
          }
```

---

## Demo

---

うーん、パワーが足りん・・

![smilelint.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142668/4dbcda08-6c04-71a7-088d-185227d961e6.gif)

私には少々物足りなかったので、今後より強い刺激を探究していきたいと思う。

---

## おしまい
- 今回のコード [https://github.com/boiyama/smilelint](https://github.com/boiyama/smilelint)
- 12/20 [つくばexpress.js](https://txjs.connpass.com/event/153123/)