PyQt5でクォータニオンの回転


はじめに

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

Github

VMD-3d-pose-baseline-multi

元ネタ

このソースコードは、VMD-Lifting を改変しています。
3D関節データからVMDフォーマットへの変換処理は、VMD-Lifting がやっていること - Qiita を参照してください。

課題

生成したVMDファイルをMMDで読み込むと、やや前傾した姿勢になること。

アプローチ

  • VMD-Lifting の出力結果と比較したところ、同じ動作でもモーションがかなり異なる事が判明
  • VMD-Lifting は前傾していない事から、miu200521358/3d-pose-baseline-vmd で生成された3D関節データが既に前傾しているものと想定
  • 野球の投球動画などから、モーションを生成したところ、ローカルX軸(上半身等のX軸方向の傾き)ではく、グローバルX軸(MMD上で表示される座標X軸)方向に17度ほど傾いている事が判明

解決策

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 … 初期状態からの捻り情報クォータニオン

  1. まず、グローバルX軸に補正角度分回転させる
  2. 補正角度分回した後、上半身の角度分回転させる
  3. 初期状態からの捻り角度分回転させる

順番を間違えると正しく補正されませんでした。

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