上一篇文章,我們實現了Leap Motion的簡單測試。追蹤其中一個手指並用紅色圓形表示其在空間的位置。
這篇文章,我們來實現五指的追蹤。
其實,能夠實現一指的追蹤,那么五指的追蹤自然不成問題。但是,還是有幾個問題我們需要考慮一下。
1、並不是每一幀都會包含五指的全部信息。
比如,當前幀包含了五指信息,那么,窗口上就會顯示五個紅色圓。如果此時用戶握拳,那么,下一幀就可能只會有一指的信息。此時,就應從窗口中移除多余的四個紅色圓。
2、手指如何和紅色圓對應。
因為Hand.Fingers集合對應的不一定是拇指、食指、中指、無名指、小指(可能對應的是小指、無名指、中指),所以,得想個辦法把某個指尖和某個紅色圓對應起來。幸好,Leap為每個對象都定義了ID。這樣,我們就可以將指尖的ID和紅色圓綁定在一起。自然地,我們會想到用Dictionary<int, Ellipse>。
還有一點,假設上一幀檢測到了拇指(id為5)、這一幀沒檢測到拇指,而下一幀又檢測到了拇指,那么,它的id可能是5,但也有可能不是5。
3、如何刪除上一幀有的而這一幀中沒有的紅色圓。
這個問題相對簡單,做一個List<int>,把這一幀中id一次加進去,然后,再從Dictionary<int, Ellipse>的Keys里面刪除那些不在List<int>中的id所對應的紅色圓。
OK,大部分問題都有了思路,那么,我們開始寫代碼吧。記得,一定要先看看上一篇文章啊。
Step1:構造下面的用戶界面。
Step2:聲明MyLeapListener類和窗口Closing事件。代碼和LeapMotion(1)中的一樣。
Step3:添加成員變量Dictionary<int, Ellipse>表示手指ID和紅色圓的對應,添加成員變量List<int>表示當前幀追蹤到的手指編號。代碼如下:
1 private Dictionary<int, Ellipse> ellipses; 2 private List<int> fingerIds;
Step4:編寫“連接設備”的單擊事件和“斷開設備”的單擊事件。與之前不同的是,在“連接設備”的單擊事件中,需要初始化ellipses成員變量,在“斷開設備”的單擊事件中,需要清空ellipses成員變量。
1 private void connect_device_button_Click(object sender, RoutedEventArgs e) 2 { 3 listener = new MyLeapListener(); 4 listener.OnFrameEvent += listener_OnFrameEvent; 5 controller = new Controller(); 6 controller.AddListener(listener); 7 8 connect_device_button.IsEnabled = false; 9 disconnect_device_button.IsEnabled = true; 10 11 ellipses = new Dictionary<int, Ellipse>(); 12 fingerIds = new List<int>(); 13 } 14 15 private void disconnect_device_button_Click(object sender, RoutedEventArgs e) 16 { 17 controller.RemoveListener(listener); 18 19 connect_device_button.IsEnabled = true; 20 disconnect_device_button.IsEnabled = false; 21 22 ellipses.Clear(); 23 }
Step5:編寫OnFrameEvent事件。還是先放上事件聲明。
1 void listener_OnFrameEvent(object sender, EventArgs e) 2 { 3 4 }
和之前一樣,在事件中,我們首先要獲取追蹤到的手部的信息。
1 LeapFrame frame = controller.Frame();//獲取當前幀 2 if (!frame.Hands.IsEmpty)//判斷是否追蹤到手部 3 { 4 Hand hand = frame.Hands.FirstOrDefault();//獲取追蹤到的第一只手 5 LeapVector palmPosition = hand.PalmPosition;//獲取手部位置 6 float palmHeight = palmPosition.y; 7 float detectionWidth = (float)(palmHeight * Math.Tan(75.0 / 180.0 * Math.PI) * 2);//計算當前高度的檢測寬度 8 9 //將要放下面的代碼 10 11 }
接下來,就需要找到追蹤到的每一個指尖(是指尖,而不是筆之類的東西歐)。
1 foreach (Finger finger in hand.Fingers.Where(f => f.IsFinger)) 2 { 3 //將要放下面的代碼 4 }
獲取指尖id放入List<int>,然后判斷Dictionary<int, Ellipse>中是否有指定id對應的ellipse。代碼如下:
1 //獲取指尖ID,放入List<int> 2 fingerIds.Add(finger.Id); 3 4 Ellipse ellipse = null; 5 if (ellipses.ContainsKey(finger.Id))//如果在Dictionary<int, Ellipse>中有,則用ellipse表示其 6 { 7 ellipse = ellipses[finger.Id]; 8 } 9 else//Dictionary<int, Ellipse>中不存在,則創建一個ellipse 10 { 11 this.Dispatcher.Invoke(new Action(delegate 12 { 13 ellipse = new Ellipse(); 14 ellipse.Width = 10; 15 ellipse.Height = 10; 16 ellipse.Fill = Brushes.Red;//10x10大小的紅色圓 17 ellipses.Add(finger.Id, ellipse); 18 container_canvas.Children.Add(ellipse); 19 }), null); 20 }
然后,就是在Canvas中設置ellipse的位置了。代碼比較簡單(和上一篇中的代碼類似),如下:
1 //設置ellipse的位置 2 LeapVector position = finger.TipPosition; 3 4 double x = position.x; 5 double y = position.y; 6 7 double screenWidth = container_canvas.ActualWidth; 8 double screenHeight = container_canvas.ActualHeight; 9 10 x = x / detectionWidth * screenWidth + (screenWidth / 2); 11 y = screenHeight - y / 600 * screenHeight; 12 13 this.Dispatcher.BeginInvoke(new Action(delegate 14 { 15 Canvas.SetLeft(ellipse, x); 16 Canvas.SetTop(ellipse, y); 17 }), null);
這樣,我們就完成了指尖位置的繪制。
但是,要記得,在Dictionary<int, Ellipse>中可能存在本幀中沒有檢測到的指尖的id。為此,我們需要移除Dictionary<int, Ellipse>中那些多余的Key。代碼如下:
1 //去掉這一幀中沒追蹤到的手指 2 IEnumerable<int> deletedIds = ellipses.Keys.Except(fingerIds); 3 foreach (int id in deletedIds.ToList())//這里要記得ToList()一下,否則會出現異常。 4 { 5 Ellipse ellipse = ellipses[id]; 6 7 this.Dispatcher.Invoke(new Action(delegate 8 { 9 container_canvas.Children.Remove(ellipse); 10 }), null); 11 12 ellipses.Remove(id); 13 } 14 15 //完成本次繪制,清空List<int> 16 fingerIds.Clear();
ok,這樣就完成了。運行程序看看吧。
你會發現,基本上還是我們要的效果。但是,
當手越高,指尖距離越近,這是為什么呢?考慮一下。
附上源代碼。