程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Kinect for Windows SDK開發入門(十)手勢識別 上:基本概念

Kinect for Windows SDK開發入門(十)手勢識別 上:基本概念

編輯:關於.NET

像點擊(clicks)是GUI平台的核心,輕點(taps)是觸摸平台的核心那樣,手勢(gestures)是Kinect應用程序的核心。和圖形用戶界面中的數字交互不同,手勢是現實生活中存在的動作。如果沒有電腦我們就不需要鼠標,但是沒了Kinect,手勢依然存在。從另一方面講,手勢是日常生活中人與人之間相互交流的一部分。手勢能夠增強演講的說服力,能夠用來強調和傳遞情感。像揮手(waving)或者指向(pointing)這些手勢都是某種無聲的演講。

Kinect應用程序的設計和開發者的任務就是將這些現實生活中存在的手勢映射到計算機交互中去以傳達人的想法。嘗試從鼠標或觸摸式的GUI設計移植基於手勢的自然交互界面要做很多工作。借鑒過去30多年來對於這一概念的研究,以及從一些Kinect for Xbox的體感游戲中獲取一些設計理念,計算機工程師和交互設計師一起為Kinect創建了一系列新的手勢庫。

本文將會介紹用戶體驗的一些知識,並討論如何將手勢應用到Kinect應用程序中。我們將展示Kinect如何作為自然交互界面(Natural User Interface)的人機交互模型的一部分。我們將討論一些具體的使用Kinect來進行手勢識別及交互的例子。更重要的是,將會展示一些已經作為Kinect手勢識別庫中的手勢。

1. 什麼是手勢

在許多不同的學科中,手勢(gesture)有著其獨特的含義,可能這些含義之間有某些異同。在藝術領域,手勢被用來傳達舞蹈中最富表現力的部分,特別是在亞洲舞蹈藝術中,手勢被作為某些宗教符號或者象征。在交互設計領域,在基於觸摸的自然交互界面中手勢和操控有很大區別。

以上這些說明手勢在不同的學科領域都有自己獨特的含義。在學術領域都試圖對手勢定義一個抽象的概念。在用戶體驗設計領域使用最廣泛的關於手勢的定義實在Eric Hulteen 和Gord Kurtenbach 1990年發表的一篇名為人機交互中的手勢(Gestures in Human-Computer Communication),定義如下:”手勢是身體的運動,他包含一些信息。揮手道別是一種手勢。敲擊鍵盤不是手勢,因為用手指的運動去敲擊按鍵沒有被觀察,也不重要,他只表達的鍵盤被按下這一動作。(A gesture is a motion of the body that contains information. Waving goodbye is a gesture. Pressing a key on a keyboard is not a gesture because the motion of a finger on its way to hitting a key is neither observed nor significant. All that matters is which key was pressed)”

這個定義既解釋了什麼是手勢也解釋了什麼不是手勢。像這樣的下一個正式的定義通常有兩個方面的困難,既要避免太具體也要避免太抽象。如果一個定義太具體-如,定義某項技術-可能會隨著UI技術的變化會變得模糊不清。作為一種學術定義而不是以常見的用法為基礎的定義,它也必須足夠一般,並且符合或者說廣大的研究機構先前已發表在HCI的研究成果及藝術中符號學。另一方面,定義過於寬泛,也會有有無關緊要的風險:如果一切都是一種姿態,那麼就什麼都不是了。

Eric Hulteen 和Gord Kurtenbach關於手勢的定義的中心在於手勢能夠用來交流,手勢的意義在於講述而不是執行。

有趣的是將語言和行為引入到人機交互接口中來,這是一種徹底的變革。我們與計算機交互語音變為無聲的語言(mute):我們通過指向和手勢而不是語言與計算設備進行溝通。當和計算機進行交互時,我們點擊鍵盤按鍵或觸摸屏幕。我們似乎更喜歡這種形式的靜音通信即使當前的技術能夠支持更簡單的語音指令。我們沒有操作(manipulation)的力量,和虛擬的對象而不是真實的物體進行交互,因而沒有持久性。運動成為純粹的手勢。

基於Eric Hulteen 和Gord Kurtenbach的定義,我們都明白什麼是 UI 操作 ——暫時不是一種手勢 ——理解什麼是手勢以及手勢表示"重大"行為或者符號仍然有很大的困難。移動交互的含義是什麼?手勢進行溝通和語言進行溝通的最明顯不同是什麼?我們做手勢的象征意義往往很抽象簡單。

在人機交互領域,手勢通常被作為傳達一些簡單的指令而不是交流某些事實、描述問題或者陳述想法。使用手勢操作電腦通常是命令式的,這通常不是人們使用手勢的目的。例如,揮手(wave)這一動作,在現實世界中通常是打招呼的一種方式,但是這種打招呼的方式在人機交互中卻不太常用。通常第一次寫程序通常會顯示“hello”,但我們對和電腦打招呼並不感興趣。

