見出し画像

Magic Leap2 の ハンドトラッキング

はじめに

この記事は、Magic Leap2 Advent Calendar 2022 の18日目です。
Magic Leap2 トラッキング について説明します。
Magic Leap 1 と同様にMagic Leap2 にもハンドトラッキングの機能が搭載しています。Magic Leap 1 の時は位置のみ取得することができ、回転情報はMagic Leap Toolkit(MLTK)を導入する必要がありましたが、Magic Leap2では標準のAPIで回転の情報も取得することができます。


OnePlanet XR について

https://1planet.co.jp/xrconsulting.html

このブログ記事は OnePlanet XR によるものです。
OnePlanet XR は、AR/MR/VPS技術に専門特化したコンサルティングサービスです。豊富な実績を元に、AR/MR技術を活用した新たな事業の立ち上げ支援や、社内業務のデジタル化/DX推進など、貴社の必要とするイノベーションを実現いたします。

ご相談から受け付けております。ご興味ございましたらお問い合わせください。


Magic Leap2 の ハンドトラッキングについて

ハンドトラッキングの各キーポイントにCubeを配置してトラッキングするデモアプリケーションか開発します。


開発環境 / 動作環境

Unity Editor 2022.2.0b8.3023
Magic Leap SDK 1.1.0-dev1
Magic Leap XR Plugin 7.0.0.pre.1
Magic Leap2 OS 1.1.0-dev1 (B3E.221020.13-R.039_40)


取得できるキーポイント

計28個のキーポイントの取得が可能です。

  • 親指 4個

  • 親指以外 5個

  • 手の真ん中 1個

  • 手首 3個(Wrist UlnarとWrist Radialは計算しないため実質1個。)


ヒエラルキー

Hand Tracking Example

シーンを新規作成します。Main Cameraは削除し、XR Rigのプレファブをヒエラルキーに配置します。
Game Objectを作成し、名前を Hand Tracking Example にします。

キーポイント用の Game Object 群

親指(4個のCube)、親指以外の4本の指(5個のCube)、中央(1個のCube)、手首(中央しかキーポイントは取得できないが、ここでは3個のCube)を左右分生成してヒエラルキー上に配置します。Cubeのスケールは0.05とします。


HandTrackingExample

Hand Tracking Example の Game Object にアタッチするHandTrackingExampleというスクリプトを作成します。

スクリプトは以下になります。

using UnityEngine;
using UnityEngine.XR;
using UnityEngine.XR.MagicLeap;
using InputDevice = UnityEngine.XR.InputDevice;
using HandTracking = UnityEngine.XR.MagicLeap.InputSubsystem.Extensions.MLHandTracking;
using System.Collections.Generic;
using System.Collections;

public class HandTrackingExample : MonoBehaviour
{
    private float highConfidence = 0.6f;

    private InputDevice leftHandDevice;
    private InputDevice rightHandDevice;

    private List<Bone> leftPinkyFingerBones = new List<Bone>();
    private List<Bone> leftRingFingerBones = new List<Bone>();
    private List<Bone> leftMiddleFingerBones = new List<Bone>();
    private List<Bone> leftIndexFingerBones = new List<Bone>();
    private List<Bone> leftThumbFingerBones = new List<Bone>();
    private List<Bone> leftWristBones = new List<Bone>();

    private List<Bone> rightPinkyFingerBones = new List<Bone>();
    private List<Bone> rightRingFingerBones = new List<Bone>();
    private List<Bone> rightMiddleFingerBones = new List<Bone>();
    private List<Bone> rightIndexFingerBones = new List<Bone>();
    private List<Bone> rightThumbFingerBones = new List<Bone>();
    private List<Bone> rightWristBones = new List<Bone>();

    [SerializeField]
    private GameObject[] leftPinkyFinger;

    [SerializeField]
    private GameObject[] leftRingFinger;

    [SerializeField]
    private GameObject[] leftMiddleFinger;

    [SerializeField]
    private GameObject[] leftIndexFinger;

    [SerializeField]
    private GameObject[] leftThumbFinger;

    [SerializeField]
    private GameObject[] leftWrist;

    [SerializeField]
    private GameObject leftCenter;

    [SerializeField]
    private GameObject[] rightPinkyFinger;

    [SerializeField]
    private GameObject[] rightRingFinger;

    [SerializeField]
    private GameObject[] rightMiddleFinger;

    [SerializeField]
    private GameObject[] rightIndexFinger;

    [SerializeField]
    private GameObject[] rightThumbFinger;

    [SerializeField]
    private GameObject[] rightWrist;

    [SerializeField]
    private GameObject rightCenter;


    // Start is called before the first frame update
    void Start()
    {
        if (MLSegmentedDimmer.Exists)
        {
            MLSegmentedDimmer.Activate();
            MLSegmentedDimmer.SetEnabled(true);
        }

        if (MLPermissions.CheckPermission(MLPermission.HandTracking).IsOk)
        {
            InputSubsystem.Extensions.MLHandTracking.StartTracking();
        }
    }

