在上一篇《C#开发基于Leap Motion的手势控制应用(一)》中,我们介绍了Leap motion系统的一些特征以及如何使用Helix 3Dtoolkit打开3D模型文件。本文作为Leap Motion使用介绍的第二篇,将着重讨论Leap Motion的开发架构和手势识别所涉及的代码细节。
Leap Motion的系统架构
Leap Motion的数据采集都是基于一个系统服务(Service)。这个Service和LeapMotion控制器通过USB3.0相连接。凡是和Leap Motion相关的应用程序都会和这个Service交互,从而获得运动的追踪数据。Leap Motion的SDK提供了两大类编程接口来访问追踪数据:Native接口和Web Socket接口。所有的这些接口都可以用结合不同的编程语言来开发基于Leap Motion的应用,例如使用JSP可以开发基于Web的Leap Motion应用程序。
应用程序编程接口
Leap Motion的SDK提供两类API用以追踪数据的获取。Native接口是以动态链接库的形式提供,此接口可以直接连接Leap Motion的Service,从而读取应用程序所需的追踪数据。这些链接库可以直接不同开发语言(例如C++或者Objective-C)的工程中加以链接使用,或者使用针对其他不同语言的封装,例如Java、C#或者Python。
图 Leap Motion系统结构的组成
LeapMotion的硬件发送数据给Leap Motion Service,再由Service处理并转发给基于Leap的应用程序。应用程序在失去前台焦点以后依旧可以在后台接受Leap的数据。
Leap的应用程序和Service是独立运行的,并且还有一个Leap Motion的配置程序容许用户对Leap Motion控制器进行配置等工作。
图 Leap Motion的Config配置界面截图
前台的Leap应用从Service接收到运动跟踪数据。应用通过Leap Motion的Native Library和Service交互。
当前台的Leap应用失去焦点以后,Service就会暂停向应用程序发发送数据。在后台工作的应用程序(或者是应用本身就当作是一个Service部署)可以申请在后台继续接收数据的许可。在后台运行的程序的行为照样可以由Leap的控制台应用程序决定。
图 在Leap的配置应用里可以控制后台应用的行为
运行时的配置
在Leap Motion控制面板中的很多选项都可以在配置文件中完成,这些配置文件包括:服务控制和功能控制。当启动Leap Motion的Service的时候,这些在配置文件中的控制选项可以在运行时动态的通过Config类来完成。这些设置上的更改会影响使用这一Service的全部应用,所以在使用Config做设置的时候需要格外谨慎。此部分不作为具体介绍(文中connectHandler函数有所涉及,可做参考),有兴趣的读者可以自行了解。file:///YOUR_LEAPMOTION_SDK_PATH/LeapSDK/docs/csharp/devguide/Leap_Configuration.html
具体应用的相关代码
首先定义一个接口(ILeapEventDelegate)和类(LeapEventListener)。在类的实现中,完成了主要的Leap Motion事件的响应函数(实际上是对接口Listener中的事件处理函数原型的实现)。并通过委托发送给真正的消息处理函数(消息被路由)
publicinterfaceILeapEventDelegate
{
voidLeapEventNotification(string EventName);
}
publicclassLeapEventListener :Listener
{
ILeapEventDelegate eventDelegate;
publicLeapEventListener(ILeapEventDelegate delegateObject)
{
this.eventDelegate =delegateObject;
}
publicoverridevoid OnInit(Controller controller)
{
this.eventDelegate.LeapEventNotification("onInit");
}
publicoverridevoid OnConnect(Controller controller)
{
controller.SetPolicy(Controller.PolicyFlag.POLICY_IMAGES);
controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
this.eventDelegate.LeapEventNotification("onConnect");
}
publicoverridevoid OnFrame(Controller controller)
{
this.eventDelegate.LeapEventNotification("onFrame");
}
publicoverridevoid OnExit(Controller controller)
{
this.eventDelegate.LeapEventNotification("onExit");
}
publicoverridevoid OnDisconnect(Controller controller)
{
this.eventDelegate.LeapEventNotification("onDisconnect");
}
}
定义Leap motion相关的变量,并将其初始化
privateController controller;//Leapmotion控制器对象定义
privateLeapEventListener listener; //Leap Motion的事件对象定义
并在构造函数中初始化:
this.controller =newController();//初始化控制器对象
this.listener =newLeapEventListener(this);//初始化listener对象
//将自定义的Listener对象和控制器相关关联,控制器的实例的所有事件都会dispatch到//这个listener对象,并完成处理。
controller.AddListener(listener);
这里实际上实现了一个LeapMotion Controller的订阅者模式,凡是被订阅者注册的controller的消息都会被接受,并最终用委托的相识发送给消息路由。
delegatevoidLeapEventDelegate(string EventName);
publicvoidLeapEventNotification(string EventName)
{
if (this.CheckAccess())
{
switch (EventName)
{
case"onInit"://当初始化发生的时候,只打印一个Debug信息
Debug.WriteLine("Init");
break;
case"onConnect"://当(每次连接只会发生一次)和控制器连接的时候,调//用connectHandler()函数
this.connectHandler();
break;
case"onFrame"://每当有新的真数据到来,就调用newFrameHandler函数
if (!this.isClosing)
this.newFrameHandler(this.controller.Frame());
break;
}
}
else
{
Dispatcher.Invoke(newLeapEventDelegate(LeapEventNotification),newobject[] { EventName});//其他的消息依旧会被送回这个“路由”函数,但永远不会得到处理。
}
}
(3)具体的消息处理函数
ConnectHandler
void connectHandler()
{
this.controller.SetPolicy(Controller.PolicyFlag.POLICY_IMAGES);
this.controller.EnableGesture(Gesture.GestureType.TYPE_SWIPE);
this.controller.EnableGesture(Gesture.GestureType.TYPE_KEY_TAP);
this.controller.EnableGesture(Gesture.GestureType.TYPE_CIRCLE);
this.controller.EnableGesture(Gesture.GestureType.TYPESCREENTAP);
this.controller.EnableGesture(Gesture.GestureType.TYPE_SCREEN_TAP);
this.controller.Config.SetFloat("Gesture.Swipe.MinLength", 100.0f);
}
connectHandler主要是在连接控制器的时候打开要识别的手势类型,并在最后使用Config配置动作Swipe的最小移动距离。具体的手势名称和对应的TYPE信息可以参考SDK文档,此处不做赘述。
newFrameHandler函数
此函数主要处理新的数据帧中的手势信息,其基本的结构如下:
voidnewFrameHandler(Leap.Frame frame)
{
//Parse thegestures here
for (int i = 0; i
{
Gesture gesture =frame.Gestures()[i];
switch (gesture.Type)
{
caseGesture.GestureType.TYPE_CIRCLE:
CircleGesture circle =newCircleGesture(gesture);
…
}
…
}
实际上,有些动态手势并不能在一帧图形中识别出来,例如TYPE_CIRCLE(用手指在空中画圈),这样的动作其实多个帧之间判断手指尖的初始位置和结束位置。然而有些手势却能在一帧数据里识别出来,例如,手势“V”,类似的手势只需在一帧的数据中判断指尖和几个特殊关节的位置,就可以多出对静态手势的识别。有兴趣的读者可以下载(https://github.com/iamwmh/ModelViwer.git)配合本文的工程源代码源代码,结合代码里的手势识别处理用例,更多的了解手势处理的细节。