見出し画像

おうち3D配信を支えるトラッキングシステムについて

こんにちは。
カバー株式会社CTO室エンジニアのIです。

今回はタレントさんが普段の配信で使用しているホロライブアプリのトラッキングシステムについて紹介します。
弊社のタレントさんが自宅から3Dモデルを使った配信を行う、通称「おうち3D」では複数のトラッキング方式が複合的に利用されていることにお気づきでしょうか?
カバーのスタジオでは本格的なモーションキャプチャー設備を用いて多自由度かつ精度の高い3D配信が可能ですが、おうち3Dでは自宅でも簡単かつ表情豊かなトラッキングが利用できることを目指しています。

※おうち3D配信参考リンク
【おうち3D凸待ち】mocopiに敗北したぺこマリ【ホロライブ/宝鐘マリン・兎田ぺこら】

トラッキング入力の種類

おうち3Dでは複数のトラッキング入力からさまざまなデータを受け取り、合成した後、タレントの3Dモデルの全身に適用しています。
トラッキング入力の種類には以下のようなものがあります。

iPhone(ARKit, ToF AR)

iPhoneのDepthカメラを利用した高精度な表情BlendShapeや、ソニーセミコンダクタソリューションズ株式会社によるToF ARソフトウェア開発キット(SDK)を利用したハンドトラッキングが可能です。

WASD歩行モーション

FPSゲーム等で利用されるキーボード入力による歩行移動モーションを生成しています。自室に居ながら広い3D空間を動き回る際に最適です。

選択式アニメーション

立つ、座る、ジャンプする、手を振る、などのいくつかのモーションを選択して再生できます。簡易的なジェスチャーコミュニケーション等ができます。

ボディトラッキング系デバイス(Mocopi, Rokoko, etc…)

市販のトラッキングデバイスからの全身モーションを受け取れます。デバイスのセットアップ作業が必要ですが、スタジオ3D配信に近い自由度の全身配信ができます。

これらのトラッキング入力は大まかには以下のように利用されています。

トラッキング入力の規格化

Unityにおいて人型モデルの姿勢計算をする際、絶対座標と相対座標で混乱したり、関節角度と手先座標をどう両立させるか悩んだことはないでしょうか?
本来、Unityにおける座標表現クラスである「Transform」のように親子関係をもつ座標系の取り扱いは、ロボット工学における運動学(Kinematics)の領分なのですが、Unity特有の事情を考慮して、おうち3Dでは独自のデータ表現で運用することになりました。

ロボット工学における人型モデル

まずは一般的なロボット工学における人型モデルを説明します。
ロボット工学において人型モデルは「浮遊ベースリンクの剛体リンクモデル」として扱います。
「剛体リンクモデル」とは剛体と剛体が、可動関節によって接続されているモデルであり、人間に適用すると、骨やその周辺の肉をまとめて剛体リンクとみなし、関節はそのまま関節とみなします。
「浮遊ベースリンク」とは、ワールド座標系に対して配置された人型モデルの最も親のリンク(多くの場合腰リンク)を、並進・回転の6自由度(DOF)が自由に可動する浮遊ベースリンクとみなします。
※6DOFと表記していますが、必ずしも6変数というわけではありません。Quaternionを用いた場合は7変数になります

浮遊ベースリンク姿勢(6 DOF)と全身関節角(N DOF)をまとめた(6+N)DOFのベクトルが、人型モデルのある瞬間の状態を一意に定める情報となり、難しく表現すると「一般化座標」と言います。
簡単に言うと、「ある瞬間の姿勢を再現したいから教えて!」と言われたら、このワールド座標系に対するベースリンク姿勢(6 DOF)+全身関節角(N DOF)を教えてあげればよいのです。

Unityにおける人型モデル

Unityでは「Mechanim」といわれる3Dキャラクターモデルを制御する機能が用意されており、Humanoidの姿勢を一意に記述する情報としては「HumanPose」というものがあります。

