夜風のMixedReality

xRと出会って変わった人生と出会った技術を書き残すためのGeekなHoloRangerの居場所

Osaka Hackathon Online 2021 二日目 すべての指の検知機能の実装

本日はイベント枠です。   昨日3月6日から大阪駆動開発のイベント「Osaka Hakkathon Online 2021 チームビルド」が始まりました。

このイベントは関西のxRコミュニティ大阪駆動開発が主催のHoloLensイベントで、毎年行われているイベントが今年は新型コロナウィルス感染症の影響を受けオンラインの形で行われました。

redhologerbera.hatenablog.com

〇ボッチソン『HoloLensで片手で操作できる未来感あるUI』

今回筆者が取り組むテーマは『HoloLensで片手で操作できる未来感あるUI』です。

これはHoloLens 2で使用できるHandTrackingを用いたオリジナルのUIを作成するプロダクトで、MRTKで提供されているHandMenuなどデフォルトのHandUIは片手操作ができないという門外があります。

様々な人が今後HoloLensを使っていく中で片手で操作できるUIというものを作りたいと思いテーマとして選びました。

f:id:Holomoto-Sumire:20210306185244j:plain
イメージ

審査員の方からはプロダクトが多い中このようなコンポーネントは非常に価値がある。ぜひUnityパッケージにして公開してほしいという言葉をいただいています。

〇すべての指検知の実装

本日は指検知のためにすべての指を検知できるスクリプトを記述しました。

ベースとなるコードは以前作成した指検知のコードです。

redhologerbera.hatenablog.com

redhologerbera.hatenablog.com

新規に追加した機能は次のようになります。

・薬指、小指の判定機能

・指が伸びていること、曲げられたときに最初に1度だけ起動するイベントの追加

・一部見やすく変更

〇HoloLensの指の精度

HoloLens2のHandTrackingでは指を取得することができますが、その精度は完ぺきではありません。

例えばHandTrackingでお寿司を握る職人の指の動きを各関節データを記録し取得したとして、それが正確であるかと言われると非常にノイズがあふれているでしょう。

あくまでMixedRealityの対話のためのHandTrackingのためモーションキャプチャーの目的としては精度が低いです。

今回でいうと人差し指、中指の曲げ伸び判定は非常に正確なのですが、中指、小指のトラッキングが甘いです。とくに小指は中指につられて伸びているとみなされることがあります。

そのため今回は5本指のそれぞれ判定をあきらめ親指を覗き人差し指、中指、薬指&小指という3つのグループで指判定を行うことにしました。

〇本日書いたコード

ハッカソン中のため大きく変更、もしくは最終的には使用されない場合もあります。

using System;
using UnityEngine;

using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Utilities;
using Microsoft.MixedReality.Toolkit.Utilities.Solvers;
using UnityEngine.Events;


public class FingerGestureDetect : MonoBehaviour
{
    [SerializeField,Header("Target"),Tooltip("Tracked Hands")]
    Handedness handType;
    
    [SerializeField,Range(0,90),Header("Threshold")]
    private float indexThreshold = 5;

    [SerializeField,Range(0,90)]
    private float middleThreshold,ringAndPinkyFingerThreshpld, thumbThreshold;

    [SerializeField, Range(0, 90)] 
    private float facingThreshold;

    [SerializeField,Header("Index"),Header("Events")] 
    UnityEvent OnIndexFingerDetect;

    [SerializeField] 
    UnityEvent OnIndexFingerLost;

    [SerializeField,Header("Middle")] 
    UnityEvent OnMiddleFingerDetect;

    [SerializeField] 
    UnityEvent OnMiddleFingerLost;

    [SerializeField, Header("Ring And Pinky")] 
    UnityEvent OnRingAndPinkyDetect;
    [SerializeField]
    UnityEvent OnRingAndPinkyLost;
    [SerializeField,Header("Thumb"),] 
    UnityEvent OnThumbFingerDetect;

    [SerializeField] 
    UnityEvent OnThumbFingerLost;

    [SerializeField, Header("FirstEvent")] 
    UnityEvent OnIndexFingerFirst;  
    [SerializeField] 
    UnityEvent OnMiddleFingerFirst,OnRingAndPinkyFingerFirst;
    
     bool?  handdetected;

     bool indexFirstfinger,middleFirstfinger,ringandPinkyFirstfinger;

