關注Leap Motion很長時間了,很早就想入手。可是,一方面,一直忙着其它的比賽,沒時間顧及;二是缺錢,錢都墊在比賽上了。
好不容易,11月18日,下定決心買進了,這么長時間,也就是再給貴陽職業學院的學生上課的時候顯擺了一次。
周末休息,總算是強迫自己擺弄一下Leap Motion了。
那么做點什么呢?不放就先從簡單的開始吧,就在窗口上顯示一個紅色的圓,以表示追蹤到的某一個手指頭(指尖)。
要開發Leap Motion應用,我覺得官方的文檔必須要仔細的看看。
文檔1:開發者首頁,這里可以下載SDK。
文檔2:理解C#例程,以便開發我們自己的應用程序。
文檔3:Leap概述,獲取開發的必要知識。
Step1:下載SDK,並解壓縮。在“LeapSDK”中有我們需要用的源代碼和類庫。
Step2:創建一個C# WPF應用程序,不妨就叫“LeapMotion1_WPF”。
Step3:在項目中添加“lib/LeapCSharp.NET4.0.dll”引用(如果是.net framework 3.5的話,則添加LeapCSharp.NET3.5.dll),如圖。

Step4:在項目中添加現有項,“lib/x86/Leap.dll”、“lib/x86/Leap.lib”和“lib/x86/LeapCSharp.dll”三個文件,並設置三個文件“始終復制”到輸出目錄,如圖。


