MMDモーショントレース自動化でのセンター推定

# はじめに

この記事は[【目次】MMDモーショントレース自動化への挑戦](https://qiita.com/miu200521358/items/d826e9d70853728abc51) の一環です。
導入方法や他の技術解説等は、上記目次から各記事を参照してください。
ほぼ自分用作業メモ。

## Github

[3d-pose-baseline-vmd](https://github.com/miu200521358/3d-pose-baseline-vmd)
[VMD-3d-pose-baseline-multi](https://github.com/miu200521358/VMD-3d-pose-baseline-multi)

# 課題

3d-pose-baseline-vmd で取得した3Dデータにはセンター移動データが含まれていない

# アプローチ

- Openposeでは関節位置の移動が取れている事から、Openposeのデータを流用すればセンター移動ができるのでは、と推定
- 基準点があれば、移動量も計測できるのではないかと想定

# 解決策

力業です。スマートじゃない…
動画は、Babo様の[SNOBBISM](http://www.nicovideo.jp/watch/sm33220501)をお借りしております。利用許諾ありがとうございます！

## 事前準備

### Openposeの関節位置情報取得

Openposeで解析された2Dの関節位置情報をVMD生成処理時に使用する。
ただし、Openposeそのもののデータは暴れたり飛んだりしているので、`3d-pose-baseline-vmd` で3Dに変換する直前の正規化2Dデータ（`smoothed.txt`）を準備する。

### 直立フレームの選定

できるだけ正面向きで直立しているフレームを決める。
具体的には、Openposeで首と両足付け根が二等辺三角形になっているフレーム。

![直立図形.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/be7ffcc8-6e02-4232-d5c8-26a7e252e6a0.jpeg)

このフレームを、MMD上のセンターが(0,0,0)であると見なし、以下処理を行っていく。

## X軸移動

処理対象フレームの首、左足付け根、右足付け根のX軸の平均値を求める。
首だけにしなかったのは、Y軸方向に傾いてた場合を考慮するため。
おおざっぱに言えば、へその位置を求めるようなもんです。

![X軸移動2.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/a7c68d78-f5c2-4f51-c05c-db7245473a91.jpeg)

```
# 首・左足・右足の中心部分をX軸移動
x_avg = ((smoothed_2d[n][0].x() + smoothed_2d[n][1].x() + smoothed_2d[n][2].x()) / 3) - smoothed_2d[upright_idx][0].x()
bone_frame_dic["センター"][n].position.setX(x_avg * upright_xy_scale)
```

カメラからの距離で移動量が変わるので、その辺は外部パラメータで今のところ吸収しています。

## Y軸移動

処理対象フレームの左足付け根、右足付け根のY軸の平均値を求める。
片足だけにしなかったのは、Z軸方向に傾いてた場合を考慮するため。

```
# 左足と右足の位置の平均
leg_avg = abs((smoothed_2d[n][1].y() + smoothed_2d[n][2].y()) / 2)
       
# 足の上下差
leg_diff = upright_leg_avg - leg_avg

# Y軸移動
if is_groove:
    # グルーブがある場合には、そっちに割り当てる
    bone_frame_dic["グルーブ"][n].position.setY(leg_diff * upright_xy_scale)
else:
    bone_frame_dic["センター"][n].position.setY(leg_diff * upright_xy_scale)
```

![Y軸移動.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/1386c3d0-3bc2-62a7-3f36-e3bae05b884a.jpeg)

これも外部パラメータで移動量調整できるようにしています。
準標準モデルをトレース対象として読み込んだ場合には、グルーブにY軸移動量を割り当てます。
＃トレーサーとしての好みです

## Z軸移動

※暫定処理です。今後調整の可能性大。

ざっくり言うと、首と両足付け根の二次元面積を、2Dと3Dの両方で算出して、その縮尺の割合で求めてます。

### 2D直立フレームの二次元面積

図は「直立フレームの選定」参照。

```
# 直立フレームの三角形面積
smoothed_upright_area = calc_triangle_area(smoothed_2d[upright_idx][0], smoothed_2d[upright_idx][1], smoothed_2d[upright_idx][2])
```

### 3D直立フレームの投影二次元面積

![Z軸移動1.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/9c7bc492-f246-77b1-c022-8bcad735f09f.jpeg)

```
# 3Dでの首・左足・右足の投影三角形
pos_upright_area = calc_triangle_area(positions_multi[upright_idx][8], positions_multi[upright_idx][1], positions_multi[upright_idx][4])
```

### 2D処理フレームの二次元面積

![Z軸移動2.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/5ec0cfb8-c197-9579-8ce4-e037ce91a860.jpeg)

```
# 2Dでの首・左足・右足の投影三角形
smoothed_now_area = calc_triangle_area(smoothed_2d[n][0], smoothed_2d[n][1], smoothed_2d[n][2])
```

### 3D処理フレームの投影二次元面積

![Z軸移動3.jpg](https://qiita-image-store.s3.amazonaws.com/0/254327/766c5672-298c-02ae-7c2c-c5aec774c0f0.jpeg)

```
# 3Dでの首・左足・右足の投影三角形
pos_now_area = calc_triangle_area(positions_multi[n][8], positions_multi[n][1], positions_multi[n][4])
```

### 2D面積の縮尺

カメラから見て、遠ざかるほど、縮尺は小さくなる

```
# 2Dでの現在の縮尺
smoothed_scale = smoothed_now_area / smoothed_upright_area
```

### 3D面積の縮尺

傾きが大きいほど、縮尺は小さくなる

```
# 3Dでの現在の縮尺
pos_scale = pos_now_area / pos_upright_area
```

### Z軸の算出

`3D面積の縮尺 * (1 - 2D面積の縮尺)` で、傾きを考慮したZ軸距離を算出する

１． `1 - 2D面積の縮尺` は、MMDセンター基準のZ軸位置。
　→　マイナス値であれば近づき、プラス値であれば遠ざかる
２．`3D面積の縮尺`で、傾き分の補正を行う
　→　傾きが大きい（3D縮尺が小さい）ほど、Z軸の移動量は小さくなる
　　　要は、真横向きとか、傾きが大きいと、2D面積が小さくてもあんまり奥には配置しない、って事です。

```
# Z軸移動位置の算出
now_z_scale = pos_scale * (1 - smoothed_scale)
# Z軸の移動補正
bone_frame_dic["センター"][n].position.setZ(now_z_scale * center_z_scale)
```

Z軸補正は、XY移動量と連動しないパターンがあったので、別途外部パラメータで移動量調整できるようにしています。


# 課題

 - 腰から上半身を前向きに折るような姿勢の場合、Z軸移動で、希望より前に移動しがち。
 - XYおよびZ軸の移動量が外部パラメータ依存。自動判定できたらいいなぁ…

# 次回予定

最大の難関、足IK化。いつになるやら…