    // Update is called once per frame
    void Update()
    {
        handdetected = HandJointUtils.FindHand(handType)?.TryGetJoint(TrackedHandJoint.Palm, out MixedRealityPose PalmPose);
        if ( handdetected != null && handdetected==true )
        {
            
            if (indexfingerDetected()&& HanddirDetected())
            {
                OnIndexFingerDetect.Invoke();
                if (indexFirstfinger)
                {
                    indexFirstfinger = false;
                    OnIndexFingerFirst.Invoke();
                }
            }
            else
            {
                OnIndexFingerLost.Invoke();
                indexFirstfinger = true;
            }
            

            if (middlefingerDetected()&& HanddirDetected())
            {
                OnMiddleFingerDetect.Invoke();
                if (middleFirstfinger)
                {
                    middleFirstfinger = false;
                    OnMiddleFingerFirst.Invoke();
                }
            }
            else
            {
                OnMiddleFingerLost.Invoke();
                middleFirstfinger = true;
            }

            if (RingAndPinkyFingerDetected()&& HanddirDetected())
            {
                OnRingAndPinkyDetect.Invoke();
                if (ringandPinkyFirstfinger)
                {
                    ringandPinkyFirstfinger = false;
                    OnRingAndPinkyFingerFirst.Invoke();
                }
            }
            else
            {
                OnRingAndPinkyLost.Invoke();
                ringandPinkyFirstfinger = true;
            }
        }
        else
        {
            OnIndexFingerLost.Invoke();
            indexFirstfinger = true;
            OnMiddleFingerLost.Invoke();
            middleFirstfinger = true;
            OnRingAndPinkyLost.Invoke();
            ringandPinkyFirstfinger = true;
        }
    }

    private bool HanddirDetected()
    {
        var jointedHand = HandJointUtils.FindHand(handType);
        if (jointedHand.TryGetJoint(TrackedHandJoint.Palm,out MixedRealityPose palmPose))
        {
            if ( facingThreshold<Vector3.Angle(palmPose.Up, CameraCache.Main.transform.forward) )
            {
                Debug.Log(palmPose.Up);
                return true;
            }
        }

        return false;
    }


    
    private bool indexfingerDetected()
    {
        var jointedHand = HandJointUtils.FindHand(handType);
        if (jointedHand.TryGetJoint(TrackedHandJoint.Palm,out MixedRealityPose PalmPose))
        {
            //各関節のpose
            MixedRealityPose indexTipPose,indexDistalPose,IndexKnucklePose,indexMiddlePose; 
            if(jointedHand.TryGetJoint(TrackedHandJoint.IndexTip,out indexTipPose)&& jointedHand.TryGetJoint(TrackedHandJoint.IndexDistalJoint,out indexDistalPose)&&jointedHand.TryGetJoint(TrackedHandJoint.IndexMiddleJoint,out indexMiddlePose)&& jointedHand.TryGetJoint(TrackedHandJoint.IndexKnuckle,out IndexKnucklePose))
            {
                Vector3 finger1 = IndexKnucklePose.Position - PalmPose.Position;
                Vector3  finger2 = indexMiddlePose.Position - IndexKnucklePose.Position;
                Vector3 finger3 = indexDistalPose.Position - indexMiddlePose.Position;
                Vector3 finger4 = indexTipPose.Position - indexDistalPose.Position;
                
            float c = Vector3.Angle(PalmPose.Position, finger1);
            float d = Vector3.Angle(finger1, finger2);
            float e = Vector3.Angle(finger2, finger3);
            float f = Vector3.Angle(finger3, finger4);

            float aba = (Mathf.Abs(d) + Mathf.Abs(e) + Mathf.Abs(f)) / 3;

                if (aba < indexThreshold)
                {
                    return true;
                }
            }
        }
        return false;
    }

    private bool middlefingerDetected()
    {
        var jointedHand = HandJointUtils.FindHand(handType);
        if (jointedHand.TryGetJoint(TrackedHandJoint.Palm, out MixedRealityPose PalmPose))
        {
            MixedRealityPose middleTipsPose,middleDistalPose, middleKnucklePose,middleMiddlePose;
            if (jointedHand.TryGetJoint(TrackedHandJoint.MiddleTip, out middleTipsPose) &&
                jointedHand.TryGetJoint(TrackedHandJoint.MiddleDistalJoint, out middleDistalPose) &&
                jointedHand.TryGetJoint(TrackedHandJoint.MiddleKnuckle, out middleKnucklePose)&&jointedHand.TryGetJoint(TrackedHandJoint.MiddleMiddleJoint,out middleMiddlePose))
            {
                Vector3 finger1 = middleKnucklePose.Position - PalmPose.Position;
                Vector3 finger2 = middleMiddlePose.Position - middleKnucklePose.Position;
                Vector3 finger3 = middleDistalPose.Position - middleMiddlePose.Position;
                Vector3 finger4 = middleTipsPose.Position - middleDistalPose.Position;
                
                float c = Vector3.Angle(PalmPose.Position, finger1);
                float d = Vector3.Angle(finger1, finger2);
                float e = Vector3.Angle(finger2, finger3);
                float f = Vector3.Angle(finger3, finger4);

                float aba = (Mathf.Abs(d) + Mathf.Abs(e) + Mathf.Abs(f)) / 3;

                if (aba < middleThreshold)
                {
                    return true;
                }
                
            }
        }

        return false;
    }

