はじめに
TesorFlow.jsとMoveNetを用いて姿勢推定を行う方法について解説します。
以下のツイート画像のようにブラウザ上で姿勢推定ができるようになります。
姿勢推定とは
姿勢推定とは画像や映像中の人物の関節(=キーポイント)を座標データで検出する技術です。
近年ではディープラーニングを用いた姿勢推定が行われています。
TensolFlow.jsとは
- TensorFlow.jsはブラウザや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>
<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>
<body>
<img id="img" src="./xxx.png" width="300px" />
</body>
<html>
用意したimg要素に対して姿勢推定を実行します。
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)
})
コンソール上に以下のような姿勢推定結果が出力されました!
[
{
"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要素を用意します。
<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>
テーブルのスタイルを定義します。
img {
float: left;
}
table {
text-align: center;
border-collapse: collapse;
}
table th, table td {
border: solid 1px black;
}
テーブルに検出結果を描画するための処理を追加します。
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)
})
実装した内容については、CodePen上でも公開しています。
必要に応じてご参照ください!
おわりに
最後までご覧いただきありがとうございました!
現在、姿勢推定を用いたジョジョ立ちの検定アプリを開発中です。
Twitterにて開発の進捗を発信していますので、もし良ければフォローお願いいたします!
https://twitter.com/koheiyamamoto26