但是,在一個繁忙的餐館,揮手這一手勢可能就有不同的含義了。當向服務員招收時,可能是要引起服務員注意,需要他們提供服務。在計算機中,要引起計算機注意有時候也有其特殊意義,比如,計算機休眠時,一般都會敲擊鍵盤或者移動鼠標來喚醒,以提醒計算機“注意”。當使用Kinect時,可以使用更加直觀的方式,就行少數派報告阿湯哥那樣,抬起雙手,或者簡單的朝計算機揮揮手,計算機就會從休眠狀態喚醒。

在人機交互領域,手勢通常有一些含義,表示有意讓某些事情發生。手勢是一種指令。當通過鼠標或者觸控板去點擊UI界面上的按鈕時,我們希望按鈕會觸發其背後的事件。通常,按鈕上會有一個標簽來指示按鈕的功能如:開始、取消、打開、關閉。我們的手勢操作就是想要實現這些事件。

上面的定義中的第一點可以得出,手勢的另一個特點是比較隨意(arbitrary)。手勢有限定的領域,那麼在該領域之外沒有任何意義。令人驚訝的是除了指向(pointing)和聳肩(shurg),人類學家沒有發現任何東西我們可以稱之為一種通用的手勢。然而,在計算機的UI中,指向(pointing)通常被認為是直接操作因為它牽涉到跟蹤,同時聳肩的含義太微妙而不好辨識。因此,我們想要使用的任何Kinect手勢必須基於應用程序的用戶 和應用程序的設計和開發者之間就某種手勢代表的含義達成一致。

因為手勢是任意的(arbitrary)所以他們也是基於約定的(conventional)。應用程序的設計者必須告訴用戶正在使用的手勢的意義,或者是這些手勢是約定俗稱大家都知道的。此外,這些約定不是基於語言文化,而是對已確定的技術規則。我們知道如何使用鼠標 (行為學習) 並不是因為這是我們已經從我們的文化導入的東西,而是因為這是基於特定的圖形用戶界面的跨文化約定。同樣地,我們知道如何點擊或滑動智能手機,不是因為這些都是文化的約定,而是因為這些都是跨文化自然用戶界面項約定。有趣的是,我們在一定程度上知道如何點擊平板電腦,因為我們以前學習了如何使用鼠標單擊。技術約定之間可以相互轉化,這是因為語言和手勢可以通過不同的語言和文化之間來轉換。

然而,手勢的這種任意性和基於約定的特性也帶來了誤解性(misunderstanding),這是在設計任何用戶界面,尤其是像Kinect這樣的沒有任何預先設定好的操作約定的用戶界面時需要關注的風險。就像在有些國家,點頭表示否定搖頭表示可能。手勢,或者任何身體的運動,都有可能產生誤解。

總之,在人機交互領域,手勢是:

表達一種簡單的命令

天生有隨意性

基於某種協定

可能被誤解

注意:實際的直接操作(manipulation)不是手勢。

2. 自然交互界面(NUI)

討論手勢而不討論自然用戶界面顯然不完整。自然用戶界面是一系列技術的合計,他包括:語音識別,多點觸控以及類似Kinect的動感交互界面,他和Windows和Macs操作系統中鼠標和鍵盤交互這種很常見圖形交互界面不同。就像圖像交互界面和之前的命名行交互界面不同那樣。

自然交互界面自然在哪兒呢?早期自然交互界面的發起者認為交互界面的設計應該對用戶非常直觀,使用用戶先天就會的行為來進行交互操作。他的目標是不需要操作由圖標和菜單構成的基於GUI 的應用程序界面,因為這種界面通常具有陡峭的學習曲線。相反,理想化的狀態是,用戶應該能夠走到應用程序前面,就能夠開始使用它。在過去的幾年裡隨著觸摸功能的智能手機和平板電腦的流行,逐漸取代了鍵盤鼠標,當我們看到孩子們開始走到任何觸摸屏設備面前,用手去觸摸它,期待它的響應,在這一點上看這一理念已經實現。

雖然自然用戶界面的自然性似乎是直接操作的最佳寫照,當使用手指來進行觸摸交互時,先天自然和後天學習行為之間的對立被打破。一些手勢,如輕觸屏幕,在某種意義上就是先天就會的動作。其他的動作比如說雙擊,獲得點擊然後拖拉等,沒有先天就會。而且隨著不同的設備制造商開始支持不同觸摸手勢,為了使得相同的手勢在不同的觸摸平台上有相同的意義和行為,為某些手勢定義一些約定顯得更加重要。

自然用戶界面(NUI)的自然性更多的是一種相對自然的概念。對於NUI的更現代的理解受Bill Buxton所影響。他認為NUI界面的設計充分利用了用戶預先就會的技能,用戶和UI進行交互感到很自然,使得他們甚至忘了是從哪裡學到這些和UI進行交互所需的技能的。換句話說,第一次操作時,我們不記得我們曾經學過這些知識。例如,輕點(tap)這個手勢早平板電腦和手機中使用的很頻繁,這個技能是從我們之前在傳統的人機交互界面上使用鼠標來指向並點擊某一個界面上的元素學來的。點擊(click)和輕點(tap)的最主要區別在於,點擊需要鼠標,對於觸摸屏,不需要額外的設備,只需要用手指輕輕觸摸一下屏幕就可以了。

