Leap Motion自带Sample之详解_Win/C++版本

这篇文章我没翻译,因为我觉得他的例子一看便知,太清晰简单了(也是自己翻译的太慢,翻不动了~)。转载来自wyf2742的文章。

本文基于Leap SDK,对C++版本的应用程序sample进行解释。希望在通读全文之后,大家能够借助Leap设备获取跟踪数据。

 

纲要:

一、概述

二、创建一个Controller对象

三、创建类Listener的子集

四、获取一个帧数据

五、获取手势信息

六、运行程序

 

在Leap SDK文件夹下,请找到本文所需的文件,具体如下:

  • LeapSDK/sample/Sample.cpp — C++ sample application
  • LeapSDK/include/Leap.h — Leap C++ API class and struct definitions
  • LeapSDK/include/LeapMath.h — Leap C++ API Vector and Matrix class and struct definitions
  • LeapSDK/lib/x86/Leap.lib — 32-bit Leap compile-time library for Windows
  • LeapSDK/lib/x64/Leap.lib — 64-bit Leap compile-time library for Windows
  • LeapSDK/lib/x86/Leap.dll — 32-bit Leap runtime library for Windows
  • LeapSDK/lib/x64/Leap.dll — 64-bit Leap runtime library for Windows
  • LeapSDK/lib/x86/Leapd.lib — 32-bit Leap compile-time debug library for Windows
  • LeapSDK/lib/x64/Leapd.lib — 64-bit Leap compile-time debug library for Windows
  • LeapSDK/lib/x86/Leapd.dll — 32-bit Leap runtime debug library for Windows
  • LeapSDK/lib/x64/Leapd.dll — 64-bit Leap runtime debug library for Windows

需要注意的是,我们可以在  LeapSDK/docs/API_Reference/annotated.html  下扎到Leap API的参考文献。

 

一、概述

在本概述中,Leap Motion Controller检测并跟踪人手和手指,Leap设备一次捕获一“帧”,我们的应用程序将通过Leap API来获取这一“帧”的数据。

应用程序sample说明了如何使用Leap API来监听frame事件以及如何获取每一帧中的人手、手指,其中frame事件由Leap设备来触发。应用程序是个简单的命令行程序,程序可以输出被检测人手和手指的信息。应用程序的代码包含在了文件Sample.cpp中。

应用程序sample使用了Leap API中很大部分的关键类,具体包括如下:

  • Leap::Controller — the interface between the Leap and your application
  • Leap::Listener — used to handle events dispatched by the Leap
  • Leap::Frame — contains a set of hand and finger tracking data
  • Leap::Hand — contains tracking data for a detected hand
  • Leap::Finger — contains tracking data for a detected finger
  • Leap::Vector — represents a 3D position or directional vector
  • Leap::Gesture — represents a recognized gesture.

关于上述类的更多信息,请参阅Leap API的参考文献。

 

二、创建一个Controller对象

类Leap::Controller在Leap设备和应用程序之间提供了主要的接口,当我们创建了一个Controller对象后,Controller对象将连接PC上的Leap软件,然后通过Leap::Frame对象获取人手的跟踪数据。其中,我们可以通过实例化一个Controller对象、调用Controller::Frame函数,来获取Frame对象。

如果我们以后自己的应用程序中包含一个不断更新的循环或帧率,那我们可以调用Controller::frame作为更新程序的一部分;不然的话,我们就需要自己手动给controller对象绑定一个监听器。一旦读入有效的跟踪帧数据(其他Leap事件也可),controller对象将调用定义在Leap::Listener子类中的回调函数。

在本应用程序sample中,主函数main创建了一个Controller对象,并通过调用Controller::addListener函数,将Leap::Listener子类的一个实例(即对象)绑定到Controller对象上,代码如下:

int main() {
  // Create a sample listener and controller
  SampleListener listener;
  Controller controller;

  // Have the sample listener receive events from the controller
  controller.addListener(listener);

  // Keep this process running until Enter is pressed
  std::cout << "Press Enter to quit..." << std::endl;
  std::cin.get();

  // Remove the sample listener when done
  controller.removeListener(listener);

  return 0;
}

 

但仅有这一段是不能运行的,还需要要创建一个Leap::Listener的子类SampleListener。这个监听器的子类定义了一些回调函数,当Leap事件发生时或者跟踪的帧数据就绪时,controller对象可以来回调。

 

三、创建类Listener的子集

应用程序sample定义了一个Leap::Listener的子类SampleListener,其中集成了处理Leap事件的回调函数,这些事件包括:

1、onInit—— 一旦监听器监听的controller对象开始初始化时,立即触发;

2、onConnect—— 当controller对象连接Leap设备,并即将发送运动跟踪的帧数据时,立即触发;