HumanPoseは以下のように読み書きができ、

var animator = GetComponent<Animator>();    
var handler = new HumanPoseHandler(animator.avatar, animator.transform);
var humanpose = new HumanPose();
handler.GetHumanPose(ref humanPose);
// ここでhumanPose.musleをいじったりする
handler.SetHumanPose(ref humanPose);

HumanPose構造体には以下の内容が格納されています。

humanPose.bodyPosition
humanPose.bodyRotation
humanPose.muscle

一見、ロボット工学における「ベースリンク+全身関節角」のように見えますが、実は若干異なります。
・bodyPosition
bodyPositionはHumanoidの重心が格納されておりベースリンク並進位置ではありません。
・bodyRotation
bodyRotationは胴体のいくつかの点を使って人間の向きをそれらしく表現したもので、ベースリンク回転姿勢ではありません。
・muscle
muscleは関節角度ではなく、モデルごとに設定された関節可動域のmin~maxを-1~1で表現しています。
こうした姿勢の表現方法を採っている理由は、四肢の長さや可動域の異なる複数の人型モデル間でモーションの再利用(Retargeting)ができるように、あえて抽象的な中間表現にしているとのことですが、正確な運動学計算を行いたいときには困ってしまいます。
また、Unityの人型モデルにはワールドとの間にベースリンクに相当すると思われるTransformが2つ存在して混乱しやすいです。

おうち3Dにおける人型モデルとトラッキング入力の規格化

そこで、おうち3DにおいてはUnityのHumanoidモデルを考慮して、以下のようなデータをまとめて人型モデルの一般化座標とみなし、そのまま順運動学(Forward Kinematics: FK)の入力とします。
・Rootの目標姿勢
広い空間を歩き回って移動したときの「現在の立ち位置原点」というニュアンスで使います。ワールド座標系に対する相対座標(=絶対座標)であることに注意です。

var root = animator.transform;

・Hipsの目標姿勢
本来の意味でのベースリンク相当として扱います。座標データは常にRootに対する相対座標で読み書きすることに注意です。

var hipsFromWorld= animator.GetBoneTransform(HumanBodyBones.Hips);
var hipsFromRoot = CalcRelativeTransform(root, hipsFromWorld); // 後述の相対座標計算

・musclesの目標値
関節角の代わりに使います。

加えて、逆運動学(Inverse Kinematics: IK)の入力として、以下のデータも受け付けています。

・各リンクの目標姿勢
手先・足先目標姿勢のような任意のリンクの6 DOF姿勢。必ずしもワールドやRoot相対で計測されるとも限らないため、任意のリンクに対する相対姿勢として、親リンクの情報とともに受け取ります。

var leftHandFromWorld= animator.GetBoneTransform(HumanBodyBones.LeftHand);
var leftHandFromHips= CalcRelativeTransform(hipsFromWorld, leftHandFromWorld); // 後述の相対座標計算

(補足)Unityにおける相対座標計算

ロボット工学の人型モデルもUnityの人型モデルもツリー構造のように親子関係を持った座標系が連なっており、これをSerial Kinematic Chainといいます。
Unityにおいてはワールドに対する絶対座標を表すTransform.position / Transform.rotationに対して、親Transformからの相対座標を表すTransform.localPosition / Transform.localRotationが存在します。
しかしこれらは一つ上の親Transformとの相対座標しか取り扱えず、Unityには任意のTransform間の相対座標を読み書きできる機能は用意されていないようなので、自力で計算して代入する必要があります。
毎回Quaternionをどちらから掛けるか迷ってバグを生むことになるので、関数として用意しておくとよいです。
※Transformクラスは任意に生成できないのでPose構造体を使用しています。

