Leap Motion 模拟手势:触击_C++

来源:互联网 发布:啊哈算法并查集 编辑:程序博客网 时间:2024/04/29 06:47

    鉴于博客里有人留言,发现大家对Leap这一块的兴趣还是很高的,而且最近ZOL今年底的时候也给Leap分配了一定的篇幅来介绍,可见Leap体感(准确说是手势)识别的宣传及普及力度还是有的,至于Leap在未来应用领域的开拓上会有怎样的情况,我们就拭目以待吧。好,闲话不多说,今天翻译另一篇文章——Touch Emulation,模拟手指的触击动作。

    Leap Motion提供了丰富的API,在我们自己开发的应用中,咱可以使用API的输出数据来识别触击动作。需要注意的是,在API中,有关触击的数据是由类Pointable提供的。

1、概述

    为了识别触击动作,Leap设备虚拟出一个自适应的触摸平面,在这个虚拟出来的二维平面上,我们便可以实现数据交互了。虽然虚拟二维平面会根据用户手指及手的位置做调整,但在Leap设备的三维坐标系中,与X-Y面是基本平行的。当用户的手指或者使用工具向前移动时,Leap会判断Pointable对象是在靠近还是在触击虚拟二维平面。借助Leap API,当手指或工具触击到虚拟平面时,API会返回两个值:虚拟面区域的信息、手指与触摸面的距离。

(图1为虚拟触摸区域,0点左侧+1区域为悬停区,0点右侧-1区域为触击区)

    Leap Motion借助触摸面,能将Pointable对象识别成这样几种情况:悬停在靠近触摸面的地方、触击触摸面、远离触摸面(触击方向有误时也会识别为远离)。三个区域分别称为:“悬停”、“触击”、“无信号”。注意,三个区域的转换常常是延迟于触击diatance的,这样的延迟是用来防止突然性和重复性的转换,所以当在我们自己开发的应用中,在实现触击交互时,我们可以不必过分考虑“触击”区域。

    记住,只有当Pointable对象在悬停区和触击区的时候,触击distance才变为有效。distance是一个在[+1,-1]区间内规格化的数值,当Pointable对象第一次进入悬停区,触击distance为+1.0,随着Pointable对象靠近触击区域,distance的值也逐渐降为0值;当Pointable对象穿透了触摸面(图1中红色网络区域)进入触击区域,distance为0值,随着Pointable对象在触击区域中越来越深,distance值将逐渐变为-1。但注意了,distance值永远不会超出-1。

    在开发应用时,我们可以根据区域信息来判断,是否需更新UI中与悬停和触击动作相关的UI元素。我们还可以根据distance信息表征的触摸面距离信息,进一步修改UI元素。例如,当我们把手指悬在Leap Controller设备的上方、且处在悬停区域时,咱可以把Leap Controller设备当前的聚焦状态显示出来,然后呢,根据distance信息在屏幕上显示对应的游标,作为手指离触摸面距离的反馈信息。

    作为模拟触击API信息中的一部分,Leap Motion提供的Pointable对象位置信息不仅是规格化的,而且数据很稳定。Leap Motion软件内含自适应的滤波器,改滤波器能够对行为动作进行平滑化和平缓操作,以方便用户在屏幕的小块区域进行操作交互(如点击按钮、链接)。当用户的动作缓慢时,平滑化效果会更好,用户点击特殊点的效果会更好,同样,我们可以用这样的方法来做归零矫正。

2.获取触摸面

    触摸面的信息可以通过Pointable类的touchZone属性来获取,触摸面是用Zone枚举产生的,其中包含以下状态信息:

    NONE——此时,pointable对象要么离触摸面太远,要么就是触击方向有问题(比如触击方向为指向用户);

    HOVERING——此时pointable对象指尖已经进入悬停区,但还没有触击;

    TOUCHING——此时pointable对象已经穿过触摸面,进入触击区;

    下面一段代码,描述最前端手指所在区域的检测信息:

    Leap::Frame frame = leap.frame();    Leap::Pointable pointable = frame.pointables().frontmost();    Leap::Pointable::Zone zone = pointable.touchZone();

