Documentation
Getting Frame Data
The Leap Motion API presents motion tracking data to your application as a series of snapshots called frames. Each frame of tracking data contains the measured positions and other information about each entity detected in that snapshot. This article discusses the details of gettting Frame objects from the Leap Motion controller.
Topics:
Overview
Get a Frame object containing tracking data from a connected Controller object. You can get a frame whenever your application is ready to process it using the frame() method of the Controller class:
if( controller.isConnected()) //controller is a Controller object
{
Frame frame = controller.frame(); //The latest frame
Frame previous = controller.frame(1); //The previous frame
}
The frame() function takes a history parameter that indicates how many frames back to retrieve. Typically, the last 60 frames are maintained in the history buffer.
Getting Frames by Polling
Polling the Controller object for frames is the simplest and often best strategy when your application has a natural frame rate. You just call the Controller frame() function when your application is ready to process a frame of data.
When you use polling, there is a chance that you will get the same frame twice in a row (if the application frame rate exceeds the Leap frame rate) or skip a frame (if the Leap frame rate exceeds the application frame rate). In many cases, missed or duplicated frames are not important. For example, if you are moving an object on the screen in response to hand movement, the movement should still be smooth (assuming the overall frame rate of your application is high enough).
To detect whether you have already processed a frame, save the ID value assigned to the last frame processed and compare it to the current frame:
int64_t lastFrameID = 0;
void processFrame( Leap::Frame frame )
{
if( frame.id() == lastFrameID ) return;
//...
lastFrameID = frame.id();
}
If your application has skipped frames, you can use the history parameter of the frame() function to access the skipped frames (as long as the Frame object is still in the history buffer):
int64_t lastProcessedFrameID = 0;
void nextFrame( Leap::Controller controller )
{
int64_t currentID = controller.frame().id();
for( int history = 0; history < currentID - lastProcessedFrameID; history++)
{
processFrame( controller.frame(history) );
}
lastProcessedFrameID = currentID;
}
void processNextFrame( Leap::Frame frame )
{
if( frame.isValid() )
{
//...
}
}
Getting Frames with Callbacks
Alternatively, you can use a Listener object to get frames at the Leap Motion controller's frame rate. The Controller object calls the Listener's onFrame() function when a new frame is available. In the onFrame handler, you can call the Controller frame() function to get the Frame object itself.
Using the Listener callbacks is more complex because the callbacks are multi-threaded; each callback is invoked on an independent thread. You must ensure that any data accessed by multiple threads is handled in a thread-safe manner.
The following example defines a minimal Listener subclass that handles new frames of data:
class FrameListener : Leap::Listener
{
void onFrame(Controller &controller)
{
Frame frame = controller.frame(); //The latest frame
Frame previous = controller.frame(1); //The previous frame
//...
}
};
As you can see, getting the tracking data through a Listener object is otherwise the same as polling the controller.
Note that it is possible to skip a frame even when using Listener callbacks. If your onFrame callback function takes too long to complete, then the next frame is added to the history, but the onFrame callback is skipped. Less commonly, if the Leap software itself cannot finish processing a frame in time, that frame can be abandoned and not added to the history. This problem can occur when a computer is bogged down with too many other computing tasks.
Getting Data from a Frame
The Frame class defines several functions that provide access to the data in the frame. For example, the following code illustrates how to get the basic objects tracked by the Leap Motion system:
Leap::Controller controller = Leap::Controller();
// wait until Controller.isConnected() evaluates to true
//...
Leap::Frame frame = controller.frame();
Leap::HandList hands = frame.hands();
Leap::PointableList pointables = frame.pointables();
Leap::FingerList fingers = frame.fingers();
Leap::ToolList tools = frame.tools();
The objects returned by the Frame object are all read-only. You can safely store them and use them in the future. They are thread-safe. Internally, the objects use the C++ Boost library shared pointer class.
Using IDs to track entities across frames
If you have an ID of an entity from a different frame, you can get the object representing that entity in the current frame. Pass the ID to the Frame function of the appropriate type:
Hand hand = frame.hand(handID);
Pointable pointable = frame.pointable(pointableID);
Finger finger = frame.finger(fingerID);
Tool tool = frame.tool(toolID);
If an object with the same ID cannot be found -- perhaps a hand or finger moved out of the Leap field of view -- then a special, invalid object is returned instead. Invalid objects are instances of the appropriate class, but all their members return 0 values, zero vectors, or other invalid objects. This technique makes it more convenient to chain method calls together. For example, the following code snippet averages finger tip positions over several frames:
//Average a finger position for the last 10 frames
int count = 0;
Leap::Vector average = Leap::Vector();
Leap::Finger fingerToAverage = frame.fingers()[0];
for( int i = 0; i < 10; i++ )
{
Leap::Finger fingerFromFrame = controller.frame(i).finger(fingerToAverage.id());
if( fingerFromFrame.isValid() )
{
average += fingerFromFrame.tipPosition();
count++;
}
}
average /= count;
Without invalid objects, this code would have to check each Frame object before checking the returned Finger objects.