1. 程式人生 > >手勢識別(一)--手勢基本概念和ChaLearn Gesture Challenge

手勢識別(一)--手勢基本概念和ChaLearn Gesture Challenge

以下轉自:

像點選(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。

image

    互動術語形成規範有幾個障礙。第一個就是為了獲得利益而避免標準化。在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),前臂垂直於後臂和地面。下圖展示了這一姿勢。

image

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

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

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

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

<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private enum </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition
</span></span>{
    None = 0,
    Left = 1,
    Right = 2,
    Neutral = 3
}

<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private enum </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureState
</span></span>{
    None = 0,
    Success = 1,
    Failure = 2,
    InProgress = 3
}

<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">private struct </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureTracker
</span></span>{
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public int </span></span>IterationCount;
    <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGestureState </span></span>State;
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public long </span></span>Timestamp;
    <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition </span></span>StartPosition;
    <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WavePosition </span></span>CurrentPosition;

    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>Reset()
    {
        IterationCount = 0;
        State = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WaveGestureState</span></span>.None;
        Timestamp = 0;
        StartPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None;
        CurrentPosition = <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">WavePosition</span></span>.None;
    }
}

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

當主程式接收到一個新的資料幀時,就呼叫WaveGesture的Update方法。該方法迴圈遍歷每一個使用者的骨骼資料幀,然後呼叫TrackWave方法對左右手進行揮手姿勢識別。當骨骼資料不在追蹤狀態時,重置手勢識別狀態。

<span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public class </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">WaveGesture
</span></span>{
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const float </span></span>WAVE_THRESHOLD = 0.1f;
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>WAVE_MOVEMENT_TIMEOUT = 5000;
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>LEFT_HAND = 0;
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>RIGHT_HAND = 1;
    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private const int </span></span>REQUIRED_ITERATIONS = 4;

    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">private </span></span>WaveGestureTracker[,] _PlayerWaveTracker = <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">new </span></span>WaveGestureTracker[6, 2];

    <span style="background-color: rgb(238, 238, 238);"><span style="margin: 0px; padding: 0px; color: blue;">public event </span><span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);">EventHandler </span></span>GestureDetected;

    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">public void </span></span>Update(<span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton</span></span>[] skeletons, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">long </span></span>frameTimestamp)
    {
        <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeletons != <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">null</span></span>)
        {
            <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">Skeleton </span></span>skeleton;

            <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">for </span></span>(<span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">int </span></span>i = 0; i < skeletons.Length; i++)
            {
                skeleton = skeletons[i];

                <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">if </span></span>(skeleton.TrackingState != <span style="margin: 0px; padding: 0px; color: rgb(43, 145, 175);"><span style="background-color: rgb(238, 238, 238);">SkeletonTrackingState</span></span>.NotTracked)
                {
                    TrackWave(skeleton, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">true</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref this</span></span>._PlayerWaveTracker[i, LEFT_HAND], frameTimestamp);
                    TrackWave(skeleton, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">false</span></span>, <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">ref this</span></span>._PlayerWaveTracker[i, RIGHT_HAND], frameTimestamp);
                }
                <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238, 238);">else
                </span></span>{
                    <span style="margin: 0px; padding: 0px; color: blue;"><span style="background-color: rgb(238, 238,