Help us understand the problem. What is going on with this article?

Azure Kinectでキャラクターを動かしてみた

概要

Azure Kinect DK と Body Tracking SDK を使ってVRMのキャラクターを動かしてみました。

成果物

Kinectからの生データをHumanoidのボーンに変換して動かしてます。(Final IK とかは無し)
SnapCrab_KinectVTuber - SampleScene - PC Mac & Linux Standalone - Unity 201937f1 Personal DX11_2020-5-30_15-46-5_No-00.png

Body Tracking SDK って何?

Body Tracking SDK は Azure Kinect DKでボディトラッキングを行うためのパッケージです。
ちなみに、Azure Kinectには2つのSDKが存在して用途が違うようです。

それでは作ってみましょ

1. SDKのインストールなど

https://docs.microsoft.com/ja-jp/azure/Kinect-dk/body-sdk-setup

2. Kinectから生データを取る

今回は、下のサンプルコードを参考にしてKinectのボーンデータをUnityの形式に変換させます。
https://github.com/microsoft/Azure-Kinect-Samples/tree/master/body-tracking-samples/sample_unity_bodytracking

SDKからQuaternionを取得するコードは複雑なので、
今回はサンプルコードのAssets以下を全てコピーしましょう。

Assets/Scenes/Kinect4AzureSampleScene.unityを開くと棒人間が現れるかと思います。
SnapCrab_NoName_2020-5-30_16-49-17_No-00.png

3. 生データからHumanoidのボーンに変換する

サンプルコードのTrackerHandlerクラスで人型のボーンが取れるので、
Humanoid のアニメーションデータに変換していきます。

スクリプト下方の Dictionary<HumanBodyBones, JointId> を返す定数値で、
Body Tracking SDKのボーン値 と Humanoidのボーン値 のマップを作っています。

さらに、手に関しては外れ値を引いて骨折してしまうため可動域を設けています。

KinectHumanoid.cs
using System.Collections.Generic;
using Microsoft.Azure.Kinect.BodyTracking;
using UnityEngine;

public class KinectHumanoid : MonoBehaviour
{
    [SerializeField] private TrackerHandler trackerHandler;
    [SerializeField] private Animator animator;
    [SerializeField] private GameObject pelvis;

    void Update()
    {
        MoveBody();
    }

    private void MoveBody()
    {
        transform.localPosition = pelvis.transform.localPosition;
        transform.localRotation = pelvis.transform.localRotation;

        upperBodyBoneMap.TryForeach(bone =>
        {
            var nextRot = trackerHandler.GetRelativeJointRotation(bone.Value);
            var prevRot = animator.GetBoneTransform(bone.Key).localRotation;
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });

        lowerBodyBoneMap.TryForeach(bone =>
        {
            var nextRot = trackerHandler.GetRelativeJointRotation(bone.Value);
            var prevRot = animator.GetBoneTransform(bone.Key).localRotation;
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });

        leftHandBoneMap.TryForeach(bone =>
        {
            var nextRotZ = trackerHandler.GetRelativeJointRotation(bone.Value).eulerAngles.z;
            var nextZ = nextRotZ <= 0 ? 0 : nextRotZ > 70 ? 70 : nextRotZ;
            var nextRotVec = new Vector3(0, 0, nextZ);
            var nextRot = Quaternion.Euler(nextRotVec);
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });

        rightHandBoneMap.TryForeach(bone =>
        {
            var nextRotZ = trackerHandler.GetRelativeJointRotation(bone.Value).eulerAngles.z;
            var nextZ = 0 >= nextRotZ ? 0 : nextRotZ >= 360 ? 360 : nextRotZ < 290 ? 290 : nextRotZ;
            var nextRotVec = new Vector3(0, 0, nextZ);
            var nextRot = Quaternion.Euler(nextRotVec);
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });

        leftThumbBoneMap.TryForeach(bone =>
        {
            var nextRotX = trackerHandler.GetRelativeJointRotation(bone.Value).eulerAngles.x;
            var nextX = nextRotX <= 0 ? 0 : nextRotX > 70 ? 70 : nextRotX;
            var nextRotVec = new Vector3(nextX, 0, 0);
            var nextRot = Quaternion.Euler(nextRotVec);
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });

        rightThumbBoneMap.TryForeach(bone =>
        {
            var nextRotX = trackerHandler.GetRelativeJointRotation(bone.Value).eulerAngles.x;
            var nextX = 0 >= nextRotX ? 0 : nextRotX >= 360 ? -360 : nextRotX < 290 ? -290 : -nextRotX;
            var nextRotVec = new Vector3(nextX, 0, 0);
            var nextRot = Quaternion.Euler(nextRotVec);
            animator.GetBoneTransform(bone.Key).localRotation = new Quaternion(nextRot.x, nextRot.y, nextRot.z, nextRot.w);
        });
    }

