6
1

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.

Oculus Quest, ハンドトラッキングにおけるハンドサイン認識

Last updated at Posted at 2020-02-09

#目的
Questのハンドトラッキングでなんかしたい

#手法
とりあえず指が曲がってるかどうかでハンドサインを認識してみる.
ボーンの内積掛けて指がどれだけ曲がってるかを閾値判定

以下二つのコードはOVRSkeltonが付いてるオブジェクトに付けてください.
まず, 指の内積計算するコード

HandBoneDot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System;

public class HandBoneDot : MonoBehaviour
{
	public List<BoneDot> _boneDots;
	public List<FingerDot> _fingerDots;
	public enum FingerIndex { Thumb, Index, Middle, Ring, Pinky }
	private OVRSkeleton _ovrSkeleton;
	private bool _isInitialized;
	int[,] BoneDirectionIndex = {
	{ (int)OVRSkeleton.BoneId.Hand_Thumb1,(int)OVRSkeleton.BoneId.Hand_Thumb2, (int)OVRSkeleton.BoneId.Hand_Thumb3 },
	{ (int)OVRSkeleton.BoneId.Hand_Thumb2,(int)OVRSkeleton.BoneId.Hand_Thumb3, (int)OVRSkeleton.BoneId.Hand_ThumbTip },//Thumb
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Index1, (int)OVRSkeleton.BoneId.Hand_Index2 },
	{ (int)OVRSkeleton.BoneId.Hand_Index1,(int)OVRSkeleton.BoneId.Hand_Index2, (int)OVRSkeleton.BoneId.Hand_Index3 },
	{ (int)OVRPlugin.BoneId.Hand_Index2,(int)OVRSkeleton.BoneId.Hand_Index3, (int)OVRSkeleton.BoneId.Hand_IndexTip },//Index
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Middle1, (int)OVRSkeleton.BoneId.Hand_Middle2 },
	{ (int)OVRSkeleton.BoneId.Hand_Middle1,(int)OVRSkeleton.BoneId.Hand_Middle2, (int)OVRSkeleton.BoneId.Hand_Middle3 },
	{ (int)OVRSkeleton.BoneId.Hand_Middle2,(int)OVRSkeleton.BoneId.Hand_Middle3, (int)OVRSkeleton.BoneId.Hand_MiddleTip },//Middle
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Ring1, (int)OVRSkeleton.BoneId.Hand_Ring2 },
	{ (int)OVRSkeleton.BoneId.Hand_Ring1,(int)OVRSkeleton.BoneId.Hand_Ring2, (int)OVRSkeleton.BoneId.Hand_Ring3 },
	{ (int)OVRSkeleton.BoneId.Hand_Ring2,(int)OVRSkeleton.BoneId.Hand_Ring3, (int)OVRSkeleton.BoneId.Hand_RingTip },//Ring
	{ (int)OVRPlugin.BoneId.Hand_WristRoot,(int)OVRSkeleton.BoneId.Hand_Pinky1, (int)OVRSkeleton.BoneId.Hand_Pinky2 },
	{ (int)OVRSkeleton.BoneId.Hand_Pinky1,(int)OVRSkeleton.BoneId.Hand_Pinky2, (int)OVRSkeleton.BoneId.Hand_Pinky3 },
	{ (int)OVRSkeleton.BoneId.Hand_Pinky2,(int)OVRSkeleton.BoneId.Hand_Pinky3, (int)OVRSkeleton.BoneId.Hand_PinkyTip }//Pinky
	};
	public class BoneDot
	{
		Transform BoneBegin, BoneMiddle, BoneEnd;
		public float dot;
		public BoneDot(Transform begin, Transform middle, Transform end)
		{
			BoneBegin = begin;
			BoneMiddle = middle;
			BoneEnd = end;

			this.Update();
		}
		public void Update()
		{
			dot = Vector3.Dot((BoneMiddle.position - BoneBegin.position).normalized, (BoneEnd.position - BoneMiddle.position).normalized);
		}
	}
	public class FingerDot
	{
		BoneDot[] bones;
		public float dot;
		public FingerDot(params BoneDot[] bone)
		{
			bones = bone;
		}
		public void Update()
		{
			dot = 1;
			for (int i = 0; i < bones.Length; i++)
				dot *= bones[i].dot;
		}
	}
	private void Awake()
	{
		if (_ovrSkeleton == null)
		{
			_ovrSkeleton = GetComponent<OVRSkeleton>();
		}
	}

	private void Start()
	{
		if (_ovrSkeleton == null)
		{
			this.enabled = false;
			return;
		}

		Initialize();
	}

	private void Initialize()
	{
		_boneDots = new List<BoneDot>();
		_fingerDots = new List<FingerDot>();
		_ovrSkeleton = GetComponent<OVRSkeleton>();

		for (int i = 0; i < BoneDirectionIndex.Length / 3; i++)
		{
			var boneVis = new BoneDot(
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 0]].Transform,
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 1]].Transform,
				_ovrSkeleton.Bones[BoneDirectionIndex[i, 2]].Transform);

			_boneDots.Add(boneVis);
		}

		for (int i = 0; i < Enum.GetNames(typeof(FingerIndex)).Length; i++)
		{
			FingerDot Fing;
			if (i == 0)
				Fing = new FingerDot(_boneDots[0], _boneDots[1]);
			else
				Fing = new FingerDot(_boneDots[2 + 3 * (i - 1)], _boneDots[3 + 3 * (i - 1)], _boneDots[4 + 3 * (i - 1)]);
			_fingerDots.Add(Fing);
		}

		_isInitialized = true;
	}

	public void Update()
	{
		if (_isInitialized)
		{
			for (int i = 0; i < _boneDots.Count; i++)
				_boneDots[i].Update();
			for (int i = 0; i < _fingerDots.Count; i++)
				_fingerDots[i].Update();
		}
	}
}

