どうも、にー兄さんです。
この記事は3D Sensor Advent Calendar 2019の15日目の記事です。
昨日は @takmin さんのカメラ/IMUのキャリブレーションツールkalibrを使ってみたという記事でした。
3D Sensorアドベントカレンダーということで、自分が最近Kinect v2を用いたボディトラッキングを使った開発を行っている中で「こんな文献あったらいいな」と思ったことを書きたいと思います。
とはいえめっちゃ小ネタなので、ご了承ください~
環境
- Windows 10
- Unity 2018.4.x
- Kinect v2
- KinectForWindows_UnityPro 2.0.1410
- UniVRM 0.53
KinectにおけるBody Tracking
UnityでKinectを使ってボディトラッキングをするためには、KinectForWindows_UnityPro
というSDKを使うわけですが、これを用いることによってKinectが認識した人の関節のデータ、つまりJointデータが取得できます。
例えば、以下のようなスクリプトを適当なゲームオブジェクトにアタッチします。
using System.Collections.Generic;
using System.Linq;
using Kinect = Windows.Kinect;
using UnityEngine;
[RequireComponent(typeof(BodySourceManager))]
public class KinectBodyTrackingSample : MonoBehaviour
{
private BodySourceManager _BodySource;
/// <summary>
/// jointデータを格納しているBodyデータ
/// </summary>
private Kinect.Body _Body;
/// <summary>
/// cubemanの関節
/// </summary>
private GameObject
_head,
_leftHand,
_rightHand,
_spineShoulder,
_spineBase,
_leftFoot,
_rightFoot;
private Dictionary<GameObject, Kinect.JointType> ObjectJointTable;
void Start()
{
_BodySource = GetComponent<BodySourceManager>();
#region cubeman
_head = GameObject.CreatePrimitive(PrimitiveType.Cube);
_spineShoulder = GameObject.CreatePrimitive(PrimitiveType.Cube);
_leftHand = GameObject.CreatePrimitive(PrimitiveType.Cube);
_rightHand = GameObject.CreatePrimitive(PrimitiveType.Cube);
_spineBase = GameObject.CreatePrimitive(PrimitiveType.Cube);
_leftFoot = GameObject.CreatePrimitive(PrimitiveType.Cube);
_rightFoot = GameObject.CreatePrimitive(PrimitiveType.Cube);
#endregion cubeman
ObjectJointTable = new Dictionary<GameObject, Kinect.JointType>() {
{_head, Kinect.JointType.Head},
{_spineShoulder, Kinect.JointType.SpineShoulder},
{_leftHand, Kinect.JointType.HandLeft},
{_rightHand, Kinect.JointType.HandRight},
{_spineBase, Kinect.JointType.SpineBase},
{_leftFoot, Kinect.JointType.FootLeft},
{_rightFoot, Kinect.JointType.FootRight}
};
}
void Update()
{
_Body = _BodySource.GetData().FirstOrDefault(b => b.IsTracked);
if (!_Body.IsTracked)
return;
SetPose(_head);
SetPose(_spineShoulder);
SetPose(_leftHand);
SetPose(_rightHand);
SetPose(_spineBase);
SetPose(_leftFoot);
SetPose(_rightFoot);
}
/// <summary>
/// cubemanのそれぞれの関節にjointの姿勢データを適用する
/// </summary>
/// <param name="obj">cubemanの関節</param>
void SetPose(GameObject obj)
{
obj.transform.position = GetJointPose(ObjectJointTable[obj]).position;
obj.transform.rotation = GetJointPose(ObjectJointTable[obj]).rotation;
obj.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
obj.name = ObjectJointTable[obj].ToString();
}
/// <summary>
/// Jointの位置と回転をPose構造体として返す
/// </summary>
/// <param name="type">jointの種類</param>
/// <returns>jointの位置と回転データ</returns>
Pose GetJointPose(Kinect.JointType type)
{
Pose pose = Pose.identity;
pose.position = new Vector3(
_Body.Joints[type].Position.X,
_Body.Joints[type].Position.Y,
_Body.Joints[type].Position.Z
);
pose.rotation = new Quaternion(
_Body.JointOrientations[type].Orientation.X,
_Body.JointOrientations[type].Orientation.Y,
_Body.JointOrientations[type].Orientation.Z,
_Body.JointOrientations[type].Orientation.W
);
return pose;
}
}
そうすると、以下のツイートのようにCubemanを動かすことができます。
Qiitaアドカレ用の資料
— にー兄さん@3DSensorとVTuberTechアドカレ執筆中 (@ninisan_drumath) December 10, 2019
Cubemanの作成 pic.twitter.com/JZ5erKunzA
Kinect v2のJointとUnityのHumanoidリグされたアバターのボーンの種類を見てみる
ということで、Kinectで与えられるJointデータとHumanoidのボーンデータを比較してみましょう。
KinectのSDKではWindows.Kinect
名前空間にJointType
列挙体が定義してあり、HumanoidはHumanBodyBones
定義してあります。
主要な部位を以下の表にまとめてみました。
部位 | HumanBodyBones | Kinect.JointType |
---|---|---|
頭 | Head | Head |
首 | Neck | Neck |
胸 | Chest | x |
両肩 | LeftUpperArm, RightUpperArm | ShoulderLeft, ShoulderRight |
両ひじ | LeftLowerArm, RightLowerArm | ElbowLeft, ElbowRight |
両手首 | LeftHand, RightHand | WristLeft, WristRight |
両手の中央? | x | HandLeft, HandRight |
その他の背骨のボーン | Spine | SpineShoulder, SpineMid |
腰 | Hips | SpineBase |
両太もも | LeftUpperLeg, RightUpperLeg | HipLeft, HipRight |
両ひざ | LeftLowerLeg, RightLowerLeg | KneeLeft, KneeRight |
両足首 | LeftFoot, RightFoot | AnkleLeft, AnkleRight |
つまさき | LeftToes, RightToes | FootLeft, FootRight |
大分大雑把にまとめたので、実際には位置が若干違います。
たとえば腰の位置と足の付け根の位置がそれにあたりますね。
加えて、Humanoidのボーンデータには必要なものと任意なものがあり、
必ずしもモデルに組み込まれているとは限らないのでご注意ください(特にChestなど)
リファレンスにある画像をみてみるとわかりやすいです。
Kinect v2 リファレンス元
Humanoid リファレンス元
個人的にハマったところなど
Kinect v2には前後の認識がない
実際上記の表のように当てはめて終わりなら楽なのですが、KinectV2には前後の状態を判定できないため、ミラー機能を追加する必要があります。Kinectの各Jointを背骨を対象に180度回転させて、アバターの右手にKinectの左手を、アバターの左手をKinectの右手を割り当てるような処理です...
一回動かしてみて、「あれ、変だな」と思ったら試してみてください。
Chestがない
自分はキャリブレーションをするときに上半身と下半身を分離する方法を使っているのですが、上半身のJointデータの基準に胸のボーンを使おうと思ったところ、ちょうどいい胸のボーンがなくて困りました。
上半身にある胸に一番近いJointはSpineShoulder
という、肩の中点みたいなやつでしたので、SpineShoulder
とSpineBase
までの距離とChest
からHips
までの距離の比から推定しました。
Azure KinectのBodyTracking
自分は触ったことないのですが、AzureKinectのBodyTrackingSDKではどうなっているのかちょっと見てみました。具体的な図はありませんでしたが、以下のような構造体が定義してあるっぽいです。
typedef enum
{
K4ABT_JOINT_PELVIS = 0,
K4ABT_JOINT_SPINE_NAVEL,
K4ABT_JOINT_SPINE_CHEST,
K4ABT_JOINT_NECK,
K4ABT_JOINT_CLAVICLE_LEFT,
K4ABT_JOINT_SHOULDER_LEFT,
K4ABT_JOINT_ELBOW_LEFT,
K4ABT_JOINT_WRIST_LEFT,
K4ABT_JOINT_HAND_LEFT,
K4ABT_JOINT_HANDTIP_LEFT,
K4ABT_JOINT_THUMB_LEFT,
K4ABT_JOINT_CLAVICLE_RIGHT,
K4ABT_JOINT_SHOULDER_RIGHT,
K4ABT_JOINT_ELBOW_RIGHT,
K4ABT_JOINT_WRIST_RIGHT,
K4ABT_JOINT_HAND_RIGHT,
K4ABT_JOINT_HANDTIP_RIGHT,
K4ABT_JOINT_THUMB_RIGHT,
K4ABT_JOINT_HIP_LEFT,
K4ABT_JOINT_KNEE_LEFT,
K4ABT_JOINT_ANKLE_LEFT,
K4ABT_JOINT_FOOT_LEFT,
K4ABT_JOINT_HIP_RIGHT,
K4ABT_JOINT_KNEE_RIGHT,
K4ABT_JOINT_ANKLE_RIGHT,
K4ABT_JOINT_FOOT_RIGHT,
K4ABT_JOINT_HEAD,
K4ABT_JOINT_NOSE,
K4ABT_JOINT_EYE_LEFT,
K4ABT_JOINT_EAR_LEFT,
K4ABT_JOINT_EYE_RIGHT,
K4ABT_JOINT_EAR_RIGHT,
K4ABT_JOINT_COUNT
} k4abt_joint_id_t;
v2とは違うところがいくつか見受けられますね。ChestのJointもあるっぽいです。
おわりに
これからv2で開発する方は少ないかもしれませんが、わりと対応表みたいなのがなかったので今回執筆させていただきました。ご参考になれば幸いです。
明日は @asidys230 さんの記事です。