Leap Motion翻译系列文章http://52coding.com/leap-motion-official-doc-translation
追踪手、手指和工具
手、手指和工具是Leap Motion系统的基本追踪实体。这篇文章将详细讨论如何获取和使用这些实体对象。
Leap的API定义了一个可以表示每个基本追踪到的物体的类。 帧对象提供了一个可以访问手、手指和工具的列表。指尖和工具都是尖端物体,并且他们可以被同时放入尖端列表PointableList中,或者分别使用手指列表FingerList和工具列表类ToolList。手对象提供访问他们的手指和工具的途径(都一起在尖端列表中或者分开)。 手、手指和工具的物体特点在Leap的坐标系统下(以毫米为单位衡量)有报告。Leap的SDK提供了向量类(Vector)来描述点和朝向。向量类提供了一些有用的向量运算的数学函数[这个挺不错,避免自己写了]。
列表类都都拥有近似的结构。他们被设计成向量形式矩阵并且支持迭代器。你不能移除或者改变从Leap API接收的列表中的成员变量,但你可以组合列表中的同一个类型的对象。 对某一个列表使用迭代器:
for(HandList::const_iterator hl = handList.begin(); hl != handList.end();) std::cout << *hl << std::endl;
手、尖端物体、手指和工具列表基于在Leap坐标系统下的相对位置,定义了额外的函数来获取列表中的成员变量。这些函数包含leftmost(),rightmost(),和frontmost()。接下来的小片段代码说明了使用这些函数的方法:
Leap::Finger farLeft = frame.fingers().leftmost(); Leap::Finger mostForwardOnHand = frame.hands()[0].fingers().frontmost(); Leap::Tool rightTool = frame.tools().rightmost();
一个更复杂的例子是计算一个包含检测到的所有尖端物体箱子的矩形体。因为在API中没有提供,这个例子定义了它自己的函数来获取顶部、底部和后方的尖端。
float left = frame.pointables().leftmost().tipPosition().x; float right = frame.pointables().rightmost().tipPosition().x; float front = frame.pointables().frontmost().tipPosition().z; float back = backmost(frame.pointables()).tipPosition().z; float top = topmost(frame.pointables()).tipPosition().y; float bottom = bottommost(frame.pointables()).tipPosition().y; Leap::Pointable backmost(PointableList pointables) { if(pointables.count() == 0) return Leap::Pointable::invalid(); Leap::Pointable backmost = pointables[0]; for( int p = 1; p < pointables.count(); p++ ) { if( pointables[p].tipPosition().z > backmost.tipPosition().z) backmost = pointables[p]; } return backmost; } Leap::Pointable topmost(PointableList pointables) { if(pointables.count() == 0) return Leap::Pointable::invalid(); Leap::Pointable topmost = pointables[0]; for( int p = 1; p < pointables.count(); p++ ) { if( pointables[p].tipPosition().y > topmost.tipPosition().y) topmost = pointables[p]; } return topmost; } Leap::Pointable bottommost(PointableList pointables) { if(pointables.count() == 0) return Leap::Pointable::invalid(); Leap::Pointable bottommost = pointables[0]; for( int p = 1; p < pointables.count(); p++ ) { if( pointables[p].tipPosition().y < bottommost.tipPosition().y ) bottommost = pointables[p]; } return bottommost; }
[也就是简单计算下所有物体三维坐标的最小外接矩形,很容易理解~代码如出一辙,看懂一个后面都懂。看了下API,以及我自己猜测,left,front,right这几个方位,Leap Motion是比较关注的,而对于高Leap可能无法准确探测,但对于其它几个方向为什么不直接提供函数呢?]
手类描述了一个被Leap追踪到的物理形式的手。一个手对象提供了访问它自己尖端(手指)的列表,以及一个描述的手的坐标、朝向和运动的属性。从一帧中获取手对象:
Leap::Frame frame = controller.frame(); // controller is a Leap::Controller object Leap::HandList hands = frame.hands(); Leap::Hand firstHand = hands[0];
或者,如果你从前一帧中已知ID:
Leap::Hand knownHand = frame.hand(handID);
你还可以通过它们在帧中相对位置获取手对象:
Leap::Frame frame = controller.frame(); // controller is a Leap::Controller object Leap::HandList hands = frame.hands(); Leap::Hand leftmost = hands.leftmost(); Leap::Hand rightmost = hands.rightmost(); Leap::Hand frontmost = hands.frontmost();
注意,rightmost()和rightmost()函数仅仅区分哪只手是最右侧或者最左侧。这个方法不区分这只手是左手还是右手。
一只手被通过它的坐标、朝向和运动得以描述。 手的坐标是通过手掌坐标属性得到的,提供了一个以Leap Motion为原点,毫米为单位的手掌中心三维坐标的向量。手的朝向由2个向量给出:方向---从手掌中心指向手指方向,手掌垂直面---指向手掌外侧,垂直于手的平面。 手的运动通过运动速度属性给出,它是一个描述手在毫米每秒瞬时运动的向量。你还可以获得运动因子,它可以描述一只手在两帧时的位移、旋转和缩放值的变化。 下面的代码块描述了如何在一帧中获取一个手对象和它的基本属性:
Leap::Hand hand = frame.hands().rightmost(); Leap::Vector position = hand.palmPosition(); Leap::Vector velocity = hand.palmVelocity(); Leap::Vector direction = hand.direction();
你可以通过列表或仅仅通过之前帧的ID两种方式,获取与手关联的手指和工具。 通过列表方法:
// hand is a Leap::Hand object Leap::PointableList pointables = hand.pointables(); // Both fingers and tools Leap::FingerList fingers = hand.fingers(); Leap::ToolList tools = hand.tools();
通过之前帧ID方法: Leap::Pointable knownPointable = hand.pointable(pointableID); 为了获取手指或者工具在Leap视野的相对位置,可以使用匹配列表类中right-,left-和frontmost函数。
// hand is a Leap::Hand object Leap::Pointable leftPointable = hand.pointables().leftmost(); Leap::Finger rightFinger = hand.fingers().rightmost(); Leap::Tool frontTool = hand.tools().frontmost();
注意,这些函数都是相对于Leap Motion原点位置的,而不是手它自己的。为了获取相对于手的手指,你可以使用Leap矩阵类来讲手指坐标转换到参考帧的手。[不太懂]
使用手的方向和垂直向量,你可以计算出手的朝向角度(相对于东方)。向量类定义了俯仰Pitch旋转(围绕x轴旋转)、左右Yaw旋转(围绕y轴的旋转)和平面Roll旋转(围绕z轴旋转):
float pitch = hand.direction().pitch(); float yaw = hand.direction().yaw(); float roll = hand.palmNormal().roll();
注意,roll函数仅仅提供使用垂直向量时的预判的角度。[看来roll判断比较困难吧]
有时,通过手的参照帧获取在手上的手指坐标是常用的方法。这种方法可以让你对手指在空间上进行排序,并且可以简单的分析手指的坐标[很有用啊]。你可以使用Leap矩阵类构建一个转换矩阵来转换手指坐标和方向坐标。参照中的手帧可以通过手的朝向和手掌的垂直向量(由与两个坐标轴正交的第三个坐标轴)被有效的定义。这种方法使得x轴沿着手侧面,而z轴指向前方,y轴与手掌平面平行。
Leap::Frame frame = leap.frame(); for( int h = 0; h < frame.hands().count(); h++ ) { Leap::Hand leapHand = frame.hands()[h]; Leap::Vector handXBasis = leapHand.palmNormal().cross(leapHand.direction()).normalized(); Leap::Vector handYBasis = -leapHand.palmNormal(); Leap::Vector handZBasis = -leapHand.direction(); Leap::Vector handOrigin = leapHand.palmPosition(); Leap::Matrix handTransform = Leap::Matrix(handXBasis, handYBasis, handZBasis, handOrigin); handTransform = handTransform.rigidInverse(); for( int f = 0; f < leapHand.fingers().count(); f++ ) { Leap::Finger leapFinger = leapHand.fingers()[f]; Leap::Vector transformedPosition = handTransform.transformPoint(leapFinger.tipPosition()); Leap::Vector transformedDirection = handTransform.transformDirection(leapFinger.direction()); // Do something with the transformed fingers } }
尖端对象可以被表示为手指和工具 ---- 也就是可以指出方向的东西。 你可以通过手对象中特定的手,获取与手关联的手指和工具。尖端未必要和手对象关联 --- 物理上的手也许不在视野中或者被另一只手挡住了。因此帧中尖端列表可以包含哪些和手不关联的手指和工具[即增加了适用性,同时也引入了复杂性呀]。 尖端对象有着许多描述手指和工具的属性: #尖端坐标 --- 在Leap Motion坐标系下以毫米为单位的瞬时的坐标。 #尖端速度 --- 以毫米每秒为单位的瞬时速度。 #指向 --- 当前的指向朝向向量。 #长度 --- 手指和工具显现的长度。 #宽度 --- 平均宽度。 #触摸距离 --- 在虚拟触摸平面中归一后的距离。 #触摸区域 --- 当前尖端和虚拟触摸平面的关系。 下面的例子说明如何从帧中获取简短对象和基本访问属性的方法:
Leap::Pointable pointable = frame.pointables().frontmost(); Leap::Vector direction = pointable.direction(); float length = pointable.length(); float width = pointable.width(); Leap::Vector stabilizedPosition = pointable.stabilizedTipPosition(); Leap::Vector position = pointable.tipPosition(); Leap::Vector speed = pointable.tipVelocity(); float touchDistance = pointable.touchDistance(); Leap::Pointable::Zone zone = pointable.touchZone();
为了将尖端对象转化到合适的手指和工具子类,需要使用合适的手指和工具构造函数(某些时候你应该使用Leap类的构造函数)[不太明白]。
if (pointable.isTool()) { Leap::Tool tool = Leap::Tool(pointable); } else { Leap::Finger finger = Leap::Finger(pointable); }
如果你需要计算手指的基本位置,你可以如下使用手指尖位置和朝向:
Leap::Vector basePosition = -pointable.direction() * pointable.length(); basePosition += pointable.tipPosition();