ハンドサイン認識のコード
閾値付近でハンドサインがバタつかないようにsignには多少補正をかけてある.
補正をかけてない値が欲しい場合はHandSignNumを参照する.
HandSignNumは親指を一桁目とした二進数になっている.
例えば親指だけ立っているとき00001(2)=1(10),ピースサインの場合は人差し指と中指が立ってるので00110(2)=6(10)といった具合.
一つ下のEnumも同様に0~31で表される.

HandSignDetector.cs
using UnityEngine;
using System.Collections;

public class HandSignDetector : MonoBehaviour
{
	[Range(-1, 1)] public float[] FingerBendThrethold = new float[5] { 0.85f, 0, 0, 0, 0 };
	public int HandSignNum;
	public HandSign_Bend sign;
	public float SignEndWaitTime = 0.2f;
	bool IsPosing = false, PoseChangeWaiting = false;
	HandBoneDot handBone;
	void Start()
	{
		handBone = GetComponent<HandBoneDot>();
		if (handBone == null)
			return;
		HandSignNum = 0;
		for (int i = 0; i < handBone._fingerDots.Count; i++)
			if (handBone._fingerDots[i].dot > FingerBendThrethold[i])
				HandSignNum += (int)Mathf.Pow(2, i);
		UpdateSignInit();
	}
	void Update()
	{
		HandSignNum = 0;
		for (int i = 0; i < handBone._fingerDots.Count; i++)
			if (handBone._fingerDots[i].dot > FingerBendThrethold[i])
				HandSignNum += (int)Mathf.Pow(2, i);

		if (!IsPosing)
			UpdateSignInit();
		else if (HandSignNum != (int)sign && !PoseChangeWaiting && IsPosing)
			StartCoroutine(PoseEndWait(SignEndWaitTime));
	}
	void UpdateSignInit()
	{
		sign = (HandSign_Bend)System.Enum.ToObject(typeof(HandSign_Bend), HandSignNum);
		IsPosing = true;
	}
	IEnumerator PoseEndWait(float waitime)
	{
		float t = Time.time;
		PoseChangeWaiting = true;
		while (Time.time < t + waitime)
		{
			if (HandSignNum == (int)sign)
			{
				PoseChangeWaiting = false;
				yield break;
			}
			yield return null;
		}

		IsPosing = false;
		PoseChangeWaiting = false;
	}
}

以下ハンドサイン一覧用のEnum
HandSign_Bend.csの方は思いつくハンドサインの名前を入れたEnum. 思いつかなかったところは立ってる指の名前を羅列してある.
HandSign_BendRaw.csの方は指の名前の羅列のみ.

HandSign_Bend.cs
public enum HandSign_Bend
{
	Fist,
	ThumbsUp,
	Point,
	Gun,
	Fuck,
	ThumbMiddle,
	Peace,
	ThumbIndexMiddle,
	Ring,
	ThumbRing,
	IndexRing,
	ThumbIndexRing,
	MiddleRing,
	ThumbMiddleRing,
	Three,
	ThumbIndexMiddleRing,
	Lover,
	Phone,
	DevilsHorn,
	Spidey,
	MiddlePinky,
	ThumbMiddlePinky,
	IndexMiddlePinky,
	ThumbIndexMiddlePinky,
	RingPinky,
	ThumbRingPinky,
	IndexRingPinky,
	ThumbIndexRingPinky,
	MiddleRingPinky,
	ThumbMiddleRingPinky,
	Four,
	OpenHand
}
HandSign_BendRaw.cs
public enum HandSign_BendRaw
{
	AllBend,
	Thumbs,
	Index,
	ThumbIndex,
	Middle,
	ThumbMiddle,
	IndexMiddle,
	ThumbIndexMiddle,
	Ring,
	ThumbRing,
	IndexRing,
	ThumbIndexRing,
	MiddleRing,
	ThumbMiddleRing,
	IndexMiddleRing,
	ThumbIndexMiddleRing,
	Pinky,
	ThumbPinky,
	IndexPinky,
	ThumbIndexPinky,
	MiddlePinky,
	ThumbMiddlePinky,
	IndexMiddlePinky,
	ThumbIndexMiddlePinky,
	RingPinky,
	ThumbRingPinky,
	IndexRingPinky,
	ThumbIndexRingPinky,
	MiddleRingPinky,
	ThumbMiddleRingPinky,
	IndexMiddleRingPinky,
	ThumbIndexMiddleRingPinky
}

#結果

#まとめ
割とうまくいってる.

6
1
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
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?