這引出了自然用戶界面的另一個特點。用戶和計算機之間的交互看起來不需要任何媒介,這種相互作用的媒介是不可見的。例如,在語音識別界面中,人機交互是通過具有復雜電子過濾去噪的麥克風實現的,其內部有解析發音語義單元的各種算法,將這些語義傳遞給其它軟件來進行將特定的短語解釋為命令,並將該命令映射到某種軟件功能操作。但是,內部的這一切,對用戶是不可見的。當用戶對計算機發出這樣的命令,"嘿,注意我",她會認為計算機會像類似大多數人的本能那樣的響應這個命令。

自然用戶界面的 依賴於先驗知識和不需要媒介的交互這兩個特征是每一種NUI界面的共同特征,其他方面如觸摸,語音和動態交互界面則因設備的不同而各異。目前,大多數關於NUI的設計都是基於多點觸控體驗的。這就是為什麼前面對於手勢的標准定義是那樣定義的。它是將多點觸摸的場景進行修改並將手勢和操作區分開來。

關於手勢(gesture)和操作(manipulation)的爭論也存在於語音交互界面中,命令等同於手勢,語音等同於直接操作,在動態交互界面中,將手或者身體追蹤展示在可視化界面上手和身體的運動等同於直接操作。自由形式的運動像揮手這一動作就屬於手勢。

但是Kinect還有第三種交互界面,他和觸摸和語音交互不同。那就上一篇文章中所講的姿勢(pose),姿勢是身體的某一部分和其他部分之間的一種靜態關系,他不是運動的。Kinect中的姿勢和日常生活中的姿勢是一樣的,例如,左臂伸出45度表示將當前的窗口變為活動的交互窗體,右臂伸出45度或者135度表示垂直滾動工具欄。

另外,交互方式可以從一種類型的交互界面轉換到另外一種交互界面。以按鈕為例,按鈕其實就是一個符號,這是一個先驗的圖形用戶界面。從最基本的功能來講,按鈕就是一個通過鼠標點擊在一個可視化元素的文字或者圖像上觸發一些命令的工具。在過去15年,按鈕被作為人機交互界面的一個集成部分,被轉換到多點觸摸界面,以及Kinect用戶界面中來。

自然用戶界面設計師所追求的是的是自然,按鈕恰好提供了這一點。但是按鈕在每一種用戶界面中的轉換都面臨著一些挑戰。

圖形用戶界面中按鈕的一個通常的特征是他提供了一個懸浮狀態來指示用戶光標已經懸停在的按鈕上方的正確位置。這種懸浮狀態將點(click)這個動作離散開來。懸浮狀態可以為按鈕提供一些額外的信息。當將按鈕移植到觸摸屏交互界面時,按鈕不能提供懸浮狀態。觸摸屏界面只能響應觸摸。因此,和電腦上的圖像用戶界面相比,按鈕只能提供“擊”(click)操作,而沒有“點”(point)的能力。

當將按鈕移植到基於Kinect的用戶界面上時,按鈕的行為就變得更加特殊了。基於Kinect的圖形界面中,按鈕的行為和觸摸界面中的剛好相反,他只提供了懸浮(hover)的“點”(point)的能力,沒有“擊”(click)的能力。按鈕這種更令用戶體驗設計者感到沮喪的弱點,在過去的幾年裡,迫使設計者不斷的對Kinect界面上的按鈕進行改進,以提供更多巧妙的方式來點擊視覺元素。這些改進包括:懸停在按鈕上一段時間、將手掌向外推(笨拙地模仿點擊一個按鈕的行為)等。

雖然觸摸界面也有手勢,但Kinect 界面有些互動不是手勢,不過軟件的開發和設計者傾向於以 Kinect 手勢操作作為交互界面。這似乎是因為使用手勢作為物理操作是 Kinect 應用程序的最大的特點。與此相反的是,觸摸界面的突出特點是直接操作。雖然可能不准確,人們通常將自然交互界面劃分為三類:語音交互界面,觸摸交互界面和手勢交互界面。

然而,在關於Kinect的相關介紹文檔中,你會發現有時候姿勢(pose)和操作(manipulation)都被描述為手勢。這些都沒有錯。要記住的是,當我們討論Kinect中的一些術語,如揮手(wave),滑動(swipe),我們會作為純粹的手勢,而姿勢和操控只有在隱喻意義上才稱之為手勢。

以上的討論都很重要,因為我們會進一步設計Kinect互動的語意,我們將最終移除從其他圖形界面上借鑒過來的關於按鈕的語意,然後嘗試建立基於Kinect的先驗的語意。揮手(wave)這是Kinect中純粹的手勢,是最早的這種嘗試。喬治亞技術研究所的研究人員正在利用 Kinect 來解釋美國手語。相反,其他研究人員,正在利用 Kinect 解釋身體語言——另一種預先形成的手勢和姿勢的溝通。諸如此類的研究可以視為對於NUI的第二層研究。這些逐漸接近了最初NUI人機交互的原始的夢想,不只是看不見,而且NUI能夠自適應以理解我們的行為,而不是迫使我們了解我們和電腦的人機交互。

3. 手勢從哪裡來

