TensorFlow.js + MoveNetでジョジョ立ちの姿勢推定を行う

# はじめに
TesorFlow.jsとMoveNetを用いて姿勢推定を行う方法について解説します。
以下のツイート画像のようにブラウザ上で姿勢推定ができるようになります。

https://twitter.com/koheiyamamoto26/status/1456594828502843397?s=21

# 姿勢推定とは
姿勢推定とは画像や映像中の人物の関節(=キーポイント)を座標データで検出する技術です。
近年ではディープラーニングを用いた姿勢推定が行われています。


# TensolFlow.jsとは
- [TensorFlow.js](https://www.tensorflow.org/js/?hl=ja)はブラウザやNode.js上で機械学習モデルのトレーニングやデプロイを行うためのJavaScriptライブラリです。
- 学習済みモデルが公開されており、それらを利用して物体検出や姿勢推定を行うことができます。
- 今回はMoveNetという学習済みモデルを利用して姿勢推定を行います。

# MoveNetとは
- MoveNetはTensorFlow公式によって公開されている姿勢推定用のモデルです。
- 画像や動画を入力として身体の17のキーポイントを高速かつ正確に検出することができます。
- LightningとThunderの2種類が用意されています。
  - 前者はレイテンシーが重要なアプリケーションを対象としています。
  - 後者は高精度を必要とするアプリケーションを対象としています。
- 詳細な仕様はTensolFlowの公式ブログを参照してください。
- https://blog.tensorflow.org/2021/05/next-generation-pose-detection-with-movenet-and-tensorflowjs.html

# MoveNetの導入
今回はCDNを用いてMoveNetを導入します。

```html:html
<html>
  <head>
   <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-converter"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-backend-webgl"></script>
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/pose-detection"></script>
  </head>
</html>
```

# 姿勢推定の実行
姿勢推定の対象となるimg要素を用意します。

```html:html
<html>
  <body>
    <img id="img" src="./xxx.png" width="300px" />
  </body>
<html>
```


用意したimg要素に対して姿勢推定を実行します。

```javascript:javascript
document.addEventListener('DOMContentLoaded', async() => {
  
  //　検出器を生成
  const detector = await poseDetection.createDetector(poseDetection.SupportedModels.MoveNet)
 
  // img要素を取得する 
  const imageElement = document.getElementById('img')
  
  // 姿勢推定を実行する
  const poses = await detector.estimatePoses(imageElement)
  
  // 姿勢推定結果を出力する
  console.log(poses[0].keypoints)
  
})
```

コンソール上に以下のような姿勢推定結果が出力されました！

```javascript
[
  {
    "y": 107.0774033665657,
    "x": 170.3357219696045,
    "score": 0.672927975654602,
    "name": "nose"
  },
  {
    "y": 108.07683914899826,
    "x": 179.52157258987427,
    "score": 0.5579419136047363,
    "name": "left_eye"
  },
  {
    "y": 103.95387262105942,
    "x": 176.25893354415894,
    "score": 0.44960924983024597,
    "name": "right_eye"
  },
  {
    "y": 127.98979580402374,
    "x": 193.1210696697235,
    "score": 0.5600013136863708,
    "name": "left_ear"
  },
  {
    "y": 117.00804084539413,
    "x": 183.74462127685547,
    "score": 0.38861674070358276,
    "name": "right_ear"
  },
  {
    "y": 170.36895751953125,
    "x": 197.4759578704834,
    "score": 0.5543217658996582,
    "name": "left_shoulder"
  },
  {
    "y": 140.3295934200287,
    "x": 146.19784355163574,
    "score": 0.4909017086029053,
    "name": "right_shoulder"
  },
  {
    "y": 212.7191960811615,
    "x": 221.3673233985901,
    "score": 0.5364058613777161,
    "name": "left_elbow"
  },
  {
    "y": 128.0842810869217,
    "x": 94.87710893154144,
    "score": 0.7699795365333557,
    "name": "right_elbow"
  },
  {
    "y": 234.46808457374573,
    "x": 183.3907663822174,
    "score": 0.6135048866271973,
    "name": "left_wrist"
  },
  {
    "y": 140.4405564069748,
    "x": 127.41105258464813,
    "score": 0.39565128087997437,
    "name": "right_wrist"
  },
  {
    "y": 254.5135736465454,
    "x": 152.78272032737732,
    "score": 0.4956844747066498,
    "name": "left_hip"
  },
  {
    "y": 248.24934601783752,
    "x": 142.50272512435913,
    "score": 0.6491193175315857,
    "name": "right_hip"
  },
  {
    "y": 315.92750549316406,
    "x": 125.6029486656189,
    "score": 0.4037594497203827,
    "name": "left_knee"
  },
  {
    "y": 309.03390645980835,
    "x": 165.5895173549652,
    "score": 0.5492032766342163,
    "name": "right_knee"
  },
  {
    "y": 346.2807238101959,
    "x": 122.5919097661972,
    "score": 0.055652350187301636,
    "name": "left_ankle"
  },
  {
    "y": 343.7341272830963,
    "x": 181.74968361854553,
    "score": 0.08864666521549225,
    "name": "right_ankle"
  }
]
```
姿勢推定の結果はオブジェクトの配列として出力されます。
各オブジェクトは以下ような構成になります。

| プロパティ名 | 値              |
|:----------|----------------:|
| name      | 検出部位の名前     |
| x         | 検出部位のx座標    |
| y         | 検出部位のy座標    |
| score     | 検出結果の確からしさ |



出力された姿勢推定結果を用いれば、以下のようなことができます。
**・** 姿勢推定結果を表示する
**・** 画像中の人物に骨格線を重畳する
**・** 特定のポーズをとっているか判断する

今回は姿勢推定結果をテーブルに表示してみようと思います。

# 姿勢推定結果をテーブルに表示する
結果を表示するためのtable要素を用意します。

```html:html
    <table>
      <tr>
        <th>部位</th>
        <th>スコア(%)</th>
        <th>x座標</th>
        <th>y座標</th>
      </tr>
      <tr>
        <td>鼻</td>
        <td id="nose-score">-</td>
        <td id="nose-x">-</td>
        <td id="nose-y">-</td>
      </tr>
      <tr>
        <td>左目</td>
        <td id="left-eye-score">-</td>
        <td id="left-eye-x">-</td>
        <td id="left-eye-y">-</td>
      </tr>
      <tr>
        <td>右目</td>
        <td id="right-eye-score">-</td>
        <td id="right-eye-x">-</td>
        <td id="right-eye-y">-</td>
      </tr>
      <tr>
        <td>左耳</td>
        <td id="left-ear-score">-</td>
        <td id="left-ear-x">-</td>
        <td id="left-ear-y">-</td>
      </tr>
      <tr>
        <td>右耳</td>
        <td id="right-ear-score">-</td>
        <td id="right-ear-x">-</td>
        <td id="right-ear-y">-</td>
      </tr>
      <tr>
        <td>左肩</td>
        <td id="left-shoulder-score">-</td>
        <td id="left-shoulder-x">-</td>
        <td id="left-shoulder-y">-</td>
      </tr>
      <tr>
        <td>右肩</td>
        <td id="right-shoulder-score">-</td>
        <td id="right-shoulder-x">-</td>
        <td id="right-shoulder-y">-</td>
      </tr>
      <tr>
        <td>左肘</td>
        <td id="left-elbow-score">-</td>
        <td id="left-elbow-x">-</td>
        <td id="left-elbow-y">-</td>
      </tr>
      <tr>
        <td>右肘</td>
        <td id="right-elbow-score">-</td>
        <td id="right-elbow-x">-</td>
        <td id="right-elbow-y">-</td>
      </tr>
      <tr>
        <td>左手首</td>
        <td id="left-wrist-score">-</td>
        <td id="left-wrist-x">-</td>
        <td id="left-wrist-y">-</td>
      </tr>
      <tr>
        <td>右手首</td>
        <td id="right-wirst-score">-</td>
        <td id="right-wirst-x">-</td>
        <td id="right-wirst-y">-</td>
      </tr>
      <tr>
        <td>左腰</td>
        <td id="left-hip-score">-</td>
        <td id="left-hip-x">-</td>
        <td id="left-hip-y">-</td>
      </tr>
      <tr>
        <td>右腰</td>
        <td id="right-hip-score">-</td>
        <td id="right-hip-x">-</td>
        <td id="right-hip-y">-</td>
      </tr>
      <tr>
        <td>左膝</td>
        <td id="left-knee-score">-</td>
        <td id="left-knee-x">-</td>
        <td id="left-knee-y">-</td>
      </tr>
      <tr>
        <td>右膝</td>
        <td id="right-knee-score">-</td>
        <td id="right-knee-x">-</td>
        <td id="right-knee-y">-</td>
      </tr>
      <tr>
        <td>左足首</td>
        <td id="left-ankle-score">-</td>
        <td id="left-ankle-x">-</td>
        <td id="left-ankle-y">-</td>
      </tr>
      <tr>
        <td>右足首</td>
        <td id="right-ankle-score">-</td>
        <td id="right-ankle-x">-</td>
        <td id="right-ankle-y">-</td>
      </tr>
    </table>
```

テーブルのスタイルを定義します。

```css:css
img {
  float: left;
}

table {
  text-align: center;
  border-collapse: collapse;
}

table th, table td {
  border: solid 1px black;
}
```

テーブルに検出結果を描画するための処理を追加します。

```javascript:javascript
const keypointIds = ['nose', 'left-eye', 'right-eye', 'left-ear', 'right-ear',
                  'left-shoulder', 'right-shoulder', 'left-elbow', 'right-elbow',
                  'left-wrist', 'right-wirst', 'left-hip', 'right-hip',
                 'left-knee', 'right-knee', 'left-ankle', 'right-ankle']

keypointIds.forEach(function(item, index){
  keypoint = poses[0].keypoints[index]
  document.getElementById(item + '-score').innerHTML = Math.floor(keypoint.score * 100)
  document.getElementById(item + '-x').innerHTML = Math.floor(keypoint.x)
  document.getElementById(item + '-y').innerHTML = Math.floor(keypoint.y)
})

```

以下のように、姿勢推定結果がテーブルに表示されました！
<img width="600" alt="77e2c2b6-577d-d654-7404-b2b527ecd474.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1519716/77e2c2b6-577d-d654-7404-b2b527ecd474.png">

実装した内容については、CodePen上でも公開しています。
必要に応じてご参照ください！

https://codepen.io/kohei-yamamoto1012/pen/ZEXGEPY

# おわりに
最後までご覧いただきありがとうございました！

現在、姿勢推定を用いたジョジョ立ちの検定アプリを開発中です。
Twitterにて開発の進捗を発信していますので、もし良ければフォローお願いいたします！
https://twitter.com/koheiyamamoto26

# 参考サイト
- [TensorFlow.js](https://www.tensorflow.org/js/?hl=ja)
- [Next-Generation Pose Detection with MoveNet and TensorFlow.js
](https://blog.tensorflow.org/2021/05/next-generation-pose-detection-with-movenet-and-tensorflowjs.html)