だらけちゃうので機械学習を使って女の子に叱ってもらった


##概要
みなさんこんにちは。今回初Qiita記事を投稿します、フロントエンドエンジニアのブンです。

さて、2020年は多数のエンジニアがリモートワークをしています。

リモートワーク生活が長く続いていますが、**周りに人がいない**せいか、ついだらけてしまうなんてことはありませんか？
あまりワクワクしないタスクなどを目の前にすると、つい適当な理由をつけて先延ばししようとしてしまいますよね。

**「メールに返事しなきゃいけないけど、とりあえず皿でも洗うか」**
**「このドキュメントすすめなきゃいけないけど、とりあえず歯間ブラシするか」**
**「このレビューしなきゃリリース遅れるけど、とりあえず洗濯ものが乾いてるか見るか」**

などなど、なにかとそれなりの理由をつけて**急に立ち上がり**仕事から離脱しようとしてしまうのが人間の常なのです（みんなそうだよね？）。

そもそも人間は進化心理学的に、短期的な快楽を追い求め苦痛からは逃げようとするように脳にプログラムされているので、このような行動は至極当たり前です（[参考](https://note.com/bouldering_hack/n/n8877155fc525)）。

しかし本能に従って生きることが必ずしも正ではないと[ブッダも言っている](https://www.a-inquiry.com/buddha115/)のですべき仕事はしたいです。

ただ、仕事をきちんとするためには監視員が必要です。

そこで**AI（今回は機械学習）を使えば監視員を人工的に作り出せる**と考え、
**立ち上がろうとすると女の子が叱ってくれるアプリを作りました。**

その名も**No-Standing-App**です（upとappがかかっていて非常に上手い）。

##できたもの
https://bunhojun.github.io/no-standing-app/

####使いかた
Google Chrome 推奨です。
1. ブラウザでウェブカメラ利用の許可をする
2. テキストが「準備OKだよ！」と表示されたら、立ちあがろうとする
3. 女の子に叱られる(特に大きくしているわけではありませんが音量注意です)

以上です。やりたいことはお分かりになられたでしょう。
きわめてコードをシンプルにしたかったので最低限のUIしかありません。

##使用ライブラリ・機械学習モデル
###ml5.js（※以下ml5と呼びます。）
https://ml5js.org/ 
機械学習用JavaScriptライブラリです。
すでに学習済みの機械学習モデルを利用できます。ほかには自分が用意した画像で機械学習ができたりします。
**フロントエンドしかできない人にも問題なく使えます。**とにかく簡単ですごい。
JavaScriptで機械学習を使うといえば[TensorFlow.js](https://www.tensorflow.org/js)が有名ですが、今回は初心者向けのml5を使用しました。
[※機械学習とは](https://ainow.ai/2019/11/26/180809/)
[※機械学習モデルとは](https://www.dsk-cloud.com/blog/what-is-machine-learning-model#:~:text=%E3%80%8C%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%83%A2%E3%83%87%E3%83%AB%E3%80%8D%E3%81%A8%E3%81%AF%E3%80%81%E3%83%87%E3%83%BC%E3%82%BF%E8%A7%A3%E6%9E%90%E3%82%92%E8%A1%8C%E3%81%86,%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%99%E3%80%82&text=%E3%82%B3%E3%83%B3%E3%83%94%E3%83%A5%E3%83%BC%E3%82%BF%E3%83%BC%E3%81%8C%E5%8F%97%E3%81%91%E5%8F%96%E3%81%A3%E3%81%9F%E5%85%A5%E5%8A%9B,%E5%87%BA%E5%8A%9B%E3%81%99%E3%82%8B%E3%81%A8%E3%81%84%E3%81%86%E3%82%82%E3%81%AE%E3%81%A7%E3%81%99%E3%80%82)

####PoseNet
https://learn.ml5js.org/#/reference/posenet
PoseNetは人間のポーズ推定に特化した機械学習モデルです。TensorFlow.jsでもml5でも使えるようです。
これを使うとウェブカメラに写っている**顔や体のパーツがウェブカメラスクリーン上のどこの座標にあるか**判定してくれます。

####p5.js
https://p5js.org/
ml5を学習するうえでよく併用される描画系ライブラリ。ウェブカメラの描画などを楽々やってくれます。ml5との相性はいいですが、一応**ml5はp5.jsなしでも書ける**ようです（記事下部の参考を参照）。

####p5.sound
https://p5js.org/reference/#/libraries/p5.sound
p5.jsの派生ライブラリで、音声ファイルのロードと再生に使用しました。

####※音声は以下のページからダウンロードしました。
[効果音ラボ - 商用無料、報告不用の効果音素材をダウンロード](https://soundeffect-lab.info/sound/voice/line-girl1.html)

##Let's code
####基本アルゴリズム
* ウェブカメラ上に写っている顔の、**鼻の座標位置**によって立ち上がっているかどうか判定
* 立ち上がっていたら女の子の声で「こら」と叱る

####html
ますはhtmlファイルから。
index.htmlを一つ呼ぶだけにして、今回はCDNでライブラリは読み込みます。
pタグを用意して、ml5のモデル群が読みこめているかを表出します（詳細は後述）。
JavaScriptはsketch.jsというファイルで記述していきます。

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

<head>
  <meta charset="UTF-8">
  <title>Image classification using MobileNet and p5.js</title>
  <!-- CDNでライブラリを読み込む。p5.js/p5.soundとml5をここで呼びます -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/addons/p5.sound.js"></script>
  <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
</head>

<body>
  <h1>立ち上がっちゃだめだよ</h1>
  <p id="message">loading</p>
  <script src="sketch.js"></script>
</body>

</html>
```
####JavaScript

#####準備

まずはp5.jsとml5の準備に必要なコードを書きます。p5.js/p5.soundのためのボイラープレートが多いのですが、今回は詳説しません。詳しくは上記に貼ったp5.jsのドキュメントを見てください。

```sketch.js
// 「こら」という声を発生するためのインスタンスを入れるためのグローバル変数。
let kora; 

// p5.js/p5.soundで用意されたプログラム開始前に呼ばれる関数。これで音声の使用ができるようになる。
function preload() {
  soundFormats('mp3');
  kora = loadSound('assets/kora');
}

// p5.jsで用意されたプログラム開始時に実行される関数。
function setup() {
  // createCaptureを使うとp5.jsがvideo要素（ウェブカメラ要素）をdomツリーに作り出してくれる。VIDEOはp5.jsが用意したグローバル変数。
  const video = createCapture(VIDEO);
  // ml5のPoseNetモデルを読み込むための記述。video要素とコールバックを引数にとる。
  const poseNet = ml5.poseNet(video, modelLoaded);
  // PoseNetが人間のポーズを感知した時のリスナー。コールバックのgotPosesについては後述。
  poseNet.on('pose', gotPoses);
}

// ml5の機械学習モデル（今回はPoseNet）が読み込まれた時に呼ばれるコールバック関数。
function modelLoaded() {
  const message = document.querySelector('#message');
  message.innerHTML = '準備OKだよ！';
}
```


大事なものをピックアップします。

```sketch.js

const poseNet = ml5.poseNet(video, modelLoaded); 
```

これによって、PoseNetモデルを使用できるように宣言してます。``ml5.poseNet()``はvideo要素とモデルが読み込まれた時のコールバックを引数に入れます。今回は先ほど述べたpタグに、準備完了の旨を表示させる``modelLoaded()``という関数を実行するようにしました。

そして次に重要なのは以下です。

```sketch.js
poseNet.on('pose', gotPoses);
```

ここではPoseNetがウェブカメラを通して感知する人間のポーズのリスナーを設定してます。１秒間に何回もコールバックは発火します。
今回は``gotPoses()``という関数を呼ぶことにしてます。詳細は下に続きます。


#####ポーズを感知した時

先ほどの``poseNet.on('pose', gotPoses)``で呼ばれた``gotPoses()``について見ていきたいのですが、その前に1つポイントがあります。
PoseNetのポーズのリスナーはコールバック関数に引数を渡します。
この引数を見てみると、配列が返ってきています。

```
[
  {
    pose: {
      keypoints: [{position:{x,y}, score, part}, ...],
      leftAngle:{x, y, confidence},
      leftEar:{x, y, confidence},
      leftElbow:{x, y, confidence},
      // 今回はnoseを使うのでここだけ詳しく書く
      nose: {
        confidence: 0.9991235136985779
        x: 361.67679872030413
        y: 199.94389418961936
      }
      ...
    },
    // 今回は無視
    skeleton: [...]
  }
]
```
この配列にはオブジェクトがあり、
その中にさらにposeとskeletonというオブジェクトが入ってます。今回は**pose**を使います。
このposeはウェブカメラに写っている**体のパーツのそれぞれの座標位置**を教えてくれます。
今回は鼻のy座標が画面上部に移動したら女の子に叱られるというアルゴリズムなので、
poseの中の**nose**というプロパティから**y座標を取り出します**。
この**y座標が50未満になったら立ち上がろうとしている**と判断することにしましょう。※鼻が上に行くほどｙ座標は小さくなる

そしてgotPoses()はこんな感じになります。

```sketch.js
// 人間のポーズを感知した時に発火される関数
function gotPoses(poses) {
  if (poses && poses[0]) {
    const pose = poses[0].pose;
    const noseY = pose.nose.y;
    if (noseY < 50) {
      // 鼻のｙ座標が50より小さい時=立ち上がっている時
      onStandingUp();
    } else {
      // 座っている時
      onSitting();
    }
  }
}
```
そして立ち上がっている時とそうでない時のそれぞれの関数は以下になってます。

```sketch.js
// 立ち上がっているかどうかの判定のためのグローバル変数。連続で叱られるのを防ぐために使う。
let isStandingUp = false; 

// 座っている時に呼ばれる関数
function onSitting() {
  if (isStandingUp) {
    isStandingUp = false;
  }
}

// 立ち上がろうとしている時に呼ばれる関数
function onStandingUp() {
  if (!isStandingUp) {
    isStandingUp = true;
    // 「こら」と叱る。play()はp5.soundが提供するメソッド。
    kora.play();
  }
}
```

以上です！
最終的なコードは[GitHub](https://github.com/bunhojun/no-standing-app/blob/master/sketch.js)にまとめてあります。


##課題

なんとタブがバックグランドではml5は動かない模様です...!
アプリを起動しウィンドウをめちゃくちゃ小さくして画面の端っことかに置いておいてやってください…
他のウインドウに覆いかぶさるとうまく作動しません。

また、今回は鼻のy座標だけしか見てないので横に体をスライドさせて抜け出したら叱られません。
でも、無意識にそんな立ち上がり方はしないので今回は別によしとします。

##感想
機械学習やAIと聞くと「高度な数学が必要」「学習データを自分で確保しなければいけない」という先入観があるかもしれませんが、もうすでにそのインフラはすべての開発者に対し開けてきており、その敷居は以前に比べだいぶ下がってきたと言えます。
たとえば[TensorFlow.js](https://www.tensorflow.org/js)をつかえば、だいたいのフロントエンドエンジニアになじみの深いJavaScriptでも高度な機械学習モデルの作成や既存モデルの使用も可能です。
しかし今回使用したml5でも簡単なコードで沢山のことができるし、AIや機械学習は畑違いだと思っているフロントエンドの方にも手軽にこの分野に参入できるのはありがたいですね。


##参考
[The Coding Train] (https://www.youtube.com/watch?v=OIo-DIOkNVg&t=448s)
個人的によくプログラミング学習で利用するYouTubeチャンネルです。
講師がめちゃくちゃ気さくで見ているだけで幸せになれます。
基本英語ですが、たまに日本語字幕もついてます。


[ジェイソン・ステイサムに「死にたくないなら手を挙げろ！」と言われるのが夢だったので、自分でそんなアプリを作りました](https://qiita.com/twtjudy1128/items/4c29a98a035a6e8cbcb4)
めちゃくちゃ面白いml5に関するQiita記事です。
ml5を**p5.jsなし**で書こうとする時に役に立ちそう。
この記事では[Teachable Machine](https://teachablemachine.withgoogle.com/)を使い自分で撮影した機械学習モデルを利用しているようですね。