19
15

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.

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

Last updated at Posted at 2020-05-30

概要

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のインストールなど

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したい人の役に立ったら嬉しいです(^^)

19
15
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
19
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?