3、onDisconnect—— 当controller对象与Leap设备断开(比如,从USB拔出Leap设备或关闭Leap软件),立即触发;

4、onExit—— 当监听器与controller对象分离时,将触发监听器;

5、onFrame—— 当获取到运功跟踪的帧数据时,立即触发;

对于3种生命周期事件的回调函数onInit、onDisconnect和onExit,为方便起见,在本应用程序sample中,把一句话作为标准输出。而对于onConnect和onFrame事件,监听器的回调函数做了稍多些的处理。当controller对象调用了回调函数onConnect,函数将识别所有的手势类型。当controller对象调用onFrame函数,函数将获取最新的运动跟踪帧数据,并把检测目标的信息作为标准输出。

 

四、获取一个帧数据

当Leap产生新的运功跟踪帧数据,controller会调用回调函数onFrame,我们可以通过调用函数Controller::frame()来获得对应的数据,其中函数的返回值就是最新的Frame对象(Controller对象的引用被作为参数传给了回调函数)。一个Frame对象包含一个ID、一个时间戳、包括手的对象的list列表(手的对象即Leap检测范围内实际存在的手)。

下面的代码是应用程序sample中函数onFrame的一部分,onFrame函数实现的功能有:从controller对象获取最新的Frame对象,并根据Frame对象检索人手的list列表,然后输出Frame的ID、时间戳、以及帧数据中人手的数量、手指数、工具的数量:

// Get the most recent frame and report some basic information
const Frame frame = controller.frame();
std::cout << "Frame id: " << frame.id()
          << ", timestamp: " << frame.timestamp()
          << ", hands: " << frame.hands().count()
          << ", fingers: " << frame.fingers().count()
          << ", tools: " << frame.tools().count() << std::endl;

接下来,函数将检测list列表中的第一只人手:

