今天突然想查一下關於unity的手勢識別的資料,由於前段時間瀏覽器收藏的所有網址都丟失了,不敢信任,所以打算以后把所有資料放到博客中。
此篇說的是unity通過厲動(Leap Motion)進行手勢識別,不過沒有寫過只是看(因為沒有時間。。。。),轉自 https://blog.csdn.net/u012289636/article/details/46883731,以下全是從這個鏈接復制粘貼過來的。
Leap Motion作為一款手勢識別設備,相比於Kniect,優點在於精確度。
在我的畢業設計《場景漫游器》的開發中,Leap Motion的手勢控制作為重要的一個環節。以此,談談開發中使用Leap Motion進行手勢識別的實現方式以及需要注意的地方。
一、對Leap Motion的能力進行評估
在設定手勢之前,我們必須知道Leap Motion能做到哪種程度,以免在設定方案之后發現很難實現。這個評估依靠實際對設備的使用體驗,主要從三個方面:
1.Leap Motion提供的可視化的手勢識別界面
2.SDK文檔說明
3.Leap商店中的APP
基本可以的得出:
1.Leap Motion的識別對於水平方向或者以水平方向為基礎手勢能夠較好的識別。
2.對於握拳或者垂直的行為識別會出現誤差,這種誤差和具體的手勢行為有關。
3.不應該過分依賴高精確度,Leap Motion能檢測到毫米級別是沒錯的,但是有時候會把你伸直的手指識別成彎曲的,所以要做好最壞的打算。
二、實際的需要
移動、旋轉、點擊按鈕、縮放和旋轉物體、關閉程序、暫停,基本的功能需求是這樣。
有一些原則:
1.相同環境下的手勢應該接近和方便的轉換。旋轉和移動的之間的轉換應該設計的很自然。
2.手勢避免沖突,手勢過於相似不是什么好事。比如三個伸直的手指和四個伸直的手指不應該被設計成兩個手勢。當然這不是絕對的,如果你進行一個緩慢的動作並且動作是面向Leap Motion的攝像頭,這時候應該相信它,至少要針對這個手勢做一個單獨的測試。
三、考慮基本的數據結構和算法的輪廓
Leap Motion的SDK在第一部分的時候已經瀏覽過,最起碼能知道Leap Motion可以包含的信息,從SDK看來這是非常豐富的,既然設計自己的手勢,那么最好不要依賴於SKD開發包的炫酷的手勢。很可能,這些手勢只是官方用來演示或者炫耀的。自己設計手勢的基本數據結構也有另外的好處,比如更換了體感設備,但是功能是相似的,這時候只需要更改獲取數據的方式就可以了(從一個SDK更換到另一個SDK),而不要修改算法。
算法的輪廓與基本數據有很大的關系。所以數據結構一定要盡量的精簡並且允許修改(可能某個算法占據了決定性因素,但是開始沒考慮到)。
- public class HandAndFingersPoint : MonoBehaviour
- {
- const int BUFFER_MAX=5;
- Controller m_LeapCtrl;
-
- <span style="white-space:pre"> </span>public E_HandInAboveView m_AboveView = E_HandInAboveView.None;
-
-
- private Dictionary<Finger.FingerType,FingerData>[] m_FingerDatas = new Dictionary<Finger.FingerType, FingerData>[2];
-
- private Dictionary<Finger.FingerType,FingerData>[,] m_FingerDatasBuffer=new Dictionary<Finger.FingerType, FingerData>[2,BUFFER_MAX];
- private int m_CurBufIndex=0;
-
- private PointData[] m_PalmDatas = new PointData[2];
-
- private readonly PointData m_DefaultPointData = new PointData(Vector.Zero, Vector.Zero);
- private readonly FingerData m_DefaultFingerData = new FingerData(Vector.Zero,Vector.Zero,Vector.Zero);
HandAndFingersPoint類中剩下的部分是對數據的填充、清除、刷新等方法。E_HandInAboveView記錄哪只手先進入Leap Motion的視野,用於設定優先級。
另外兩個基本的數據結構PointData和FingerData:
- public struct FingerData
- {
- public PointData m_Point;
- public Vector m_Position;
-
- public FingerData(PointData pointData, Vector pos)
- {
- m_Point = pointData;
- m_Position = pos;
- }
-
- public FingerData(Vector pointPos, Vector pointDir, Vector pos)
- {
- m_Point.m_Position = pointPos;
- m_Point.m_Direction = pointDir;
- m_Position = pos;
- }
-
- public void Set(FingerData fd)
- {
- m_Point = fd.m_Point;
- m_Position = fd.m_Position;
- }
- }
- public struct PointData
- {
- public Vector m_Position;
- public Vector m_Direction;
-
- public PointData(Vector pos,Vector dir)
- {
- m_Position = pos;
- m_Direction = dir;
- }
-
- public void Set(PointData pd)
- {
- m_Position = pd.m_Position;
- m_Direction = pd.m_Direction;
- }
-
- public void Set(Vector pos,Vector dir)
- {
- m_Position = pos;
- m_Direction = dir;
- }
- }
-
- public enum E_HandInAboveView
- {
- None,
- Left,
- Right
- }
基本數據定義好之后,最好確認數據的填充是沒問題的,實際通過Frame frame = Leap.Controller.Frame();來獲取最新的數據。這時候並不急着寫完和基本數據相關的方法,現在最終要的是手勢算法的合理性。要判斷是否合理,最好先寫一個算法。
最簡單的是伸掌手勢,在控制中水平的伸掌用於漫游,垂直的伸掌用於暫停。我發現手掌依賴於手指,而手指包括兩個狀態——伸直和彎曲。另外,其他的手勢,也都是手指的伸直或者彎曲,外加方向的判定累積出各種效果。理所當然的,應該單獨寫出手指的彎曲和伸直判定算法:
- public class FingerMatch
- {
-
- static readonly float FingerBendState_Radian = Mathf.PI*4f / 18 ;
-
- static readonly float FingerStrightState_Radian = Mathf.PI/12;
-
-
-
-
-
-
-
- public static bool StrightState(FingerData fingerData, float adjustBorder=0f)
- {
- bool isStright =false;
- Vector disalDir = fingerData.m_Point.m_Direction;
-
- if (!disalDir.Equals(Vector.Zero))
- {
- Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;
- float radian = fingerDir.AngleTo(disalDir);
-
- if (radian < FingerStrightState_Radian + adjustBorder)
- {
- isStright = true;
- }
- }
- return isStright;
- }
-
-
-
-
-
-
-
- public static bool BendState(FingerData fingerData, float adjustBorder=0f)
- {
- bool isBend = false;
-
-
- Vector disalDir = fingerData.m_Point.m_Direction;
- if( !disalDir.Equals(Vector.Zero) )
- {
- Vector fingerDir = fingerData.m_Point.m_Position - fingerData.m_Position;
-
- float radian = fingerDir.AngleTo(disalDir);
-
-
- if (radian > FingerBendState_Radian + adjustBorder)
- {
- isBend = true;
- }
- }
-
- return isBend;
- }
-
- }
上面包含了一個重要的概念——閾值。它是描述到底何種程度算是伸直,何種程度算是彎曲。閾值的確定是需要實際測試來決定的。寫到這里也是時候進行一次簡單的測試了,畢竟算法的輪廓已經確定。我甚至沒寫出手掌伸直的判定算法,就確定是可行的。
基本數據結構相關的操作——HandAndFingersPoint類:https://github.com/LoranceChen/Leap-Motion-In-Unity3D
該類使用基本數據,在Unity Editor中運行會展示了一個手掌的輪廓,藍色表示手指的方向,紅色表示手指骨根到掌心和指尖的連線,黃色表示掌心到指尖的連線:

四、手勢實現中簡要的概括
其他代碼都可以在我的GitHub:Leap Motion In Unity3D倉庫中(https://github.com/LoranceChen/Leap-Motion-In-Unity3D)獲取,在手勢的實現中,也包含了一些小的技巧,比如對於動作的匹配要防止手指的顫抖引起的誤差,采用離散的數據取樣——每隔一定時間做一次取樣。
使用和觀察這些腳本的方式:可以把這些腳本放在一個GameObject中,通過Leap Motion會看到腳本的屬性在匹配成功時會發生變化。另外,腳本中包含了事件的注冊功能,換句話說,外部可以向任意的手勢注冊一個事件,以便手勢完成匹配或者到達某種匹配狀態時做一些額外的處理。這些腳本現在並不能直接完成我們的需求,如暫停。我們需要在這些手勢狀態或者動作上做進一步的限定,如根據掌心的方向設定垂直向前的手掌為暫停,水平的手掌為平移之類的。