在手勢交互界面中,純粹的手勢,姿勢和追蹤以及他們之間的組合構成了交互的基本術語。對於Kinect來說,目前可以使用的有8個通用的手勢:揮手(wave),懸浮按鈕(hover button),磁吸按鈕(magnet button),推按鈕(push button),磁吸幻燈片(magnetic slide),通用暫停(universal pause),垂直滾動條(vertical scrolling)和滑動(swipping)。其中的一些術語是微軟自己引入的,有一些是游戲代理商設計的,還有一些是Kinect for PC開發人員為了開發應用而引入的。

很少情況下會為人際交互界面術語進行定制。通常要將這8種手勢區分開來,並在一些應用中通用也不常見。相似的情況在web術語和手機手勢中設計新的界面時也會遇到,其中只有部分的設計能夠變成標准。在網頁設計領域,走馬燈和光標動畫流行一時,並在一片鄙夷聲中迅速消失。在手機設計領域由於蘋果公司在觸摸屏領域的早期地位這種術語得到了很好的規范。蘋果引入了一些觸摸手勢術語,如輕點(tap),點住不放(tap and hold),滑動swipe及pinch。

交互術語形成規范有幾個障礙。第一個就是為了獲得利益而避免標准化。在90年代後期的浏覽器大戰中,盡管各大廠商在口頭上說標准化協議很重要,但是在浏覽器開發上依舊不停的開發自己的HTML版本,以吸引開發者使用他們的技術。設備制造商可以利用市場占有率的優勢來鎖定消費者,通過在他們的手機上實現自己定義語意的觸屏,來推行自己的手勢操作。這些都是不自然的行為,因為不同廠商對於同一手勢的語意都不同,並且他們看起來不自然,使用不同廠商的產品需要再學習。

另一種形成規范化的障礙是上下文手勢的專利。例如,蘋果公司不能對“滑動”(swipe)操作申請專利,但是它可以對“滑動解鎖手機”這個手勢申請專利,這使得其他公司需要使用這一技術或者設計理念時要麼給蘋果公司支付專利費,要麼將蘋果告上法庭以避免專利費,或則干脆不使用這一上下文手勢。如果不使用這一上下文手勢,那麼產品就破壞了之前我們學習到使用很自然的方式滑動解鎖手機,音樂播放器,平板電腦等這一約定了。

最後一個障礙是,設計一個手勢很困難。手勢術語會面對一些App Store中手機應用程序和YouTube中視頻應用所遇到的一些問題:人們要麼會要麼不會。手勢需要思考如何定義的簡單使得人們能夠去用,這就是長尾理論留下來的問題。

那麼什麼樣的手勢術語才是好的呢。如果一個手勢易於使用,那麼他就被認為是設計良好的。在交互設計中,易用性有兩個方面:可用(affordance)和反饋(feedback)。反饋就是說用戶知道當前正在進行的操作。在網頁中,點擊按鈕會看到按鈕有一點偏移,這就表示交互成功。鼠標按鍵按下時的聲音在某種意義上也是一種反饋,他表示鼠標在工作。對於Winodw Phone Metro風格的界面上的磁貼,開發這認為這些按鈕應該足夠大,以容下大面積的觸摸區域,但是他們也認為過大的觸摸區域會使得用戶觸摸到區域外面也會觸發注冊的事件。另外,狀態信息或者確認對話框會在應用程序中彈出以提示用戶發生了一些事情。在 Xbox 的儀表板中,使用Kinect傳感器產生的光標懸停在的熱點上開始動畫播放。

如果說反饋發生在操作進行中或者之後,那麼可用性(affordance)就發生在操作之前了。可用性就是一種提示或者引導,告訴用戶某一個可視化元素是可以交互的,指示用戶該元素的用處。在GUI交互界面中,按鈕是能夠最好的完成這些理念的元素。按鈕通過文字或者圖標提示來執行一些函數操作。GUI界面上的按鈕通過懸浮狀態可以提示用戶其用途。最好的可用性-可能有點繞圈-就是約定俗成。用戶知道某一個可視化元素的用途,因為之前在其他應用中使用過類似的可視化控件,或者是在其他設備中執行過類似的操作。但是,這一點對於基於Kinect的手勢交互界面來說有點困難,因為一切都是新的。

通常的做法就是使用借用其他類型交互界面中的約定。在觸摸交互界面中,一個輕點(tap)手勢和通常的鼠標點擊是等同的。響應輕點事件的兩個可視化元素,圖標和按鈕,也被設計的和傳統的GUI界面上的圖標和按鈕一樣,來達到提示用戶該元素的作用這一目的。Kinect也使用按鈕和圖標來使得用戶能夠更加容易使用。因為Kinect基本上是基於”點”(pointing)而原生不支持“擊”(clicking)。在此之前,軟件界面設計者和開發者的花費了很多精力來對手勢交互界面進行定制以實現“擊”這一動作。

