なんかいろいろしてみます

Mar 18, 2020 - 2 minute read - HoloLens

HoloLens 2でHand Jointの利用方法

HoloLens 2から取得可能になったHand Jointの利用方法の解説をします.

HoloLens 2のHand情報

HoloLens 2からハンドトラッキングが強化されAirTapポーズ以外でもトラッキングが行えるようになりました.

また取得できるトラッキング情報もハンド位置のみから

  • 左右の識別
  • 回転情報
  • 関節の位置,回転情報
  • 形状メッシュ情報

が取得できるようになりました.

今回はHand Jointを取得するまでを行います.

開発環境

  • Unity 2018.4

取得手順

Unityのプロジェクトの設定は前回のHoloLens 2で利用できるジェスチャー入力について(Unity 2018編)の設定を参考にしてください.

Hand Joint情報はUnityの関数からは取得できないためUWP関数から取得する必要があります.

1234567
#if WINDOWS_UWP
using UnityEngine.XR.WSA;
using System.Runtime.InteropServices;
using Windows.Perception.Spatial;
using Windows.Perception.People;
using Windows.UI.Input.Spatial;
#endif

Unityで利用できないUWP関数はWINDOWS_UWPで切り分けを行い,UnityのThreadで取得できない機能はInvokeOnUIThreadでThreadを変更しておく必要があります.

Hand Jointは関節位置HandJointKindとPose情報JointPoseSpatialInteractionManagerのイベント内で取得することができます.

 1 2 3 4 5 6 7 8 91011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
    void Start()
    {
#if WINDOWS_UWP
        handJoints = new HandJointKind[]
        {
            HandJointKind.Palm,
            HandJointKind.Wrist,
            HandJointKind.ThumbMetacarpal,
            HandJointKind.ThumbProximal,
            HandJointKind.ThumbDistal,
            HandJointKind.ThumbTip,
            HandJointKind.IndexMetacarpal,
            HandJointKind.IndexProximal,
            HandJointKind.IndexIntermediate,
            HandJointKind.IndexDistal,
            HandJointKind.IndexTip,
            HandJointKind.MiddleMetacarpal,
            HandJointKind.MiddleProximal,
            HandJointKind.MiddleIntermediate,
            HandJointKind.MiddleDistal,
            HandJointKind.MiddleTip,
            HandJointKind.RingMetacarpal,
            HandJointKind.RingProximal,
            HandJointKind.RingIntermediate,
            HandJointKind.RingDistal,
            HandJointKind.RingTip,
            HandJointKind.LittleMetacarpal,
            HandJointKind.LittleProximal,
            HandJointKind.LittleIntermediate,
            HandJointKind.LittleDistal,
            HandJointKind.LittleTip
        };
        jointPoses = new JointPose[handJoints.Length];

        rightHandJoints = new GameObject[handJoints.Length];
        leftHandJoints = new GameObject[handJoints.Length];
        for (int i = 0; i < rightHandJoints.Length; i++)
        {
            rightHandJoints[i] = GameObject.CreatePrimitive(PrimitiveType.Cube);
            rightHandJoints[i].transform.localScale = Vector3.one * 0.01f;
            leftHandJoints[i] = GameObject.CreatePrimitive(PrimitiveType.Cube);
            leftHandJoints[i].transform.localScale = Vector3.one * 0.01f;
        }

        SpatialInteractionManager spatialInteraction = null;
        spatialCoordinateSystem = Marshal.GetObjectForIUnknown(WorldManager.GetNativeISpatialCoordinateSystemPtr()) as SpatialCoordinateSystem;
        UnityEngine.WSA.Application.InvokeOnUIThread(() =>
        {
            spatialInteraction = SpatialInteractionManager.GetForCurrentView();
        }, true);
        spatialInteraction.SourceUpdated += SpatialInteraction_SourceUpdated;
#endif
    }

左右の手の判別と合わせることで各関節の位置と回転情報を取得することができます.

ただしUnityのTransformやGameObjectの値を入れる場合には,InvokeOnAppThreadを利用してUnityのThread内で処理を行う必要があります.

 1 2 3 4 5 6 7 8 9101112131415161718192021222324252627282930
    private void SpatialInteraction_SourceUpdated(SpatialInteractionManager sender, SpatialInteractionSourceEventArgs args)
    {
        var item = args.State;
        var handPose = item.TryGetHandPose();
        if (handPose != null && handPose.TryGetJoints(spatialCoordinateSystem, handJoints, jointPoses))
        {
            for (int i = 0; i < handJoints.Length; i++)
            {
                var joint = i;
                var pos = jointPoses[(int)handJoints[joint]].Position;
                var rot = jointPoses[(int)handJoints[joint]].Orientation;
                if (item.Source.Handedness == SpatialInteractionSourceHandedness.Right)
                {
                    UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                    {
                        rightHandJoints[joint].transform.position = new Vector3(pos.X, pos.Y, -pos.Z);
                        rightHandJoints[joint].transform.rotation = new Quaternion(-rot.X, -rot.Y, rot.Z, rot.W);
                    }, false);
                }
                else if (item.Source.Handedness == SpatialInteractionSourceHandedness.Left)
                {
                    UnityEngine.WSA.Application.InvokeOnAppThread(() =>
                    {
                        leftHandJoints[joint].transform.position = new Vector3(pos.X, pos.Y, -pos.Z);
                        leftHandJoints[joint].transform.rotation = new Quaternion(-rot.X, -rot.Y, rot.Z, rot.W);
                    }, false);
                }
            }
        }
    }

各Hand Jointの位置と回転を取得してCubeとして表示するサンプルプロジェクトを公開しています.

また動作時の動画が以下になります.

GitHub : HoloLens2HandJointSampleWithUnity

Windows XR Plugin対応

WorldManager.GetNativeISpatialCoordinateSystemPtr()UnityEngine.XR.WSAの機能なのでUnity 2019以降のWindows XR Pluginでは利用できません.

代わりにWindowsMREnvironment.OriginSpatialCoordinateSystemからSpatialCoordinateSystemを取得することができます.

Windows XR Pluginの設定はHoloLens 2で利用できるジェスチャー入力について(Unity 2019編)

サンプルプロジェクト内でもUnity 2019以降はWindows XR Pluginに対応するようにしています.

GitHub : HoloLens2HandJointSampleWithUnity

まとめ

  • UnityからHoloLens 2のHand Joint情報が取得できる
  • Unityの機能では取得できないのでWINDOWS_UWPで切り分けを行う
  • UnityEngine.XR.WSAWindows XR Pluginで利用できる関数が一部異なる
  • 人差し指の位置が認識できるとオブジェクトをツンツンできる
  • 頑張ればこんなのができる