// ある座標系(from)からある座標系(to)への相対姿勢を返す
public Pose CalcRelativeTransform(Transform from, Transform to)
{
    return new Pose(
        Quaternion.Inverse(from.rotation) * (to.position - from.position),
        Quaternion.Inverse(from.rotation) * to.rotation
        );
}
  
// ある座標系(from)をその座標系から見た相対座標変換(relativeTransform)で移動後の姿勢を返す
public Pose ApplyRelativeTransform(Transform from, Pose relativeTransform)
{
    return new Pose(
        from.position + from.rotation * relativeTransform.position,
        from.rotation * relativeTransform.rotation
        );
}


トラッキング入力の統合

トラッキング入力から取得したデータを体の関節やリンクごとに取捨選択したり、相対座標計算を解決して、人体1体分の全身FK用指令値と全身IK用指令値に統合します。RootとHipsに関してはFKにもIKにも使用されることに注意です。

順運動学(FK)と逆運動学(IK)


人型モデルの姿勢を表現・生成する際に、関節角度表現(Configuration Space)とFKを用いるアプローチか、手先や足先座標(Task Space)からIKを用いるアプローチか、悩む人は多いと思います。これはロボット工学においても永遠のテーマであり、どちらが常に正しいというものではなく、目的や文脈に応じて変わります。
現状の最善案は重み付き拘束条件を扱えるIKを用いて、目標関節角度も目標手先座標も等しく重み付き拘束条件として扱うことです。
簡単に言うとFKもIKもいい感じにブレンドして反映しようという作戦です。
ロボット工学においては一般的なことですが、Unityにおいては一工夫必要になります。

UnityにおけるFK・IK

・Unity標準IK
Unity標準のIK関数は片腕・片脚ごとに6~7自由度程度のIKを個別に解くことで、全身の姿勢を決定することになります。しかし、目標手先/足先位置を再現できても目標関節角を指定する機能はありません。

animator.SetIKPositionWeight(AvatarIKGoal.RightHand,1);
animator.SetIKPosition(AvatarIKGoal.RightHand,rightHandObj.position);

・FinalIK
https://assetstore.unity.com/packages/tools/animation/final-ik-14290

Unityにおいてよく使われる有料アセットです。
手先/足先位置だけでなく、肘/膝の目標位置を指定できる等、Unity標準IKより高機能になっていますが、
目標関節角は指定できません。

・BioIK
https://assetstore.unity.com/packages/tools/animation/bio-ik-67819

こちらはあまりメジャーではないようですが、多機能なIK用有料アセットです。遺伝的アルゴリズムを使った収束計算によるIKで、目標手先・足先位置に加えて、目標関節角も拘束条件として設定可能で、ロボット工学における全身IKに近い計算を実行可能です。
しかし、遺伝的アルゴリズムはランダム探索要素を持つアルゴリズムなので、リアルタイムで安定的に解を得るためにはそれなりの計算コストを要します。

おうち3DにおけるFK・IK

本来であればUnityで動くヤコビアンベースのIKソルバーを実装すべきなのですが、Unityで高度な行列演算を実行する手段が乏しいことと、UnityのHumanoidモデルのmuscleの仕様(回転軸ベクトル、角度min-max、2関節連動、etc…)が複雑かつ、一部ブラックボックスな為、正攻法は諦めています。

次善策として採用しているのがIKの初期姿勢に目標関節角を適用する手法です。
この方式ではIKを実行する前に目標関節角を適用し(=FK)その姿勢を初期姿勢としてIKを実行することで、余剰自由度には目標関節角の影響を残したまま、IKの適用された結果が出力されることが期待できます。IK自体にはFinalIKを使用しています。

まとめ

ホロライブアプリでは多種多様なトラッキング入力を相補的にブレンドしながらタレントモデルの全身を表情豊かに動かしています。これらを不整合少なくブレンドするためには、正しい相対座標計算や運動学計算を行う必要がありました。
カバー株式会社ではタレントさんの個性を最大限支援できるような配信システムをこれからも目指していきます。