はじめに
この記事は【目次】MMDモーショントレース自動化への挑戦 の一環です。
導入方法や他の技術解説等は、上記目次から各記事を参照してください。
ほぼ自分用作業メモ。
Github
元ネタ
このソースコードは、VMD-Lifting を改変しています。
3D関節データからVMDフォーマットへの変換処理は、VMD-Lifting がやっていること - Qiita を参照してください。
課題
生成したVMDファイルをMMDで読み込むと、やや前傾した姿勢になること。
アプローチ
-
VMD-Lifting
の出力結果と比較したところ、同じ動作でもモーションがかなり異なる事が判明
左:VMD-Lifting (未正規化)
— miu (@miu200521358) 2018年5月6日
処理時間:4時間程度
右:Openpose + 3d-pose-baseline (正規化済)
処理時間:15分程度(事前学習3日)
正規化しないとこんなに暴れるのか ^^;
VMD-Lifting を正規化して使うか、3d-pose-baseline の姿勢を修正するか…
どっちにしましょうかね。 pic.twitter.com/qEiEIYD1jP
-
VMD-Lifting
は前傾していない事から、miu200521358/3d-pose-baseline-vmd で生成された3D関節データが既に前傾しているものと想定 - 野球の投球動画などから、モーションを生成したところ、ローカルX軸(上半身等のX軸方向の傾き)ではく、グローバルX軸(MMD上で表示される座標X軸)方向に17度ほど傾いている事が判明
出かける直前にやってた、レンくんの投球モーション。
— miu (@miu200521358) 2018年5月6日
こうして見ると、身体の前傾というより、グローバルX軸に対して前傾してる(背面だと反り気味)なんだなー、ということが判明。
後、足がちょっと浮いてたり回転しちゃったりもするし、どうやって調整しようかねぇ pic.twitter.com/pclvmg2ZOO
解決策
1. 補正用クォータニオンの生成
グローバルX軸方向に指定角度(デフォルトで17度)傾けた補正用クォータニオンをまず生成する。
飛行機イラスト) GATAG
MMDにおいては、
- ピッチ = グローバルX軸
- ヨー = グローバルY軸
- ロール = グローバルZ軸
# 補正角度のクォータニオン
# 3次元の角度指定による回転から、クォータニオンを求める
correctqq = QQuaternion.fromEulerAngles(QVector3D(xangle, 0, 0))
QQuaternion QQuaternion::fromEulerAngles(float pitch, float yaw, float roll)
Creates a quaternion that corresponds to a rotation of roll degrees around the z axis, pitch degrees around the x axis, and yaw degrees around the y axis (in that order).
引用元)QQuaternion Class | Qt GUI 5.10
2. 角度の結合
# 上半身
direction = pos[8] - pos[7]
up = QVector3D.crossProduct(direction, (pos[14] - pos[11])).normalized()
upper_body_orientation = QQuaternion.fromDirection(direction, up)
initial = QQuaternion.fromDirection(QVector3D(0, 1, 0), QVector3D(0, 0, 1))
# 補正をかけて回転する
bf.rotation = correctqq * upper_body_orientation * initial.inverted()
upper_body_orientation
… 上半身角度クォータニオン
initial
… 初期状態からの捻り情報クォータニオン
- まず、グローバルX軸に補正角度分回転させる
- 補正角度分回した後、上半身の角度分回転させる
- 初期状態からの捻り角度分回転させる
順番を間違えると正しく補正されませんでした。
2. 角度の結合(末端の場合)
腕(←上半身)、膝(←下半身)等、親ボーンの回転が影響する場合、自分自身の回転にまず角度補正を行い、その後、親ボーンの回転を差し引く。
# 左腕
direction = pos[12] - pos[11]
up = QVector3D.crossProduct((pos[12] - pos[11]), (pos[13] - pos[12]))
orientation = QQuaternion.fromDirection(direction, up)
initial_orientation = QQuaternion.fromDirection(QVector3D(1.73, -1, 0), QVector3D(1, 1.73, 0))
rotation = correctqq * orientation * initial_orientation.inverted()
# 左腕ポーンの回転から親ボーンの回転を差し引いてbf.rotationに格納する。
# upper_body_rotation * bf.rotation = rotation なので、
bf.rotation = upper_body_rotation.inverted() * rotation
結果
- 前傾は概ね直った
- デフォルト値で概ね修正可能だが、動画によっては微調整した方がいいものもある
- シーンによっては後傾しちゃってる箇所も…
次回予定
とりあえずフレームの間引き。
同一円周上の三点の移動(関節の回転)が、一定角度差以内であれば、間引く…予定。
#これをクォータニオンでどう表現するのかが分かりませんw