和觸摸交互界面不一樣,手勢交互界面可以從社會中人的一般手勢中借用一些手勢操作。這就使得揮手(wave)成為Kinect應用程序的經典手勢。因為這一姿勢和現實生活中的姿勢有象征性聯系使得非常容易理解和使用。軌跡追蹤,雖然在技術上不是手勢,但是他是另一個在現實生活中和指向有聯系的術語。當在電視機或者顯示器前揮動手時,好的Kinect應用程序應該能夠追蹤到手的運動,並顯示一個光標隨著手一起運動。當我們在現實生活中指向物體時,Kinect中的手部追蹤顯示的手形圖標的反饋使得程序更加易用。

目前,現實生活中的易用性手勢在Kinect交互界面中用的比較少,大部分的易用性都是從傳統的GUI界面上的可用性移植過來的。隨著時間的改變,這一點會得到改善。在觸摸屏設備上新的手勢通過在傳統的已經建立的約定中添加手指來形成。兩指輕點和一指輕點有些不同,使用兩個手指或者多個手指進行滑動有其獨特的含義。最終,觸摸手勢全部由手指完成。另一方面,真正的手勢用戶界面,有一個近乎無限的語意庫,使得我們可以基於現實生活中相關聯的手勢進行改進。

本文接下來從理論到實現,討論如何實現手勢識別,並展示了Kinect中八中基本手勢中的揮手(wave)手勢的識別。

4. 實現手勢識別

Microsoft Kinect SDK並沒有包含手勢識別引擎。因此需要開發者來定義和手勢識別。從SDK的Beta版放出以來,一些第三方開發者創建的手勢引擎已初見端倪。但是,微軟沒有將他們作為標准的引擎。看來這可能還要等微軟將手勢識別引擎添加到SDK中來,或者指明可替代的解決方案。本節對手勢識別技術進行了簡單介紹,希望能夠幫助開發者在標准的手勢識別引擎出來之前,可以自己動手開發手勢識別引擎。

手勢識別相對來說可以簡單也可以很復雜,這取決與要識別的手勢。有三種基本的方法可以用來識別手勢:基於算法,基於神經網絡和基於手勢樣本庫。每一種方法都有其優缺點。開發者具體采用那種方法取決與待識別的手勢、項目需求,開發時間以及開發水平。基於算法的手勢識別相對簡單容易實現,基於神經網絡和手勢樣本庫則有些復雜。

4.1 基於算法的手勢識別

算法是解決軟件開發中幾乎所有問題的最基本方法。使用算法的基本流程就是定義處理規則和條件,這些處理規則和條件必須符合處理結果的要求。在手勢識別中,這種算法的結果要求是一個二值型對象,某一手勢要麼符合預定的手勢要麼不符合。使用算法來識別手勢是最基本的方法,因為對於有一點編程能力的開發這來說,手勢識別的代碼易於理解,編寫,維護和調試。

但是,最簡單直接的方法也有其缺點。算法的簡單性限制了其能識別到的手勢的類別。對於揮手(wave)識別較好的算法不能夠識別扔(throw)和擺(swing)動作。前者動作相對簡單和規整,後者則更加細微且多變。可能能夠寫一個識別擺動(swing)的算法,但是代碼可能比較費解和脆弱。

算法還有一個內在的擴展性問題。雖然一些代碼可以重用,但是每一種手勢必須使用定制的算法來進行識別。隨著新的手勢識別算法加入類庫,類庫的大小會迅速增加。這就對程序的性能產生影響,因為需要使用很多算法來對某一個手勢進行識別以判斷該手勢的類型。

最後,每一個手勢識別算法需要不同的參數,例如時間間隔和阈值。尤其是在依據流程識別特定的手勢的時候這一點顯得尤其明顯。開發者需要不斷測試和實驗以為每一種算法確定合適的參數值。這本身是一個有挑戰也很乏味的工作。然而每一種手勢的識別有著自己特殊的問題。

4.2 基於神經網絡的手勢識別

當用戶在做手勢時,手勢的形式並不總是足夠清晰到能夠判斷用戶的意圖。例如跳躍手勢,跳躍手勢就是用戶短暫的跳起來,腳離開地面。這個定義不能夠提供足夠的信息來識別這一動作。

咋一看,這個動作似乎足夠簡單,使得可以使用算法來進行識別。首先,考慮到有很多種不同形式的跳躍:基本跳躍(basic jumping)、 跨欄(hurdling)、 跳遠(long jumping)、 跳躍(hopping),等等。但是這裡有一個大的問題就是,由於受到Kinect視場區域的限制,不可能總是能夠探測到地板的位置,這使得腳部何時離開地板很難確定。想象一下,用戶在膝蓋到下蹲點處彎下,然後跳起來。手勢識別引擎應該認為這是一個手勢還是多個手勢:下蹲或 下蹲跳起或者是跳起?如果用戶在蹲下的時間和跳躍的時間相比過長,那麼這一手勢可能應被識別為下蹲而不是跳躍。

看到這些,最開始對跳躍的定義就會變得模糊。這一姿勢很難定義清楚,使得不能夠通過定義一些算法來進行識別,同時這些算法由於需要定義過多的規則和條件而變得難以管理和不穩定。使用對或錯的二值策略來識別用戶手勢的算法太簡單和不夠健壯,不能夠很好的識別出類似跳躍,下蹲等動作。