3.获取触击Distance

    触击distance是通过Pointable类的touchDistance属性来获取的,distance随着手指在虚拟平面的靠近和触击,其范围在+1到-1区间内变化。distance信息没有一个实际的数量意义,它只是反映Leap Motion对pointable距离的检测效果。

    下面一段代码,描述了最前端手指的触击distance:

    Leap::Frame frame = leap.frame();    Leap::Pointable pointable = frame.pointables().frontmost();    float distance = pointable.touchDistance();

4.获取Pointable的稳定位置信息

    Pointable对象的位置信息是通过Pointable类的stabilizedTipPosition属性,此位置信息是基于标准Leap Motion十字坐标来输出的,并且经过了上下文相关的滤波和稳定化处理。

    下面一段代码,描述最前端手指的稳定位置信息:

    Leap::Frame frame = leap.frame();    Leap::Pointable pointable = frame.pointables().frontmost();    Leap::Vector stabilizedPosition = pointable.stabilizedTipPosition();

5.Leap Motion设备坐标系和App坐标系的转换

    当开发触击模拟相关app应用时,我们必须把Leap Motion设备的坐标系映射到app的屏幕空间中。为了方便映射,Leap Motion的API提供了InteractionBox类。InteractionBox描绘的是在Leap Motion视野范围内一个长方体容器,该类提供了一个函数,函数会把长方体空间内的位置映射到[0,1]规格化区间内,我们先将实际的位置信息规格化,然后根据app的尺寸产生最终的坐标,最后在app坐标系中得到一个点。

    例如,如果我们在客户区内有一窗口,窗口大小由变量windowWidth、windowHeight确定。当我们手指触击的位置在客户区范围内的时候,我们可参考下面的代码,来得到触击点的二维坐标值:

    Leap::Frame frame = leap.frame();    Leap::Finger finger = frame.fingers().frontmost();    Leap::Vector stabilizedPosition = finger.stabilizedTipPosition();    Leap::InteractionBox iBox = leap.frame().interactionBox();    Leap::Vector normalizedPosition = iBox.normalizePoint(stabilizedPosition);    float x = normalizedPosition.x * windowWidth;    float y = windowHeight - normalizedPosition.y * windowHeight;

6.触击案例
    在下面的例子中,对于app应用窗口中检测到的所有Pointable对象,我们通过调用模拟触击的API,将pointable对象的位置显示出来。本例中,在触摸区域内,我们将触击点涂色,并根据触击distance来设置阿尔法值。指尖的稳定位置数据通过类InteractionBox来映射到app窗口:

    本例中,触击的代码如下(本例通过调用库Cinder来创建app窗口并绘图):

#include "cinder/app/AppNative.h"#include "cinder/gl/gl.h"#include "Leap.h"#include "LeapMath.h"using namespace ci;using namespace ci::app;using namespace std;class TouchPointsApp : public AppNative {  public:        void setup();        void draw();  private:    int windowWidth = 800;    int windowHeight = 800;    Leap::Controller leap;};void TouchPointsApp::setup(){    this->setWindowSize(windowWidth, windowHeight);    this->setFrameRate(120);    gl::enableAlphaBlending();}void TouchPointsApp::draw(){        gl::clear( Color( .97, .93, .79 ) );    Leap::PointableList pointables = leap.frame().pointables();    Leap::InteractionBox iBox = leap.frame().interactionBox();    for( int p = 0; p < pointables.count(); p++ )    {        Leap::Pointable pointable = pointables[p];        Leap::Vector normalizedPosition = iBox.normalizePoint(pointable.stabilizedTipPosition());        float x = normalizedPosition.x * windowWidth;        float y = windowHeight - normalizedPosition.y * windowHeight;        if(pointable.touchDistance() > 0 && pointable.touchZone() != Leap::Pointable::Zone::ZONE_NONE)        {            gl::color(0, 1, 0, 1 - pointable.touchDistance());        }        else if(pointable.touchDistance() <= 0)        {            gl::color(1, 0, 0, -pointable.touchDistance());        }        else        {            gl::color(0, 0, 1, .05);        }        gl::drawSolidCircle(Vec2f(x,y), 40);    }}CINDER_APP_NATIVE( TouchPointsApp, RendererGl )





0 0
原创粉丝点击