【Unity】Kinect v2におけるJointとUnityにおけるHumanoidのボーンの対応について

どうも、にー兄さんです。

この記事は[3D Sensor Advent Calendar 2019](https://qiita.com/advent-calendar/2019/3d-sensor)の15日目の記事です。

昨日は @takmin さんの[カメラ/IMUのキャリブレーションツールkalibrを使ってみた](https://takmin.hatenablog.com/entry/2019/12/15/000830)という記事でした。

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データが取得できます。
例えば、以下のようなスクリプトを適当なゲームオブジェクトにアタッチします。

```cs
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を動かすことができます。

<blockquote class="twitter-tweet" data-partner="tweetdeck"><p lang="ja" dir="ltr">Qiitaアドカレ用の資料<br>Cubemanの作成 <a href="https://t.co/JZ5erKunzA">pic.twitter.com/JZ5erKunzA</a></p>&mdash; にー兄さん@3DSensorとVTuberTechアドカレ執筆中 (@ninisan_drumath) <a href="https://twitter.com/ninisan_drumath/status/1204256965192404993?ref_src=twsrc%5Etfw">December 10, 2019</a></blockquote>

# 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** [リファレンス元](https://docs.microsoft.com/en-us/previous-versions/windows/kinect/dn758662(v=ieb.10))

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86191/0083e7ed-fd6d-f1b4-0f95-eba7adfa689c.png)

**Humanoid** [リファレンス元](https://docs.unity3d.com/jp/460/ScriptReference/HumanBodyBones.html)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86191/dad93a93-4917-a49e-d6d5-ccead69fe7f0.png)

# 個人的にハマったところなど

## Kinect v2には前後の認識がない

実際上記の表のように当てはめて終わりなら楽なのですが、KinectV2には前後の状態を判定できないため、ミラー機能を追加する必要があります。Kinectの各Jointを背骨を対象に180度回転させて、アバターの右手にKinectの左手を、アバターの左手をKinectの右手を割り当てるような処理です...
一回動かしてみて、「あれ、変だな」と思ったら試してみてください。

## Chestがない

自分はキャリブレーションをするときに上半身と下半身を分離する方法を使っているのですが、上半身のJointデータの基準に胸のボーンを使おうと思ったところ、ちょうどいい胸のボーンがなくて困りました。
上半身にある胸に一番近いJointは`SpineShoulder`という、肩の中点みたいなやつでしたので、`SpineShoulder`と`SpineBase`までの距離と`Chest`から`Hips`までの距離の比から推定しました。

# Azure KinectのBodyTracking

自分は触ったことないのですが、AzureKinectのBodyTrackingSDKではどうなっているのかちょっと見てみました。具体的な図はありませんでしたが、以下のような構造体が定義してあるっぽいです。

```c
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 さんの記事です。