神經網絡的組織和判斷是基於統計和概率的,因此使得像識別手勢這些過程變得容易控制。基於什麼網絡的手勢識別引擎對於下蹲然後跳躍動作,80%的概率判斷為跳躍,10%會判定為下蹲。

除了能夠識別復雜和精細的手勢,神經網絡方法還能解決基於算法手勢識別存在的擴展性問題。神經網絡包含很多神經元,每一個神經元是一個好的算法,能夠用來判斷手勢的細微部分的運動。在神經網絡中,許多手勢可以共享神經元。但是每一中手勢識別有著獨特的神經元的組合。而且,神經元具有高效的數據結構來處理信息。這使得在識別手勢時具有很高的效率。

使用基於神經網絡進行手勢識別的缺點是方法本身復雜。雖然神經網絡以及在計算機科學中對其的應用已經有了好幾十年,建立一個好的神經網絡對於大多數程序員來說還是有一些困難的。大多數開發者可能對數據結構中的圖和樹比較熟悉,而對神經網絡中尺度和模糊邏輯的實現可能一點都不了解。這種缺乏建立神經網絡的經驗是一個巨大的困難,即使能夠成功的構建一個神經網絡,程序的調試相當困難。

和基於算法的方法相比,神經網絡依賴大量的參數來能得到精確的結果。參數的個數隨著神經元的個數增長。每一個神經元可以用來識別多個手勢,每一個神經遠的參數的變化都會影響其他節點的識別結果。配置和調整這些參數是一項藝術,需要經驗,並沒有特定的規則可循。然而,當神經網絡配對機器學習過程中手動調整參數,隨著時間的推移,系統的識別精度會隨之提高。

4.3 基於樣本的識別

基於樣本或者基於模版的手勢識別系統能夠將人的手勢和已知的手勢相匹配。用戶的手勢在模板庫中已經規范化了,使得能夠用來計算手勢的匹配精度。有兩種樣本識別方法,一種是存儲一系列的點,另一種方法是使用類似的Kinect SDK中的骨骼追蹤系統。在後面的那個方法中,系統中包含一系列骨骼數據和景深幀數據,能夠使用統計方法對產生的影像幀數據進行匹配以識別出已知的幀數據來。

這種手勢識別方法高度依賴於機器學習。識別引擎會記錄,處理,和重用當前幀數據,所以隨著時間的推移,手勢識別精度會逐步提高。系統能夠更好的識別出你想要表達的具體手勢。這種方法能夠比較容易的識別出新的手勢,而且較其他兩種方法能夠更好的處理比較復雜的手勢。但是建立這樣一個系統也不容易。首先,系統依賴於大量的樣本數據。數據越多,識別精度越高。所以系統需要大量的存儲資源和CPU時間的來進行查找和匹配。其次系統需要不同高度,不同胖瘦,不同穿著(穿著會影響景深數據提取身體輪廓)的樣本來進行某一個手勢。

5.識別常見的手勢

選擇手勢識別的方法通常是依賴於項目的需要。如果項目只需要識別幾個簡單的手勢,那麼使用基於算法或者基於神經網絡的手勢識別就足夠了。對於其他類型的項目,如果有興趣的話可以投入時間來建立可復用的手勢識別引擎,或者使用一些人家已經寫好的識別算法,接下來本文介紹幾種常用的手勢,並演示如何使用算法的方法來識別他們,手勢識別的另外兩種方法由於其復雜性本文不做介紹。

不論選擇哪種手勢識別的方法,都必須考慮手勢的變化范圍。系統必須具有靈活性,並允許某一個手勢有某個范圍內的變動。很少有人能夠每次都做一模一樣的手勢。例如,考慮周伯通當前左右手畫圓圈這個手勢,重復這一手勢10次,圓形的中心每次都在一個點嗎,圓形的起點和重點每次都在相同的地方嗎?每次畫圓的時長都一樣嗎?然後使用右手做這個動作,最後比較結果。或者拉幾個朋友或者家人來做,然後觀察。也可以站在鏡子前面看自己做,或者使用錄像設備錄下來再看。技巧就是對於某一手勢,讓盡可能多的人來做,然後試圖標准化這一手勢。手勢識別一個比較好的方式就是關注手勢最核心的部分而不是哪些外在的細枝末節。

5.1 揮動(wave)手勢

只要玩過Xbox上的體感游戲,可能都使用過揮手這個手勢。揮手這一手勢不論年齡大小都能夠做的一個簡單動作。這是一個友好的,快樂的手勢,人們通常揮手或者招手來打招呼或者道別。在應用開發的上下文中,揮手手勢通常告訴應用程序已經准備好了,可以開始體驗應用了。

揮手是最簡單最基本的手勢。使用算法方法能夠很容易識別這一手勢,但是之前講到的任何方法也能夠使用。雖然揮手是一個很簡單的手勢,但是如何使用代碼來識別這一手勢呢?讀者可以在鏡子前做向自己揮手,然後仔細觀察手的運動,尤其注意觀察手和胳膊之間的關系。繼續觀察手和胳膊之間的關系,然後觀察在做這個手勢事身體的整個姿勢。有些人保持身體和胳膊的不動,使用手腕左右移動來揮手。有些人保持身體和胳膊不動使用手腕前後移動來揮手。可以通過觀察這些姿勢來了解其他各種不同揮手的方式。

