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

来源:互联网 发布:蜂群算法和蚁群算法 编辑:程序博客网 时间:2024/05/18 00:14

转自http://blog.csdn.net/guoming0000/article/details/12242447
本文基于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++应用程序啦!

0 0
原创粉丝点击