    private readonly Dictionary<HumanBodyBones, JointId> upperBodyBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.Head, JointId.Head},
            {HumanBodyBones.Neck, JointId.Neck},
            {HumanBodyBones.Chest, JointId.SpineChest},
            {HumanBodyBones.Spine, JointId.SpineNavel},
            {HumanBodyBones.LeftShoulder, JointId.ClavicleLeft},
            {HumanBodyBones.RightShoulder, JointId.ClavicleRight},
            {HumanBodyBones.LeftUpperArm, JointId.ShoulderLeft},
            {HumanBodyBones.RightUpperArm, JointId.ShoulderRight},
            {HumanBodyBones.LeftLowerArm, JointId.ElbowLeft},
            {HumanBodyBones.RightLowerArm, JointId.ElbowRight},
            {HumanBodyBones.LeftHand, JointId.WristLeft},
            {HumanBodyBones.RightHand, JointId.WristRight},
        };

    private readonly Dictionary<HumanBodyBones, JointId> lowerBodyBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.Hips, JointId.SpineNavel},
            {HumanBodyBones.LeftUpperLeg, JointId.HipLeft},
            {HumanBodyBones.RightUpperLeg, JointId.HipRight},
            {HumanBodyBones.LeftLowerLeg, JointId.KneeLeft},
            {HumanBodyBones.RightLowerLeg, JointId.KneeRight},
            {HumanBodyBones.LeftFoot, JointId.FootLeft},
            {HumanBodyBones.RightFoot, JointId.FootRight},
            {HumanBodyBones.LeftToes, JointId.FootLeft},
            {HumanBodyBones.RightToes, JointId.FootRight},
        };

    private readonly Dictionary<HumanBodyBones, JointId> leftHandBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.LeftIndexProximal, JointId.HandLeft},
            {HumanBodyBones.LeftIndexIntermediate, JointId.HandLeft},
            {HumanBodyBones.LeftIndexDistal, JointId.HandLeft},
            {HumanBodyBones.LeftMiddleProximal, JointId.HandLeft},
            {HumanBodyBones.LeftMiddleIntermediate, JointId.HandLeft},
            {HumanBodyBones.LeftMiddleDistal, JointId.HandLeft},
            {HumanBodyBones.LeftRingProximal, JointId.HandLeft},
            {HumanBodyBones.LeftRingIntermediate, JointId.HandLeft},
            {HumanBodyBones.LeftRingDistal, JointId.HandLeft},
            {HumanBodyBones.LeftLittleProximal, JointId.HandLeft},
            {HumanBodyBones.LeftLittleIntermediate, JointId.HandLeft},
            {HumanBodyBones.LeftLittleDistal, JointId.HandLeft},
        };

    private readonly Dictionary<HumanBodyBones, JointId> rightHandBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.RightIndexProximal, JointId.HandRight},
            {HumanBodyBones.RightIndexIntermediate, JointId.HandRight},
            {HumanBodyBones.RightIndexDistal, JointId.HandRight},
            {HumanBodyBones.RightMiddleProximal, JointId.HandRight},
            {HumanBodyBones.RightMiddleIntermediate, JointId.HandRight},
            {HumanBodyBones.RightMiddleDistal, JointId.HandRight},
            {HumanBodyBones.RightRingProximal, JointId.HandRight},
            {HumanBodyBones.RightRingIntermediate, JointId.HandRight},
            {HumanBodyBones.RightRingDistal, JointId.HandRight},
            {HumanBodyBones.RightLittleProximal, JointId.HandRight},
            {HumanBodyBones.RightLittleIntermediate, JointId.HandRight},
            {HumanBodyBones.RightLittleDistal, JointId.HandRight},
        };

    private readonly Dictionary<HumanBodyBones, JointId> leftThumbBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.LeftThumbProximal, JointId.ThumbLeft},
            {HumanBodyBones.LeftThumbIntermediate, JointId.ThumbLeft},
            {HumanBodyBones.LeftThumbDistal, JointId.ThumbLeft},
        };

    private readonly Dictionary<HumanBodyBones, JointId> rightThumbBoneMap =
        new Dictionary<HumanBodyBones, JointId>
        {
            {HumanBodyBones.RightThumbProximal, JointId.ThumbRight},
            {HumanBodyBones.RightThumbIntermediate, JointId.ThumbRight},
            {HumanBodyBones.RightThumbDistal, JointId.ThumbRight},
        };

    /* NOT ASSIGN */
    // {HumanBodyBones.UpperChest, JointId.SpineChest},
    // {HumanBodyBones.LeftEye, JointId.EyeLeft},
    // {HumanBodyBones.RightEye, JointId.EyeRight},
    // {HumanBodyBones.Jaw, JointId.Nose},
}

アサインできなかったボーンが少しありますが、なくても動きはしました。

Arrayの便利関数も用意。

ArrayExtensions.cs
public static class ArrayExtensions
{
    public static List<Exception> TryForeach<T>(this ICollection<T> array, Action<T> block)
    {
        var ex = new List<Exception>();
        foreach (var a in array)
        {
            try
            {
                block(a);
            }
            catch (Exception exception)
            {
                ex.Add(exception);
            }
        }
        return ex;
    }
}

出来たコンポーネントは、VRMのキャラクターにつけて各オブジェクトをアタッチしてください。
SnapCrab_NoName_2020-5-30_17-7-44_No-00.png

まとめ

トラッキングが外れて関節が捻じ曲がるときはありますが、結構感動しますね!
Azure Kinect DKでVtuberしたい人の役に立ったら嬉しいです(^^)

TakenokoTech
たけのこです! 都内でエンジニアやってます。 最近はVRに嵌っています。
https://takenoko.tech
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away