Step5:創建用戶界面,在MainWindow上,放一個Canvas,兩個Button,一個Ellipse,如圖。
設置好相應的屬性。Canvas的大小和窗口一樣。
Step6:編寫“連接設備”按鈕的單擊事件。在該事件中,我們需要讓Leap Motion工作起來。不放看看“samples/Sample.cs”中是如何實現的。
該類實現的是一個控制台應用程序。我們從main開始看。代碼如下:
1 public static void Main () 2 { 3 // Create a sample listener and controller 4 SampleListener listener = new SampleListener (); 5 Controller controller = new Controller (); 6 7 // Have the sample listener receive events from the controller 8 controller.AddListener (listener); 9 10 // Keep this process running until Enter is pressed 11 Console.WriteLine ("Press Enter to quit..."); 12 Console.ReadLine (); 13 14 // Remove the sample listener when done 15 controller.RemoveListener (listener); 16 controller.Dispose (); 17 }
文檔2中說,“The Controller class provides the main interface between the Leap and your application. When you create a Controller object, it connects to the Leap software running on the computer and makes hand tracking data available through Frame objects. You can access these Frame objects by instantiating a Controller object and calling the Controller.Frame method”,即應用程序使用Controller對象來訪問Leap硬件,追蹤到的數據封裝在Frame對象中的,這個對象可以通過Controller.Frame獲取。
文檔2中還說,“If your application has a natural update loop or frame rate, then you can call Controller.Frame as part of this update. Otherwise, you can add a listener to the controller object. The controller object invokes the callback methods defined in your Listener subclass whenever a new frame of tracking data is available (and also for a few other Leap events)”。如果開發的應用程序本身有個大循環的話(類似XNA應用程序的update,或是單片機程序的loop),可以在這個大循環中通過Controller.Frame獲取Frame對象。否則,我們可以給Controller對象提供一個Listener,當Controller准備好新的Frame時會回調Listener,之后我們在進行處理。
這樣來看,前三行代碼就比較好理解了。
在我們的項目中,“連接設備”按鈕的單擊事件的代碼如下:
1 private void Button_Click_1(object sender, RoutedEventArgs e) 2 { 3 listener = new MyLeapListener(); 4 controller = new Controller(); 5 controller.AddListener(listener); 6 7 btn1.IsEnabled = false;//btn1表示“連接設備” 8 btn2.IsEnabled = true;//btn2表示“斷開設備” 9 }
Step7:“斷開設備”的點擊事件的代碼自然也比較容易寫出,代碼如下:
1 private void Button_Click_2(object sender, RoutedEventArgs e) 2 { 3 controller.RemoveListener(listener); 4 5 btn1.IsEnabled = true; 6 btn2.IsEnabled = false; 7 }
Step8:仿照例程的Main方法,我們還需要銷毀Controller對象。如下:
1 private void Window_Closing_1(object sender, System.ComponentModel.CancelEventArgs e) 2 { 3 controller.Dispose(); 4 }
Step9:實現自己的監聽器。先來看看SampleListener是如何實現的呢?其骨架如下。
1 class SampleListener : Listener 2 { 3 public override void OnInit (Controller controller) 4 { 5 } 6 7 public override void OnConnect (Controller controller) 8 { 9 } 10 11 public override void OnDisconnect (Controller controller) 12 { 13 } 14 15 public override void OnExit (Controller controller) 16 { 17 } 18 19 public override void OnFrame (Controller controller) 20 { 21 } 22 }
同樣是在文檔2中有詳細的說明,我就不粘貼原文了。我們自己創建的監聽器需要繼承Listener,可以根據需要重寫上面幾個方法。對於我們而言,需要關注的是OnFrame方法,因為當一個新的Frame准備好后會調用這個方法。
在LeapMotion1_WPF命名空間中添加一個新類MyLeapListener,其實現如下:
1 class MyLeapListener : Listener 2 { 3 public override void OnFrame(Controller ctl) 4 { 5 } 6 }
Ok,非常好。我們可以在OnFrame方法中獲得Frame對象,然后獲取關於指尖的數據,然后進行一些操作,就如例程中那樣。但是,在這個方法中,我們是無法改變Ellipse的位置的。我們需要多做一步操作,在MyLeapListener的OnFrame方法觸發的時候,通知調用者進行處理。顯然,使用事件可以完美的解決我們的問題。於是,我們修改了MyLeapListener的代碼:
1 class MyLeapListener : Listener 2 { 3 public event EventHandler OnFrameEvent = null; 4 public override void OnFrame(Controller ctl) 5 { 6 if (OnFrameEvent != null) 7 { 8 OnFrameEvent.Invoke(ctl, null); 9 } 10 } 11 }
在OnFrame方法中觸發OnFrameEvent事件。那么,相應的,在實例化MyLeapListener的時候,就應該加一個事件處理方法進去。在Button_Click_1方法中作如下修改:
1 private void Button_Click_1(object sender, RoutedEventArgs e) 2 { 3 listener = new MyLeapListener(); 4 listener.OnFrameEvent += listener_OnFrameEvent; 5 6 controller = new Controller(); 7 controller.AddListener(listener); 8 9 btn1.IsEnabled = false; 10 btn2.IsEnabled = true; 11 }
Step10:實現listener_OnFrameEvent方法。先把方法聲明寫出來。
1 void listener_OnFrameEvent(object sender, EventArgs e) 2 { 3 }
按照之前的說法,追蹤的數據是封裝在Frame對象中的,因此,我們首先獲取該對象。
1 //獲取幀數據 2 LeapFrame frame = controller.Frame();//using LeapFrame = Leap.Frame;
再往下就需要用到文檔3的知識了。Leap可以同時檢測到若干只手,並用Hand對象封裝數據,建議最多2只手進入Leap的檢測區域。需要注意的是Leap無法區分左右手。我們判斷一下Leap是否檢測到手,如果檢測到的話,就獲取第一個。
1 //如果能夠獲取手部數據 2 if (!frame.Hands.IsEmpty) 3 { 4 //獲取手部數據 5 Hand hand = frame.Hands[0]; 6 }
有了Hand對象,我們就能夠知道手相對於Leap的位置。
1 //獲取手部的位置,判斷檢測的范圍 2 LeapVector palmPosition = hand.PalmPosition;//using LeapVetcor = Leap.Vector; 3 float palmHeight = palmPosition.y;
這里的高度為什么是y,不是z?文檔3中有介紹的啊。Leap的坐標系如下圖所示。

我們要在窗口中繪制Ellipse表示指尖在Leap檢測區域中的位置,Leap檢測的坐標是毫米,窗口繪制的時候是像素,這就涉及到了坐標的轉換。
我曾看到一篇文章,其說Leap的檢測角度為150度,因此,在不考慮z軸的情況下,我們可以計算出來手所在的高度檢測的寬度是多少,並將指尖的位置映射到窗口上。文檔3說明了有效的檢測區域是設備上方25毫米-600毫米的范圍,故可以將指尖的高度映射到窗口上。

1 float detectionWidth = (float)(palmHeight * Math.Tan(75.0 / 180.0 * Math.PI) * 2);
有了手的數據,我們接下來獲取指尖的信息。Leap檢測時,並不是每一幀都包括指尖的信息,例如當握拳時就無法得到指尖的數據。所以,在使用指尖數據時,我們需要判斷一下。
1 //獲取指尖的數據 2 FingerList fingers = hand.Fingers; 3 if (!fingers.IsEmpty) 4 { 5 Finger f1 = fingers[0]; 6 }
獲取指尖的位置數據,並計算其在窗口上的位置。
1 LeapVector position = f1.TipPosition; 2 3 float x = position.x; 4 float y = position.y; 5 6 //下面將x、y映射到屏幕上 7 double screenWidth = canvas1.ActualWidth; 8 double screenHeight = canvas1.ActualHeight; 9 10 x = x / detectionWidth * screenWidth + (screenWidth / 2); 11 y = screenHeight - y / (600 – 25) * screenHeight;
最后,需要設置Ellipse的位置。
1 this.Dispatcher.BeginInvoke(new Action(delegate { 2 Canvas.SetLeft(ellipse, x); 3 Canvas.SetTop(ellipse, y); 4 }), null);
注意,不能直接調用Canvas.SetLeft會出錯的。
好了,運行程序,看看效果吧。
注意:
1、using Leap;
2、注意代碼的縮進。