    private bool RingAndPinkyFingerDetected()
    {
        //※メモ HoloLens2の場合実装的には薬指、小指それぞれの判定は可能だが、実機の場合特に小指単体のトラッキング精度に問題があるため薬指、小指はひとまとめで一つの指として検知する。
        var jointedHand =HandJointUtils.FindHand(handType);
        if (jointedHand.TryGetJoint(TrackedHandJoint.Palm,out MixedRealityPose PalmPose))
        {
            MixedRealityPose PinkyTipPose,
                PinkyDistalPose,
                PinkyMiddlePose,
                PinkyKnucklePose,
                RingTipPose,
                RingDistalPose,
                RingMiddlePose,
                RingKnucklePose;
            if (jointedHand.TryGetJoint(TrackedHandJoint.RingTip,out RingTipPose)&& jointedHand.TryGetJoint(TrackedHandJoint.RingMiddleJoint ,out RingMiddlePose)&&jointedHand.TryGetJoint(TrackedHandJoint.RingDistalJoint ,out RingDistalPose)
                && jointedHand.TryGetJoint(TrackedHandJoint.RingKnuckle ,out RingKnucklePose)) 
            {
                Vector3 finger1 = RingKnucklePose.Position - PalmPose.Position;
                Vector3 finger2 = RingMiddlePose.Position - RingKnucklePose.Position;
                Vector3 finger3 = RingDistalPose.Position - RingMiddlePose.Position;
                Vector3 finger4 = RingTipPose.Position - RingDistalPose.Position;
                
                float c = Vector3.Angle(PalmPose.Position, finger1);
                float d = Vector3.Angle(finger1, finger2);
                float e = Vector3.Angle(finger2, finger3);
                float f = Vector3.Angle(finger3, finger4);

                float aba = (Mathf.Abs(d) + Mathf.Abs(e) + Mathf.Abs(f)) / 3;

                if (aba < ringAndPinkyFingerThreshpld)
                {
                    return true;
                }
            }
        }

        return false;
    }

    private bool ThumbDetected()
    {
        //※メモ 親指は他の指と異なり特殊でまっすぐ伸びているか?の判定+親指の向きで判定する。
        var jointedHand = HandJointUtils.FindHand(handType);
        //Get TargetHamd
        if (jointedHand.TryGetJoint(TrackedHandJoint.Palm, out MixedRealityPose PalmPose))
        {
            MixedRealityPose ThumbTipPose, ThumbDistalPose, ThumbProximalPose;
            //Get finger Joint Deta
            if (jointedHand.TryGetJoint(TrackedHandJoint.ThumbTip, out ThumbTipPose) &&
                jointedHand.TryGetJoint(TrackedHandJoint.ThumbDistalJoint, out ThumbDistalPose) &&
                jointedHand.TryGetJoint(TrackedHandJoint.ThumbProximalJoint ,out ThumbProximalPose))
            {
                Vector3 finger1 = ThumbProximalPose.Position - PalmPose.Position;
                Vector3 finger2 = ThumbDistalPose.Position - ThumbProximalPose.Position;
                Vector3 finger3 = ThumbTipPose.Position - ThumbProximalPose.Position;

                float c = Vector3.Angle(PalmPose.Position, finger1);
                float d = Vector3.Angle(finger1, finger2);
                float e = Vector3.Angle(finger2, finger3);

                float aba = (Mathf.Abs(d) + Mathf.Abs(e)/ 2);

                if (aba < thumbThreshold)
                {
                    return true;
                }
            }
        }
        return false;
    }

    private void Reset()
    {

        if (this.GetComponent<SolverHandler>() != null)
        {
            handType = this.GetComponent<SolverHandler>().TrackedHandness;
        }        
    }
}