MRが楽しい

MRやVRについて学習したことを書き残す

HoloLensで手の位置を検出する

本日は HoloLens の技術調査枠です。
HoloLensで手の位置を検出する手順を記事にします。
f:id:bluebirdofoz:20190430220049j:plain

プロジェクトとシーンの準備

以下の記事を元にHoloLens(WindowsMR)プロジェクトを作成します。
bluebirdofoz.hatenablog.com

2019/4/30現在、MRTK 2017 の最新バージョンは 2017.4.3.0 です。
f:id:bluebirdofoz:20190430220036j:plain

ハンドトラッキングイベント

手の位置を検出するには ISourcePositionHandler を利用します。
OnPositionChanged 関数で検出した手のIDとPosition情報が取得できます。

HoloLensでは人差し指を立てた状態で手を検出できます。
また、同時に複数の検出が可能です。トラッキングIDを識別することで複数の手の位置を追跡できます。

実際に検出位置を確認するため、手の位置にSphereオブジェクトを表示するスクリプトを作成しました。
・HandPointGetter.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HoloToolkit.Unity.InputModule;

public class HandPointGetter : MonoBehaviour, ISourcePositionHandler
{
    /// <summary>
    /// オブジェクト有効時の実行処理
    /// </summary>
    private void OnEnable()
    {
        // 常にハンドトラッキングのイベントを取得する
        InputManager.Instance.PushFallbackInputHandler(this.gameObject);
    }

    /// <summary>
    /// オブジェクト無効時の実行処理
    /// </summary>
    private void OnDisable()
    {
        if (InputManager.Instance)
        {
            // イベントの取得を無効化する
            InputManager.Instance.PopFallbackInputHandler();
        }
    }

    /// <summary>
    /// 定期実行処理
    /// </summary>
    private void Update()
    {
        // 追加対象のハンドIDがあれば追加処理を実行
        if (createList.Count > 0)
        {
            for (int loop = 0; loop < createList.Count; loop++)
            {
                uint id = createList[loop];
                if (handStates.ContainsKey(id))
                {
                    // Sphereオブジェクトを生成
                    GameObject posObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                    posObject.transform.localScale = new Vector3(0.1f, 0.1f, 0.1f);
                    // オブジェクトの参照をハンドトラッキング情報に追加
                    handStates[id].Object = posObject;
                }
            }
            createList.Clear();
        }
        // 削除対象のハンドIDがあれば削除処理を実行
        if (lostList.Count > 0)
        {
            for (int loop = 0; loop < lostList.Count; loop++)
            {
                uint id = lostList[loop];
                if (handStates.ContainsKey(id))
                {
                    // Sphereオブジェクトを破棄
                    Destroy(handStates[id].Object);
                    handStates.Remove(id);
                }
            }
            lostList.Clear();
        }
        // ロストが正常に検出されない場合があるため時間経過でも削除を行う
        foreach (KeyValuePair<uint, HandState> pair in handStates)
        {
            float lostTime = Time.time - pair.Value.DetectTime;
            if (lostTime > deleteTime)
            {
                uint id = pair.Key;
                // Sphereオブジェクトを破棄
                Destroy(handStates[id].Object);
                handStates.Remove(id);
            }
        }
    }

    // 追加ハンドID
    private List<uint> createList = new List<uint>();
    // 削除ハンドID
    private List<uint> lostList = new List<uint>();
    // 自動削除までの待機時間(秒)
    private float deleteTime = 3.0f;

    /// <summary>
    /// 手毎のハンドトラッキング情報
    /// </summary>
    private Dictionary<uint, HandState> handStates = new Dictionary<uint, HandState>();
    private class HandState
    {
        // 位置情報
        public Vector3 Position { get; set; }
        // オブジェクト参照
        public GameObject Object;
        // 最終検出時刻
        public float DetectTime;
    }

    /// <summary>
    /// ハンドトラッキング情報の取得
    /// </summary>
    /// <param name="id"></param>
    /// <returns></returns>
    private HandState GetHandState(uint id)
    {
        if (handStates.ContainsKey(id))
        {
            // 既に情報がある場合は返却
            return handStates[id];
        }
        else
        {
            // 未検出のハンドIDならば情報を作成
            Debug.Log("GetHandState make: id = " + id);
            HandState handState = new HandState();
            // 更新時刻を登録
            handState.DetectTime = Time.time;
            // ステータスをリスト追加
            handStates.Add(id, handState);
            // 追加リストに登録
            createList.Add(id);
            return handState;
        }
    }

    /// <summary>
    /// ハンド検出イベント
    /// </summary>
    /// <param name="eventData"></param>
    public void OnPositionChanged(SourcePositionEventData eventData)
    {
        HandState getHandState = GetHandState(eventData.SourceId);
        getHandState.Position = eventData.GripPosition;
        if (getHandState.Object != null)
        {
            // ハンドトラッキング情報のオブジェクトの位置情報を更新
            getHandState.Object.transform.position = eventData.GripPosition;
            // 更新時刻を更新
            getHandState.DetectTime = Time.time;
        }
    }

    /// <summary>
    /// ハンドロストイベント
    /// </summary>
    /// <param name="eventData"></param>
    public void OnSourceLost(SourceStateEventData eventData)
    {
        if (handStates.ContainsKey(eventData.SourceId))
        {
            // 既にハンドIDが登録されていれば削除リストに登録
            lostList.Add(eventData.SourceId);
        }
    }
}

新規の EmptyObject を作成し、作成したスクリプトをアタッチします。
f:id:bluebirdofoz:20190430220112j:plain

手の位置にSphereオブジェクトを表示するため、ニアクリップの設定を短めにしておく必要があります。
MixedRealityCameraParent/MixedRealityCamera を開き、NearClip の設定を 0.2 に設定しました。
f:id:bluebirdofoz:20190430220127j:plain

後は HoloLens 向けにプロジェクトをビルドしてインストールします。
UnityプロジェクトのビルドとHoloLensへのインストール手順については以下を参照してください。
bluebirdofoz.hatenablog.com

HoloLensでの動作確認

HoloLens 上で動作確認を行います。
人差し指を立てて手を出すと、その位置にSphereオブジェクトが表示されます。
拳の中心部分辺りの位置が検出されていることが分かります。
f:id:bluebirdofoz:20190430220200j:plain