LeapMotion(2):追踪五指

Stella981
• 阅读 673

上一篇文章,我们实现了Leap Motion的简单测试。追踪其中一个手指并用红色圆形表示其在空间的位置。

这篇文章,我们来实现五指的追踪。

其实,能够实现一指的追踪,那么五指的追踪自然不成问题。但是,还是有几个问题我们需要考虑一下。

1、并不是每一帧都会包含五指的全部信息。

比如,当前帧包含了五指信息,那么,窗口上就会显示五个红色圆。如果此时用户握拳,那么,下一帧就可能只会有一指的信息。此时,就应从窗口中移除多余的四个红色圆。

2、手指如何和红色圆对应。

 因为Hand.Fingers集合对应的不一定是拇指、食指、中指、无名指、小指(可能对应的是小指、无名指、中指),所以,得想个办法把某个指尖和某个红色圆对应起来。幸好,Leap为每个对象都定义了ID。这样,我们就可以将指尖的ID和红色圆绑定在一起。自然地,我们会想到用Dictionary<int, Ellipse>。

还有一点,假设上一帧检测到了拇指(id为5)、这一帧没检测到拇指,而下一帧又检测到了拇指,那么,它的id可能是5,但也有可能不是5。

3、如何删除上一帧有的而这一帧中没有的红色圆。

这个问题相对简单,做一个List,把这一帧中id一次加进去,然后,再从Dictionary<int, Ellipse>的Keys里面删除那些不在List中的id所对应的红色圆。

OK,大部分问题都有了思路,那么,我们开始写代码吧。记得,一定要先看看上一篇文章啊。

Step1:构造下面的用户界面。

LeapMotion(2):追踪五指

Step2:声明MyLeapListener类和窗口Closing事件。代码和LeapMotion(1)中的一样。

Step3:添加成员变量Dictionary<int, Ellipse>表示手指ID和红色圆的对应,添加成员变量List表示当前帧追踪到的手指编号。代码如下:

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,然后判断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,这样就完成了。运行程序看看吧。

你会发现,基本上还是我们要的效果。但是,

当手越高,指尖距离越近,这是为什么呢?考虑一下。

附上源代码

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Karen110 Karen110
3年前
​一篇文章总结一下Python库中关于时间的常见操作
前言本次来总结一下关于Python时间的相关操作,有一个有趣的问题。如果你的业务用不到时间相关的操作,你的业务基本上会一直用不到。但是如果你的业务一旦用到了时间操作,你就会发现,淦,到处都是时间操作。。。所以思来想去,还是总结一下吧,本次会采用类型注解方式。time包importtime时间戳从1970年1月1日00:00:00标准时区诞生到现在
Stella981 Stella981
3年前
Dubbo + Zipkin + Brave实现全链路追踪
DubboZipkinBrave实现全链路追踪最近写了一个链路追踪Demo分享下,实现了链路追踪过程中数据的记录,还有能扩展的地方,后期再继续补充。原理参考上面文章《Dubbo链路追踪——生成全局ID(traceId)》(https://my.oschina.net/Luc
Stella981 Stella981
3年前
Opencv实时眼球追踪,解脱的你双手,让你的眼睛写代码!
   Opencv实时眼球追踪,让你的眼睛写代码!这个还是有点对于我现在的追踪效果,还有点距离,但是我想完成这个还是没有问题的,用眼睛去控制电脑打字。我认为只要用手可以做,用眼睛都可以做到,包括游戏、画画、写字等等!   废话不多说,目前追踪率82.5%98%(戴眼镜和不戴眼镜),目前这个是第一个版本,只是基本上实现了眼球追踪,后面会加上G
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
3年前
Gson之实例五
前面四篇博客基本上可以满足我们处理的绝大多数需求,但有时项目中对json有特殊的格式规定.比如下面的json串解析:{"tableName":"students","tableData":{"id":1,"name":"李坤","birthDay":"Jun 22, 2012 9:54:49 PM"},{"id":2,"name":"曹贵生"