ポージングに対応した再生位置からMVを再生するシステムを作ってみた[Pose Mover]

#作ったもの
Herokuにデプロイしてあります。
https://pose-mover.herokuapp.com

[Posenet](https://github.com/tensorflow/tfjs-models/tree/master/posenet)で骨格検出をし、検出したポージングにに対応した再生位置からMVを再生するシステムです。

「ダンスの練習などに使えます💃(いちいちPC操作して踊ってみた動画をシークさせるの面倒でしょ？)」

とそれっぽい使い方を考えてみました。

しかし、ダンスの練習に使うためにはまだまだブラッシュアップが必要です。
現段階だとオレトク作品止まりなのかもしれませんね。
(ユーザに体験してもらうと結構好感触)

この作品は[ヒーローズ・リーグ2019](https://hl2019.we-are-ma.jp)のMAリーグと学生リーグとオレトクリーグにて決勝まで残りました🙌


<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">ポーズを認識して、それに紐づく楽曲を再生！<br>（楽曲部分は省略してます）<a href="https://twitter.com/hashtag/%E3%83%92%E3%83%BC%E3%83%AD%E3%83%BC%E3%82%BA%E3%83%AA%E3%83%BC%E3%82%B0?src=hash&amp;ref_src=twsrc%5Etfw">#ヒーローズリーグ</a> <a href="https://t.co/Xf4CYCi1xL">pic.twitter.com/Xf4CYCi1xL</a></p>&mdash; ヒーローズ・リーグ＠授賞式は12/8(日) (@mashupaward) <a href="https://twitter.com/mashupaward/status/1203196560151961601?ref_src=twsrc%5Etfw">December 7, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/%E3%83%92%E3%83%BC%E3%83%AD%E3%83%BC%E3%82%BA%E3%83%AA%E3%83%BC%E3%82%B0?src=hash&amp;ref_src=twsrc%5Etfw">#ヒーローズリーグ</a>  学生リーグ2019決勝審査会 Pose Mover Just pose it デモ動画 <br>ポージング認識してポージングに対応した動画再生できてた。 <a href="https://t.co/rHr1RJEWeY">pic.twitter.com/rHr1RJEWeY</a></p>&mdash; t_furu ♨ #AKIあきラジオ #オオエン (@t_furu) <a href="https://twitter.com/t_furu/status/1195534111139454977?ref_src=twsrc%5Etfw">November 16, 2019</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>


作品の紹介動画は[こちら](https://www.youtube.com/watch?v=z2hGG3WXAW0)

#ソース
ソースはGitHubで公開しております。
https://github.com/daiking1756/PoseMoverWeb

#きっかけ
- レッドハッカソンHIROSHIMA2019にて開発
- 某広島出身3人組テクノポップユニットが大好きだったこと
- ハッカソン当日が彼女たちの結成20周年の記念日だったこと
- 情報工学を専攻しているくせに、彼女たちに関する作品を作ったことが無かったこと
- エモみが高まったこと

つまり、**エモみ駆動開発**です

#Posenet
骨格検出の機械学習モデル。
計17箇所の骨格検出ができます。(口、目、耳、肩、肘、手首、腰、膝、足首)

骨格検出だと[OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose)というライブラリが有名です。
しかし、PosenetにはWebブラウザで動作するくらい軽量という特徴があります。
ブラウザ上で動作するので、作ったシステムをWebアプリケーションとして公開することもできます。

次の記事でOpenPoseとPosenetを分かりやすく比較されています。
[「OpenPose はどこまでの画像に耐えられるのか ~ Pose Estimation の紹介~」](https://qiita.com/baba5246/items/7ed50689c67c0ad3882d)

今回はTensorFlow.jsの[公式のサンプル](https://github.com/tensorflow/tfjs-models/tree/master/posenet)を参考にコードを書きました。

##Posenet VS OpenPose
見る人によって意見が別れるかもしれませんが、個人的にはOpenPoseの方が骨格検出が正確な気がします。
まあ、そこまで大きな差はないので、今回はPosenetを使いました。
![voice1_posenet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322403/bfbfa7d3-40e8-0e40-8443-1747745ff3fd.png)
<img width="708" alt="スクリーンショット 2019-12-12 14.22.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322403/e369b00d-11f7-3ad4-2b6e-1ed8541dd831.png">
![voice2_posenet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322403/b4e25372-c76a-e1df-fb7d-3f7ae8684d5a.png)
<img width="721" alt="スクリーンショット 2019-12-12 14.24.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322403/e41ff176-a0a9-b9b9-5d46-7ce41418a840.png">


#全体のロジック
1.正解ポージング画像の骨格情報を取得
2.カメラからユーザが行ったポージングの骨格情報をリアルタイムに取得
3.ユーザの骨格情報と正解ポージング画像の骨格情報を比較
4.誤差が一定値以下ならポージングとして検出し、YouTubeのMVを対応した再生位置から再生

##正解ポージング画像の骨格情報を取得
正解ポージング画像を登録する画面です。
登録する情報は3つあります

- 正解ポージング画像
- 左から何人目のポーズを正解の骨格情報とするか
- YouTubeのMVのURL

###正解ポージング画像

正解ポージング画像は現段階では直接HTML内のimgタグのsrc属性に設定しています。
(画像ファイルをユーザが追加できるようにするべきですね。)
画像が読み込まれるとPosenetの骨格検出が自動的に動作し、水色のスケルトンが描画されます。

###左から何人目のポーズを正解の骨格情報とするか

そのスケルトンを見ながら、ユーザは左から何人目のポーズを正解の骨格情報として登録するかを選択します。
(画像では左から1番目のポーズが最も正確に骨格検出できていたため、「1」と入力)

###YouTubeのMVのURL
正解ポージング画像のポーズに対応するYouTubeのMVのURLを入力します。
注意点として、パラメータに再生開始位置を表す`t`を含めたURLを入力するようにします。
(画像では`t=91`なので、1分31秒から動画が再生されます)
YouTubeの動画を右クリックして「現時点の動画のURLをコピー」を押すと良いですね。

![スクリーンショット 2019-12-12 14.34.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/322403/7a3340b9-b316-738f-4332-17b547a814a9.png)

###データの保存形式
正解データはJSON形式で保存されています

-正解ポージング画像の骨格情報
-左から

##カメラからユーザが行ったポージングの骨格情報をリアルタイムに取得
[公式のサンプル](https://github.com/tensorflow/tfjs-models/tree/master/posenet)のままです。

##ユーザの骨格情報と正解ポージング画像の骨格情報を比較
17箇所の骨格情報のうち、ポージングに関係しそうな箇所のみを比較していきます。
パッと思いついたのが以下の4つの線分です。

- 肩から肘
- 肘から手首
- 腰から膝
- 膝から足首

ということで、左右合わせて合計8本の線分の角度の誤差平均を比較することにしました。
計算しているコードは以下に載せておきます。

以下の記事を参考にしました。
[2点間の距離と角度と座標の求め方](https://qiita.com/Hoshi_7/items/d04936883ff3eb1eed2d?fbclid=IwAR2VUt5po7idZPSwNnMq7KfR76_8qttrDZtGK-42Y8PCKWVc4WTSMr_d2Yw)

```javascript:calc_pose_angle.js
// 正解ポーズとユーザポーズの、角度の誤差を計算
function calcAngleError(correct_pose, user_pose){
    let error = 0;

    // Shoulder - Elbow
    error += calcKeypointAngleError(correct_pose, user_pose, 5, 7);
    error += calcKeypointAngleError(correct_pose, user_pose, 6, 8);

    // Elbow - Wrist
    error += calcKeypointAngleError(correct_pose, user_pose, 7, 9);
    error += calcKeypointAngleError(correct_pose, user_pose, 8, 10);

    // // Hip - Knee
    error += calcKeypointAngleError(correct_pose, user_pose, 11, 13);
    error += calcKeypointAngleError(correct_pose, user_pose, 12, 14);

    // // Knee - Ankle
    error += calcKeypointAngleError(correct_pose, user_pose, 13, 15);
    error += calcKeypointAngleError(correct_pose, user_pose, 14, 16);

    error /= 8;

    return error
}

// 正解ポーズとユーザポーズの、ある2つのkeypoint間の角度の誤差を計算
function calcKeypointAngleError(correct_pose, user_pose, num1, num2){
    let error = Math.abs(calcKeypointsAngle(correct_pose.keypoints, num1, num2) - calcKeypointsAngle(user_pose.keypoints, num1, num2))
    if(error <= 180) {
        return error
    } else {
        return 360 - error
    }
}

// keypoint[num1]とkeypoint[num2]の角度を計算
function calcKeypointsAngle(keypoints, num1, num2){
    return calcPositionAngle(keypoints[num1].position, keypoints[num2].position);
}

// position1とposition2を結ぶ線分の角度を計算
function calcPositionAngle(position1, position2){
    return calcAngleDegrees(position1.x, position1.y, position2.x, position2.y);
}

// 2点間の角度を計算
function calcAngleDegrees(x1, y1, x2, y2) {
    return Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
}

```
##誤差が一定値以下ならポージングとして検出し、MV再生
[YouTube Player API](https://developers.google.com/youtube/iframe_api_reference?hl=ja)というJSでiframeで埋め込んだYouTubeプレイヤーを制御できるAPIがありました。
これを使ってMVを再生しています。

- 検出したポーズが再生中の曲のポーズならば、シーク
- 検出したポーズが再生中の曲のポーズでないならば、MVを読み込み直し、対応する再生位置から再生

というロジックです。

#まとめ
Posenetは手軽に骨格検出ができるし、ブラウザで動くの最高すぎる。
[BodyPix](https://github.com/tensorflow/tfjs-models/tree/master/body-pix)も使ってみたい。

***自分の好きなこと駆動で開発するのは楽しい！***

#参考
- [Posenet](https://github.com/tensorflow/tfjs-models/tree/master/posenet)
- [OpenPose](https://github.com/CMU-Perceptual-Computing-Lab/openpose)
- [OpenPose はどこまでの画像に耐えられるのか ~ Pose Estimation の紹介~](https://qiita.com/baba5246/items/7ed50689c67c0ad3882d)
- [Posenetの公式サンプル](https://github.com/tensorflow/tfjs-models/tree/master/posenet)
- [2点間の距離と角度と座標の求め方](https://qiita.com/Hoshi_7/items/d04936883ff3eb1eed2d?fbclid=IwAR2VUt5po7idZPSwNnMq7KfR76_8qttrDZtGK-42Y8PCKWVc4WTSMr_d2Yw)
- [YouTube Player API](https://developers.google.com/youtube/iframe_api_reference?hl=ja)
- [BodyPix](https://github.com/tensorflow/tfjs-models/tree/master/body-pix)