XBOX中的揮手動作定義為:從胳膊開始到肘部彎曲。用戶以胳膊肘為焦點來回移動前臂,移動平面和肩部在一個平面上,並且胳膊和地面保持平行,在手勢的中部(下圖1),前臂垂直於後臂和地面。下圖展示了這一姿勢。

從圖中可以觀察得出一些規律,第一個規律就是,手和手腕都是在肘部和肩部之上的,這也是大多是揮手動作的特征。這也是我們識別揮手這一手勢的第一個標准。

第一幅圖展示了揮手這一姿勢的中間位置,前臂和後臂垂直。如果用戶手臂改變了這種關系,前臂在垂直線左邊或者右邊,我們則認為這是該手勢的一個片段。對於揮手這一姿勢,每一個姿勢片段必須來回重復多次,否則就不是一個完整的手勢。這一運動規律就是我們的第二個准則:當某一手勢是揮手時,手或者手腕,必須在中間姿勢的左右來回重復特定的次數。使用這兩點通過觀察得到的規律,我們可以通過算法建立算法准則,來識別揮動手勢了。

算法通過計算手離開中間姿勢區域的次數。中間區域是一個以胳膊肘為原點並給予一定阈值的區域。算法也需要用戶在一定的時間段內完成這個手勢,否則識別就會失敗。這裡定義的揮動手勢識別算法只是一個單獨的算法,不包含在一個多層的手勢識別系統內。算法維護自身的狀態,並在識別完成時以事件形式告知用戶識別結果。揮動識別監視多個用戶以及兩雙手的揮動手勢。識別算法計算新產生的每一幀骨骼數據,因此必須記錄這些識別的狀態。

下面的代碼展示了記錄手勢識別狀態的兩個枚舉和一個結構。第一個名為WavePosition的枚舉用來定義手在揮手這一動作中的不同位置。手勢識別類使用WaveGestureState枚舉來追蹤每一個用戶的手的狀態。WaveGestureTracker結構用來保存手勢識別中所需要的數據。他有一個Reset方法,當用戶的手達不到揮手這一手勢的基本動作條件時,比如當手在胳膊肘以下時,可調用Reset方法來重置手勢識別中所用到的數據。

private enum WavePosition
{
    None = 0,
    Left = 1,
    Right = 2,
    Neutral = 3
}
    
private enum WaveGestureState
{
    None = 0,
    Success = 1,
    Failure = 2,
    InProgress = 3
}
    
private struct WaveGestureTracker
{
    public int IterationCount;
    public WaveGestureState State;
    public long Timestamp;
    public WavePosition StartPosition;
    public WavePosition CurrentPosition;
    
    public void Reset()
    {
        IterationCount = 0;
        State = WaveGestureState.None;
        Timestamp = 0;
        StartPosition = WavePosition.None;
        CurrentPosition = WavePosition.None;
    }
}

下面代碼顯示了手勢識別類的最基本結構:它定義了五個常量:中間區域阈值,手勢動作持續時間,手勢離開中間區域左右移動次數,以及左手和右手標識常量。這些常量應該作為配置文件的配置項存儲,在這裡為了簡便,所以以常量聲明。WaveGestureTracker數組保存每一個可能的游戲者的雙手的手勢的識別結果。當揮手這一手勢探測到了之後,觸發GestureDetected事件。

當主程序接收到一個新的數據幀時,就調用WaveGesture的Update方法。該方法循環遍歷每一個用戶的骨骼數據幀,然後調用TrackWave方法對左右手進行揮手姿勢識別。當骨骼數據不在追蹤狀態時,重置手勢識別狀態。

public class WaveGesture
{
    private const float WAVE_THRESHOLD = 0.1f;
    private const int WAVE_MOVEMENT_TIMEOUT = 5000;
    private const int LEFT_HAND = 0;
    private const int RIGHT_HAND = 1;
    private const int REQUIRED_ITERATIONS = 4;
    
    private WaveGestureTracker[,] _PlayerWaveTracker = new WaveGestureTracker[6, 2];
    
    public event EventHandler GestureDetected;
    
    public void Update(Skeleton[] skeletons, long frameTimestamp)
    {
        if (skeletons != null)
        {
            Skeleton skeleton;
    
            for (int i = 0; i < skeletons.Length; i++)
            {
                skeleton = skeletons[i];
    
                if (skeleton.TrackingState != SkeletonTrackingState.NotTracked)
                {
                    TrackWave(skeleton, true, ref this._PlayerWaveTracker[i, LEFT_HAND], frameTimestamp);
                    TrackWave(skeleton, false, ref this._PlayerWaveTracker[i, RIGHT_HAND], frameTimestamp);
                }
                else
                {
                    this._PlayerWaveTracker[i, LEFT_HAND].Reset();
                    this._PlayerWaveTracker[i, RIGHT_HAND].Reset();
                }
            }
        }
    }
}

下面的代碼是揮手姿勢識別的主要邏輯方法TrackWave的主體部分。它驗證我們先前定義的構成揮手姿勢的條件,並更新手勢識別的狀態。方法識別左手或者右手的手勢,第一個條件是驗證,手和肘關節點是否處於追蹤狀態。如果這兩個關節點信息不可用,則重置追蹤狀態,否則進行下一步的驗證。

