8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

3D SensorAdvent Calendar 2019

Day 15

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

Last updated at Posted at 2019-12-14

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

この記事は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を動かすことができます。

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 リファレンス元

image.png

Humanoid リファレンス元

image.png

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

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

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

Chestがない

自分はキャリブレーションをするときに上半身と下半身を分離する方法を使っているのですが、上半身のJointデータの基準に胸のボーンを使おうと思ったところ、ちょうどいい胸のボーンがなくて困りました。
上半身にある胸に一番近いJointはSpineShoulderという、肩の中点みたいなやつでしたので、SpineShoulderSpineBaseまでの距離と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 さんの記事です。

8
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?