if (!frame.hands().isEmpty()) {
  // Get the first hand
  const Hand hand = frame.hands()[0];

Hand类的一个对象包含ID、反映人手物理特征的属性、手指对象的list列表,而每个Figer对象又包含ID、反映手指物理特征的属性。

一旦检测到一只人手,程序将对手指进行判断,然后对表征手指位置的数据取平均值,最后输出手指的个数、手指的平均位置。

// Check if the hand has any fingers
const FingerList fingers = hand.fingers();
if (!fingers.isEmpty()) {
  // Calculate the hand's average finger tip position
  Vector avgPos;
  for (int i = 0; i < fingers.count(); ++i) {
    avgPos += fingers[i].tipPosition();
  }
  avgPos /= (float)fingers.count();
  std::cout << "Hand has " << fingers.count()
            << " fingers, average finger tip position" << avgPos << std::endl;
}

接下来,函数继续输出跟手掌弧度一致的球体半径、手掌心的空间位置:

// Get the hand's sphere radius and palm position
std::cout << "Hand sphere radius: " << hand.sphereRadius()
          << " mm, palm position: " << hand.palmPosition() << std::endl;

最后,函数onFrame将调用函数Vector,并根据人手的法线向量和方向向量,来计算人手的倾斜度、旋转度、偏转角。其中,角的单位进行了由弧度转为角度的变换。

// Get the hand's normal vector and direction
const Vector normal = hand.palmNormal();
const Vector direction = hand.direction();

// Calculate the hand's pitch, roll, and yaw angles
std::cout << "Hand pitch: " << direction.pitch() * RAD_TO_DEG << " degrees, "
          << "roll: " << normal.roll() * RAD_TO_DEG << " degrees, "
          << "yaw: " << direction.yaw() * RAD_TO_DEG << " degrees" << std::endl << std::endl;

 

五、获取手势信息

为了从Leap设备获取手势,我们首先要启用感兴趣的手势识别类型。在controller对象连接Leap设备后(即isConnected为真值),我们可以随时启用手势识别。在本应用程序sample中,回调函数onConnect()通过调用enableGesture()函数,启用了所有的手势识别类型。其中,enableGesture()函数是由类Controller定义的。

void SampleListener::onConnect(const Controller& controller) {
    std::cout << "Connected" << std::endl;
    controller.enableGesture(Gesture::TYPE_CIRCLE);
    controller.enableGesture(Gesture::TYPE_KEY_TAP);
    controller.enableGesture(Gesture::TYPE_SCREEN_TAP);
    controller.enableGesture(Gesture::TYPE_SWIPE);
}

Leap设备把代表识别动作模型的Gesture对象放到Frame对象中gestures的list列表里。在回调函数onFrame()中,应用程序sample循环读取gestures的list列表,并把每个手势的信息输出。整个操作是通过一个标准for循环和switch语句来实现的。

Gesture API使用的是一个Gesture基类,Gesture基类是依靠代表各种手势的类延伸出来的。gesture list列表中的对象是类Gesture的实例,所以我们必须要将Gesture的实例转换为对应子类的实例。在这里,类型转化是不支持的,不过每个子类的构造函数都提供了类型转化的功能。例如,一个代表旋转动作的Gesture实例可以用下面的语句转化为类CircleGesture的实例:

CircleGesture circle = CircleGesture(gesture);

如果要将一个Gesture实例转化为错误的子类类型,那么对应的构造函数将会返回一个无效的Gesture对象。

我们常常会用到,将当前帧的手势信息与前面的帧里对应的手势进行比较,例如,画圈的手势动作里有个进度属性,此属性用来表征手指已经画圈的次数。这是一个完整的过程,如果想在帧与帧之间获取这个进度,我们需要减去前一帧中手势进度值。在实际操作中,我们可以通过手势gesture的ID找到对应的帧。下面的代码就是用了这个方法由前帧导出相应的角(单位:弧度):

float sweptAngle = 0;
if (circle.state() != Gesture::STATE_START) {
  CircleGesture previousUpdate = CircleGesture(controller.frame(1).gesture(circle.id()));
  sweptAngle = (circle.progress() - previousUpdate.progress()) * 2 * PI;
}

整个手势识别循环检测的代码如下:

// Get gestures
const GestureList gestures = frame.gestures();
for (int g = 0; g < gestures.count(); ++g) {
    Gesture gesture = gestures[g];

    switch (gesture.type()) {
        case Gesture::TYPE_CIRCLE:
        {
            CircleGesture circle = gesture;
            std::string clockwiseness;

            if (circle.pointable().direction().angleTo(circle.normal()) <= PI/4) {
              clockwiseness = "clockwise";
            } else {
              clockwiseness = "counterclockwise";
            }

            // Calculate angle swept since last frame
            float sweptAngle = 0;
            if (circle.state() != Gesture::STATE_START) {
              CircleGesture previousUpdate = CircleGesture(controller.frame(1).gesture(circle.id()));
              sweptAngle = (circle.progress() - previousUpdate.progress()) * 2 * PI;
            }
            std::cout << "Circle id: " << gesture.id()
                      << ", state: " << gesture.state()
                      << ", progress: " << circle.progress()
                      << ", radius: " << circle.radius()
                      << ", angle " << sweptAngle * RAD_TO_DEG
                      <<  ", " << clockwiseness << std::endl;
            break;
        }
        case Gesture::TYPE_SWIPE:
        {
            SwipeGesture swipe = gesture;
            std::cout << "Swipe id: " << gesture.id()
              << ", state: " << gesture.state()
              << ", direction: " << swipe.direction()
              << ", speed: " << swipe.speed() << std::endl;
            break;
        }
        case Gesture::TYPE_KEY_TAP:
        {
        KeyTapGesture tap = gesture;
            std::cout << "Key Tap id: " << gesture.id()
              << ", state: " << gesture.state()
              << ", position: " << tap.position()
              << ", direction: " << tap.direction()<< std::endl;
            break;
        }
        case Gesture::TYPE_SCREEN_TAP:
        {
            ScreenTapGesture screentap = gesture;
            std::cout << "Screen Tap id: " << gesture.id()
            << ", state: " << gesture.state()
            << ", position: " << screentap.position()
            << ", direction: " << screentap.direction()<< std::endl;
            break;
        }
        default:
            std::cout << "Unknown gesture type." << std::endl;
            break;
    }
}

 

六、运行程序

为了运行应用程序,我们需要以下步骤:

1、编译、链接应用程序sample;

windows下,依靠Leap.h、LeapMath.h(两者在SDK的include文件目录下)和Leap.lib(32位系统下,文件在lib\x86文件目录下;64位的在lib\x64目录下)

注意:在debug模式下编译应用程序,连接Leapd.lib库文件

2、将Leap设备通过数据线连接电脑后,放在自己的前面;

3、打开Leap软件;

4、运行应用程序sample;

windows下,请确保sampple.exe和Leap.dll在同一个文件目录下,或者Leap.dll在的动态库的搜索路径内;

 

运行后,我们将会发现,当程序初始化并连接到Leap设备后,会有Initialized和Connected字符的标准输出。我们还可以发现,Leap设备每次触发onFrame事件时都会显示帧数据的信息。当把手放到放到Leap设备上面时,我们同样会发现有手指和手掌的位置信息的输出。

 

现在,我们已经知道了如何通过Leap设备来获取运动跟踪数据,那么举一反三,下面就可以基于Leap设备开发自己的C++应用程序啦!

你可能感兴趣的:(Leap Motion自带Sample之详解_Win/C++版本)