如果姿勢持續時間超過阈值且還沒有進入到下一步驟,在姿勢追蹤超時,重置追蹤數據。下一個驗證手部關節點是否在肘關節點之上。如果不是,則根據當前的追蹤狀態,揮手姿勢識別失敗或者重置識別條件。如果手部關節點在Y軸上且高於肘部關節點,方法繼續判斷手在Y軸上相對於肘關節的位置。調用UpdatePosition方法並傳入合適的手關節點所處的位置。更新手關節點位置之後,最後判斷定義的重復次數是否滿足,如果滿足這些條件,揮手這一手勢識別成功,觸發GetstureDetected事件。

private void TrackWave(Skeleton skeleton, bool isLeft, ref WaveGestureTracker tracker, long timestamp)
{
    JointType handJointId = (isLeft) ? JointType.HandLeft : JointType.HandRight;
    JointType elbowJointId = (isLeft) ? JointType.ElbowLeft : JointType.ElbowRight;
    Joint hand = skeleton.Joints[handJointId];
    Joint elbow = skeleton.Joints[elbowJointId];
    
    
    if (hand.TrackingState != JointTrackingState.NotTracked && elbow.TrackingState != JointTrackingState.NotTracked)
    {
        if (tracker.State == WaveGestureState.InProgress && tracker.Timestamp + WAVE_MOVEMENT_TIMEOUT < timestamp)
        {
            tracker.UpdateState(WaveGestureState.Failure, timestamp);
            System.Diagnostics.Debug.WriteLine("Fail!");
        }
        else if (hand.Position.Y > elbow.Position.Y)
        {
            //使用 (0, 0) 作為屏幕的中心.  從用戶的視角看, X軸左負右正.
            if (hand.Position.X <= elbow.Position.X - WAVE_THRESHOLD)
            {
                tracker.UpdatePosition(WavePosition.Left, timestamp);
            }
            else if (hand.Position.X >= elbow.Position.X + WAVE_THRESHOLD)
            {
                tracker.UpdatePosition(WavePosition.Right, timestamp);
            }
            else
            {
                tracker.UpdatePosition(WavePosition.Neutral, timestamp);
            }
    
    
            if (tracker.State != WaveGestureState.Success && tracker.IterationCount == REQUIRED_ITERATIONS)
            {
                tracker.UpdateState(WaveGestureState.Success, timestamp);
                System.Diagnostics.Debug.WriteLine("Success!");
    
                if (GestureDetected != null)
                {
                    GestureDetected(this, new EventArgs());
                }
            }
        }
        else
        {
            if (tracker.State == WaveGestureState.InProgress)
            {
                tracker.UpdateState(WaveGestureState.Failure, timestamp);
                System.Diagnostics.Debug.WriteLine("Fail!");
            }
            else
            {
                tracker.Reset();
            }
        }
    }
    else
    {
        tracker.Reset();
    }
}

查看本欄目

下面的代碼添加到WaveGestureTracker結構中:這些幫助方法維護結構中的字段,使得TrackWave方法易讀。唯一需要注意的是UpdatePosition方法。TrackWave調用該方法判斷手的位置已經移動。他的最主要目的是更新CurrentPosition和Timestamp屬性,該方法也負責更新InterationCount字段合InPorgress狀態。

public void UpdateState(WaveGestureState state, long timestamp)
{
    State = state;
    Timestamp = timestamp;
}
    
public void Reset()
{
    IterationCount = 0;
    State = WaveGestureState.None;
    Timestamp = 0;
    StartPosition = WavePosition.None;
    CurrentPosition = WavePosition.None;
}
    
public void UpdatePosition(WavePosition position, long timestamp)
{
    if (CurrentPosition != position)
    {
        if (position == WavePosition.Left || position == WavePosition.Right)
        {
            if (State != WaveGestureState.InProgress)
            {
                State = WaveGestureState.InProgress;
                IterationCount = 0;
                StartPosition = position;
            }
    
            IterationCount++;
        }
    
        CurrentPosition = position;
        Timestamp = timestamp;
    }
}

上述代碼片段就可以實現揮動(wave)手勢識別的邏輯了。

6. 結語

本文主要介紹了手勢識別中設計的基本概念以及手勢識別的發展過程,在此基礎上介紹了手勢識別的三種基本方法:基於算法的手勢識別、基於神經網絡的手勢識別和基於樣本庫的手勢識別。最後以8中常用手勢的中揮手(wave)介紹了使用算法進行手勢識別的基本步驟,並給出了相應的代碼,限於篇幅原因,就先介紹到這裡,下篇文章,將會介紹手勢識別中手部捕捉類庫和余下的7中手勢的識別方法,由於本文大部分講述的是概念,所以本部分源代碼將和下篇文章中的源代碼一同發布,敬請期待!

希望本文對您熟悉Kinect SDK中的手勢識別有所幫助!

作者:   yangecnu(yangecnu's Blog on 博客園)

出處:http://www.cnblogs.com/yangecnu/

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved