SkyWayを使ってリアルタイム物体検出つきビデオチャットを作る

### 概要
ビデオチャットのSkyWayに物体検出をいれて、リアルタイムで物体検出しながら
ビデオチャットをする謎のビデオチャットです。
### できたもの
https://yolo-videochat.ga
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/ProtoOut?src=hash&amp;ref_src=twsrc%5Etfw">#ProtoOut</a> <a href="https://t.co/bjZZPddXEY">pic.twitter.com/bjZZPddXEY</a></p>&mdash; 3yaka (@3yaka4) <a href="https://twitter.com/3yaka4/status/1271055922652045312?ref_src=twsrc%5Etfw">June 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


### 概要
SkyWayで作ったビデオチャットに機械学習のTensorFlow.jsを優しーく包んでくれたml5.jsのYOLOを使って物体検出をさせ、PoseNetを使ってプライバシーを配慮した目線をかくすものをつけました。
<img width="322" alt="スクリーンショット 2020-06-11 22.23.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282291/5bb8dd7e-dc15-d921-4232-7fb2cf983f26.png">
人物に四角がついてその上にPersonと出て、左目から右目にかけて線が入ります。
### 1. SkyWayを使って webRTC
[Javascript SDK \| API Reference \| SkyWay\(アプリやWebサービスに、ビデオ・音声通話をかんたんに導入・実装できるSDK\)](https://webrtc.ecl.ntt.com/api-reference/javascript.html#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB)
こちらを使っていきます
### 2. PoseNetを使って姿勢推定
[Webブラウザでリアルタイムに人間の姿勢推定を可能にする機械学習モデル「PoseNet」](https://shiropen.com/seamless/google-tensorflow-posenet)
PoseNetとは リアルタイムに人間の姿勢推定を可能にする機械学習モデルです。
これを使ってプライバシー保護っぽく左目から右目にかけてlineを引きます。

### 3.YOLOを使って物体検出
[YOLO](https://pjreddie.com/darknet/yolo/)（You Only Look Once）
YOLOとは、ウェブカメラやスマホカメラから取得した映像をYOLOというアルゴリズムを使用し物体検出をしてくれます。
![nyanko.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/282291/c691f0bd-14fd-e6e8-928c-deba0e833556.png)

### Netlify freenomで公開して他の人とチャットする
SkyWayを他の人と、使うのにHTTPSが必須です！
今回はindex.htmlとmain.jsと画像だけの静的ファイルなのでgitもつかわずNetlifyに直接アップロードして公開します。

### コード
```html
<!DOCTYPE html>
<html lang="ja">
  <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>myVideoChat</title>
//背景とボタン、フォームの装飾
    <style>
html{ background: url(haikei.jpg) center center no-repeat; background-size: cover; height: 100vh; } body{ width:700px; margin:auto; } input[type=text] , textarea { border-radius: 4px; border: 1px solid #999; background-color: #fff; padding: 10px; } input[type=text] { width: 14m; } input[type=text].input_err { border: 1px solid #c44; background-color: #fdd; } button{ border: none; width: 8em; padding: 10px; border-radius: 5px; background-color: #00BCD4; color: #fff; font-weight: bold; appearance: none; -webkit-appearance: none; -moz-appearance: none; cursor: pointer; margin: 0 auto; } p{ color:#fff; }
    </style>
  </head>

  <body>
    <div id="hako">
　　　//YOLOが読み込まれたら表示が変わる
     <p id="status">モデルを読み込むまでまってね...</p>
    </div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
    <script src="https://unpkg.com/ml5@0.4.3/dist/ml5.min.js"></script>
    <script src="https://cdn.webrtc.ecl.ntt.com/skyway-latest.js"></script>
    <script src="main.js"></script>
  </body>
</html>
```
```javascript
let capture;
let theirVideo;
let poses = [];
let theirPoses = [];
let yolo;
let status;
let objects = [];
let theirObjects = [];
let yolo_thier;

function setup() {
  //canvas作成
  createCanvas(640, 240);
  //自分用カメラ設定と姿勢検出
  capture = createCapture({
    video: {
      width: 640,
      height: 480
    },
    audio: false
  });
  // YOLOオブジェクトを作成する
  yolo = ml5.YOLO(capture, startDetecting);
  status = select('#status');
  //キャンバスで描くので非表示
  capture.hide();
  ml5.poseNet(capture).on("pose", (results) => {
    poses = results;
  });

  // skywayのインスタンスを作成
  let peer = new Peer({
    key: "***",
  });
  // skywayでドメインを許可していれば実行される
  peer.on("open", () => {
    console.log("open! id=" + peer.id);
    createP("Your id: " + peer.id);
  });

  // id入力タグの生成
  let idInput = createInput("");

  // 送信ボタンの生成
  createButton("Call").mousePressed(() => {
    // ボタンが押されたら
    const callId = idInput.value(); //id入力欄の値を取得
    console.log("call! id=" + peer.id);
    const call = peer.call(callId, capture.elt.srcObject); //id先を呼び出し
    addVideo(call);
  });

  // // 相手から呼び出された実行される
  peer.on("call", (call) => {
    console.log("be called!");
    call.answer(capture.elt.srcObject); //呼び出し相手に対して返す
    addVideo(call);
  });

  // 相手の映像を追加処理
  function addVideo(call) {
    call.on("stream", (theirStream) => {
      console.log("stream!");
      //相手のビデオを作成
      theirVideo = createVideo();
      theirVideo.elt.autoplay = true;
      theirVideo.elt.srcObject = theirStream;
      theirVideo.hide(); //キャンバスで描くので非表示

      // 相手のビデオから姿勢検出
      ml5.poseNet(theirVideo).on("pose", (results) => {
        theirPoses = results;
      });
      yolo_thier = ml5.YOLO(theirVideo, startDetecting);
    });
  }
}

function draw() {
  if (capture) image(capture, 0, 0, 320, 240);
  if (theirVideo) image(theirVideo, 320, 0, 320, 240);
  //物体検出
  for (let i = 0; i < objects.length; i++) {
    noStroke();
    fill(177, 30, 91);
    text(objects[i].label, objects[i].x * 320, objects[i].y * 240 - 5);
    noFill();
    strokeWeight(4);
    stroke(255, 255, 0);
    rect(objects[i].x * 320, objects[i].y * 240, objects[i].w * 320, objects[i].h * 240);
  }
  //相手の物体検出
  for (let i = 0; i < theirObjects.length; i++) {
    noStroke();
    fill(177, 0, 91);
    text(theirObjects[i].label, theirObjects[i].x * 320 + 320, theirObjects[i].y * 240 - 5);

    noFill();
    strokeWeight(4);
    stroke(255, 0, 0);
    rect(theirObjects[i].x * 320 + 320, theirObjects[i].y * 240, theirObjects[i].w * 320, theirObjects[i].h * 240);
  }

  //自分の目の位置を取得
  let myLeyePos = getPartsPosition(poses, "leftEye");
  let myReyePos = getPartsPosition(poses, "rightEye");
  //相手の目の位置を取得
  let thLeyePos = getPartsPosition(theirPoses, "leftEye");
  let thReyePos = getPartsPosition(theirPoses, "rightEye");

  //線で結ぶ
  //半分の大きさにしたので、座標も半分に
  strokeWeight(10); //線の太さ
  stroke(0, 255, 0); //線の色 R,G,B
  line(
    myLeyePos.x / 2,
    myLeyePos.y / 2,
    myReyePos.x / 2,
    myReyePos.y / 2
  );
  stroke(255, 255, 0); //線の色 R,G,B
  line(
    thLeyePos.x / 2 + 320,
    thLeyePos.y / 2,
    thReyePos.x / 2 + 320,
    thReyePos.y / 2
  );
}

//指定された名前の部位の座標を取得できる
function getPartsPosition(poses, partsName) {
  if (poses[0])
    for (let i = 0; i < poses[0].pose.keypoints.length; i++)
      if (poses[0].pose.keypoints[i].part == partsName)
        return poses[0].pose.keypoints[i].position;

  return {
    x: 0,
    y: 0
  };
}

function startDetecting() {
  status.html('モデルを読み込んだ');
  detect();
  theirDetect();
}
// ビデオからのイメージを物体検出する
function detect() {
  yolo.detect(function(err, results) {
    // 結果を配列objectsに割り当てる
    objects = results;
    // 連続して検出
    detect();
  });
}
function theirDetect() {
  yolo_thier.detect(function(err, results) {
    // 結果を配列theirObjectsに割り当てる
    theirObjects = results;
    // 連続して検出
    theirDetect();
  });
}

```


### できなかったこと
opencvからYOLOを使うと、cat：６０％　みたいな％も出せるようで、JSでやりたかった（できるのかな？）
目を線で引くのではなくて目の画像にしたかったのですが、うまく目についてこれず、目がたくさん下に溜まっていって怖かったので実装できず。。。
いつか頑張る。
### 参考サイト
+ [8\_2：YOLOとp5\.jsによるリアルタイム物体検出 ml5\.js JavaScript – HIM\.CO ヒム・カンパニー](https://himco.jp/2019/01/21/8_2%EF%BC%9Ayolo%E3%81%A8p5-js%E3%81%AB%E3%82%88%E3%82%8B%E3%83%AA%E3%82%A2%E3%83%AB%E3%82%BF%E3%82%A4%E3%83%A0%E7%89%A9%E4%BD%93%E6%A4%9C%E5%87%BA/)
+ [機械学習による推論をJavaScript上で簡単に実装できるml5\.js \- Qiita](https://qiita.com/dojyorin/items/650ef059383cfcbe7532)
+ [ml5\.jsを使ったYoloのサンプルページ作ってみた\. \| Urusu Lambda Web](http://urusulambda.com/2019/04/29/ml5-js%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%9Fyolo%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB%E3%83%9A%E3%83%BC%E3%82%B8%E4%BD%9C%E3%81%A3%E3%81%A6%E3%81%BF%E3%81%9F/)

### 感想
一人デバックの時、二つビデオチャットを立ち上げるとパソコンが壊れそうなくらいファンがすごく回るので冷や冷やする。
色々なものを組み合わせるのは本当に難しい。