    // Update is called once per frame
    void Update()
    {
        TrackingLeftHand();
        TrackingRightHand();
    }

    private void TrackingLeftHand()
    {
        if (!leftHandDevice.isValid)
        {
            leftHandDevice = InputSubsystem.Utils.FindMagicLeapDevice(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.Left);
            return;
        }

        leftHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.Confidence, out float handConfidence);
        if (handConfidence < highConfidence)
        {
            return;
        }

        if (leftHandDevice.TryGetFeatureValue(CommonUsages.handData, out UnityEngine.XR.Hand leftHand))
        {
            leftHand.TryGetFingerBones(UnityEngine.XR.HandFinger.Index, leftIndexFingerBones);
            leftHand.TryGetFingerBones(UnityEngine.XR.HandFinger.Middle, leftMiddleFingerBones);
            leftHand.TryGetFingerBones(UnityEngine.XR.HandFinger.Ring, leftRingFingerBones);
            leftHand.TryGetFingerBones(UnityEngine.XR.HandFinger.Pinky, leftPinkyFingerBones);
            leftHand.TryGetFingerBones(UnityEngine.XR.HandFinger.Thumb, leftThumbFingerBones);

            leftWristBones.Clear();
            leftHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristCenter, out Bone wrist);
            leftWristBones.Add(wrist);
            leftHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristRadial, out wrist);
            leftWristBones.Add(wrist);
            leftHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristUlnar, out wrist);
            leftWristBones.Add(wrist);

        }
        
        for (int i = 0; i < leftPinkyFingerBones.Count; i++)
        {
            leftPinkyFingerBones[i].TryGetPosition(out Vector3 leftPinkyBonePosition);
            leftPinkyFingerBones[i].TryGetRotation(out Quaternion leftPinkyBoneRotation);
            leftPinkyFinger[i].transform.position = leftPinkyBonePosition;
            leftPinkyFinger[i].transform.rotation = leftPinkyBoneRotation;
        }
        for (int i = 0; i < leftMiddleFingerBones.Count; i++)
        {
            leftMiddleFingerBones[i].TryGetPosition(out Vector3 leftMiddleBonePosition);
            leftMiddleFingerBones[i].TryGetRotation(out Quaternion leftMiddleBoneRotation);
            leftMiddleFinger[i].transform.position = leftMiddleBonePosition;
            leftMiddleFinger[i].transform.rotation = leftMiddleBoneRotation;
        }
        for (int i = 0; i < leftRingFingerBones.Count; i++)
        {
            leftRingFingerBones[i].TryGetPosition(out Vector3 leftRingBonePosition);
            leftRingFingerBones[i].TryGetRotation(out Quaternion leftRingBoneRotation);
            leftRingFinger[i].transform.position = leftRingBonePosition;
            leftRingFinger[i].transform.rotation = leftRingBoneRotation;
        }
        for (int i = 0; i < leftIndexFingerBones.Count; i++)
        {
            leftIndexFingerBones[i].TryGetPosition(out Vector3 leftIndexBonePosition);
            leftIndexFingerBones[i].TryGetRotation(out Quaternion leftIndexBoneRotation);
            leftIndexFinger[i].transform.position = leftIndexBonePosition;
            leftIndexFinger[i].transform.rotation = leftIndexBoneRotation;
        }
        for (int i = 0; i < leftThumbFingerBones.Count; i++)
        {
            leftThumbFingerBones[i].TryGetPosition(out Vector3 leftThumbBonePosition);
            leftThumbFingerBones[i].TryGetRotation(out Quaternion leftThumbBoneRotation);
            leftThumbFinger[i].transform.position = leftThumbBonePosition;
            leftThumbFinger[i].transform.rotation = leftThumbBoneRotation;
        }

        for (int i = 0; i < leftWristBones.Count; i++)
        {
            leftWristBones[i].TryGetPosition(out Vector3 leftWristBonePosition);
            leftWristBones[i].TryGetRotation(out Quaternion leftWristBoneRotation);
            leftWrist[i].transform.position = leftWristBonePosition;
            leftWrist[i].transform.rotation = leftWristBoneRotation;
        }

        leftHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 leftHandCenterPosition);
        leftHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out Quaternion leftHandCenterRotation);
        leftCenter.transform.position = leftHandCenterPosition;
        leftCenter.transform.rotation = leftHandCenterRotation;
    }

    private void TrackingRightHand()
    {
        if (!rightHandDevice.isValid)
        {
            rightHandDevice = InputSubsystem.Utils.FindMagicLeapDevice(InputDeviceCharacteristics.HandTracking | InputDeviceCharacteristics.Right);
            return;
        }

        rightHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.Confidence, out float handConfidence);
        if (handConfidence < highConfidence)
        {
            return;
        }

        if (rightHandDevice.TryGetFeatureValue(CommonUsages.handData, out UnityEngine.XR.Hand hand))
        {
            hand.TryGetFingerBones(UnityEngine.XR.HandFinger.Index, rightIndexFingerBones);
            hand.TryGetFingerBones(UnityEngine.XR.HandFinger.Middle, rightMiddleFingerBones);
            hand.TryGetFingerBones(UnityEngine.XR.HandFinger.Ring, rightRingFingerBones);
            hand.TryGetFingerBones(UnityEngine.XR.HandFinger.Pinky, rightPinkyFingerBones);
            hand.TryGetFingerBones(UnityEngine.XR.HandFinger.Thumb, rightThumbFingerBones);

            rightWristBones.Clear();
            rightHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristCenter, out Bone wrist);
            rightWristBones.Add(wrist);
            rightHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristRadial, out wrist);
            rightWristBones.Add(wrist);
            rightHandDevice.TryGetFeatureValue(InputSubsystem.Extensions.DeviceFeatureUsages.Hand.WristUlnar, out wrist);
            rightWristBones.Add(wrist);
        }

        for (int i = 0; i < rightPinkyFingerBones.Count; i++)
        {
            rightPinkyFingerBones[i].TryGetPosition(out Vector3 rightPinkyBonePosition);
            rightPinkyFingerBones[i].TryGetRotation(out Quaternion rightPinkyBoneRotation);
            rightPinkyFinger[i].transform.position = rightPinkyBonePosition;
        }
        for (int i = 0; i < rightMiddleFingerBones.Count; i++)
        {
            rightMiddleFingerBones[i].TryGetPosition(out Vector3 rightMiddleBonePosition);
            rightMiddleFingerBones[i].TryGetRotation(out Quaternion rightMiddleBoneRotation);
            rightMiddleFinger[i].transform.position = rightMiddleBonePosition;
            rightMiddleFinger[i].transform.rotation = rightMiddleBoneRotation;
        }
        for (int i = 0; i < rightRingFingerBones.Count; i++)
        {
            rightRingFingerBones[i].TryGetPosition(out Vector3 rightRingBonePosition);
            rightRingFingerBones[i].TryGetRotation(out Quaternion rightRingBoneRotation);
            rightRingFinger[i].transform.position = rightRingBonePosition;
        }
        for (int i = 0; i < rightIndexFingerBones.Count; i++)
        {
            rightIndexFingerBones[i].TryGetPosition(out Vector3 rightIndexBonePosition);
            rightIndexFingerBones[i].TryGetRotation(out Quaternion rightIndexBoneRotation);
            rightIndexFinger[i].transform.position = rightIndexBonePosition;
            rightIndexFinger[i].transform.rotation = rightIndexBoneRotation;
        }
        for (int i = 0; i < rightThumbFingerBones.Count; i++)
        {
            rightThumbFingerBones[i].TryGetPosition(out Vector3 rightThumbBonePosition);
            rightThumbFingerBones[i].TryGetRotation(out Quaternion rightThumbBoneRotation);
            rightThumbFinger[i].transform.position = rightThumbBonePosition;
            rightThumbFinger[i].transform.rotation = rightThumbBoneRotation;
        }

        for (int i = 0; i < rightWristBones.Count; i++)
        {
            rightWristBones[i].TryGetPosition(out Vector3 rightWristBonePosition);
            rightWristBones[i].TryGetRotation(out Quaternion rightWristBoneRotation);
            rightWrist[i].transform.position = rightWristBonePosition;
            rightWrist[i].transform.rotation = rightWristBoneRotation;
        }

        rightHandDevice.TryGetFeatureValue(CommonUsages.devicePosition, out Vector3 rightHandCenterPosition);
        rightHandDevice.TryGetFeatureValue(CommonUsages.deviceRotation, out Quaternion rightHandCenterRotation);
        rightCenter.transform.position = rightHandCenterPosition;
        rightCenter.transform.rotation = rightHandCenterRotation;
    }
}



HandTrackingExampleにキーポイント用のCubeを設定

Hand Tracking Example の キーポイント用のフィールドにヒエラルキー上で生成した、キーポイント用の Game Object 群を全て設定します。


実行

左右の手を目の前にかざすとハンドトラッキング機能が働き、Cubeが手の動きに追従します。


OnePlanet XR

https://1planet.co.jp/xrconsulting.html

AR/MR/VPS技術に専門特化したコンサルティングサービス

Magic Leap2 を使ったソリューションのご検討の方からのお問い合わせ、お待ちしております。


お問い合わせ先

https://1planet.co.jp/xrconsulting.html#op_form


OnePlanet Tech Magazine

Magic Leap 1、Magic Leap2、スマホAR(Niantic Lightship ARDK、WebAR、VPSなど)といったAR技術全般をブログマガジンを連載しています。