【苉雅篇】Leap Motion 向左走,Processing 向右走

本文主要回答以下问题:

  • Leap Motion 是什么
  • Leap Motion 怎么玩
  • 开发 Leap Motion 要掌握的常识
  • Processing 怎么结合 Leap Motion
  • 如何动手开发两者结合的第一个程序

周末,邻居罗斯带给苉雅一件很奇怪的小物件。
这里写图片描述
罗斯神秘兮兮地说道,这是可以触摸到未来的魔法!

The Future is in Your Hands

苉雅看着罗斯正对着电脑胡乱地挥动着双手,感觉十分滑稽。罗斯说,他只不过是在用 Leap Motion 操控电脑。

苉雅:Leap Motion?

罗斯:Leap Motion 是现在世界上最强的虚拟和增强现实运动控制技术的创造者!该系统可以在高精确度和高跟踪帧率下,检测并跟踪手、手指和类似手指的工具。

苉雅:……

罗斯:The future is in your hands.

Play It

罗斯:要想玩转 Leap Motion,我们首先需要在电脑上安装 Leap Motion 的 SDK。

苉雅:那要怎么做呢?

Leap Motion 的安装步骤:

Leap Motion 的官网开发者界面上注册一个账号,然后下载 SDK。解压,安装。

最后运行 Leap Motion App Home,程序将会自动进入新手教程,另外还有一些 APP 等你尝试。

那么接下来,舞动你的双手,让我们一起摇!

注意:Leap Motion的可视范围是一个倒金字塔,塔尖在设备中心。

基础知识

很快,苉雅就迷上了这个小巧而强大的方块。罗斯告诉苉雅,Leap Motion 和 Processing 是能够结合起来的,但在正式开发前,她可能需要了解一些小知识(参看文章,点击这里):

Coordinate system

Leap Motion 的坐标系原点位于 Leap Motion 控制器镜面的中心。以这个中心平行设备向右,为 X 轴的正方向;垂直 X 轴指向玩家,为 Z 轴的正方向;垂直镜面向上,为 Y 轴的正方向,如下图所示:
这里写图片描述
也就是右手坐标系,下图方便大家记忆:


这里写图片描述

Leap Motion API 用以下单位测量物理量:
距离: 毫米
时间: 微秒(除非另有说明)
速度: 毫米/秒
角度: 弧度

Motion tracking data

倘若 Leap Motion 设备在其视野中追踪手、手指和工具,它将提供一组数据集(或者是帧,或者是数据)进行更新。每帧数据包含一个基本追踪数据列表,如手、手指和工具,也包括识别出的手势和描述场景中的运动因素。
【注释:帧是 Leap Motion 最根本的数据模型,可以把它理解为一个集合,其他的实体数据都存放在这个集合里,比如手、手指、手势等。】

当检测到手、手指、工具或手势时,Leap Motion 将为它分配一个唯一的 ID 指示符。只要这个实体一直存在于设备的可视范围内,这个 ID 指示符就保持不变【和 Kinect 的骨骼追踪的 ID 是一致的】。如果追踪目标丢失或者失而复得,Leap Motion 会分配一个新的 ID 指示符(软件无法知道手、手指是否和之前看到的一样)【也就是说,不包含手指识别啦,和 Kinect 的骨骼追踪在丢失后情况完全一致】。

这里写图片描述
图片:XBOX 360 Kinect Sensor

Hands

手模型提供关于手,或者其他被检测出来的手指或工具的坐标、特征和运动。
Leap Motion 使用人手的内部模型来提供预测性追踪,即使手部分不可见时也是如此。手模型总是提供五个手指的位置,但是当手的轮廓和其手指清晰可见时,其跟踪效果是最佳的。
这里写图片描述
Leap Motion 的接口函数尽可能多地提供关于一个手的信息。也就是说,Leap Motion 可能无法计算出在每帧下的所有手部信息。例如,当一个手握成一个拳头,它的手指则无法被 Leap Motion 看到,所以手指的信息就为空。
如果一个人的手或其他手状物体出现在视野范围内,则手部列表中可能会出现多于两只手的情况。但是,我建议在 Leap Motion 的视野中至多保留两只手,以获得最佳运动跟踪质量。

Arms

一个臂是骨状物体。数据集提供了一个臂的取向、长度、宽度和结束点。当肘部不在视野内时,Leap Motion 控制器根据过去的观察结果以及典型的人体比例来估计其位置。

Fingers

Leap Motion 可以提供手的每个手指的信息。如果全部或部分手指不可见,则基于最近的观察和手的解剖模型来估计手指特征。每个手指由类型名称标识。
手指由 Finger 类表示。
这里写图片描述
指尖位置和方向矢量提供指尖的位置和手指指向的大致方向

一个 Finger 对象提供了描述每个解剖手指骨的位置和定向的 Bone 对象。所有的手指都包含从基座到尖端排列的四根骨头。

这里写图片描述
骨骼被识别为:

  • 掌骨 - 将手指连接到手腕上的骨头(拇指除外)
  • 近节指骨 - 手指根部的骨骼,与手掌相连
  • 中节指骨 - 指尖和基部之间的手指中间骨
  • 远节指骨 - 手指末端的终端骨骼

这种拇指模型与标准解剖命名系统不太匹配。真正的拇指比其他手指少一根骨头。然而,为了便于编程,Leap Motion 拇指模型包括零长度的掌骨,以便拇指具有与其他手指相同索引的相同数量的骨骼。因此,拇指的解剖掌骨被标记为近节指骨并且解剖近侧指骨被标记为 Leap Motion 手指骨模型中的中节节骨。

Tool

Leap Motion 会把细长笔直的圆柱形物体当作工具,比如铅笔。
相关类:Tool,Tool 是 Pointable 类的子类。

Gesture

在 Leap Motion 的世界,除了记录我们手的坐标、方向的数据,还能根据我们每一个手指和工具的运动,识别出我们的某些手势,比如:画圆、挥手、按键、点击屏幕。
相关类:Gesture以及Gesture的子类:CircleGesture、SwipeGesture、KeyTapGesture、ScreenTapGesture。

这里写图片描述

Sensor Images

随着计算出的跟踪数据,您可以从 Leap Motion 相机中获取原始传感器图像。
这里写图片描述
具有叠加校准点的原始传感器图像

Leap Motion For Processing

Leap Motion for Processing 是一个用于结合 Processing 和 Leap Motion 的库,由 Darius Morawiec 开发,倘若对遇到的问题已然束手无策,你可以发邮件给他。

Installation

  • 方法一:自动安装——

Sketch > Import library … > Add library … > Filter: “Leap Motion”

在使用这个库编程时,请保证 Leap Motion 已经可以在你的电脑上运行且处于开启状态。至于版本匹配问题,请到这里查看。

Usage

(1)Basics

以下示例展示了基本数据访问:

import de.voidplus.leapmotion.*;  //导入库

// ======================================================
// 目录:
// ├─ 1. 回调
// ├─ 2. 手
// ├─ 3. 手臂
// ├─ 4. 手指
// ├─ 5. 骨
// ├─ 6. 工具
// └─ 7. 设备
// ======================================================

//声明LeapMotion的对象
LeapMotion leap;

void setup() {
  size(800, 500);
  background(255);
  // ...

  leap = new LeapMotion(this);
}


// ======================================================
// 1. 回调

void leapOnInit() {
  // println("Leap Motion 初始化");
}
void leapOnConnect() {
  // println("Leap Motion 连接");
}
void leapOnFrame() {
  // println("Leap Motion 帧");
}
void leapOnDisconnect() {
  // println("Leap Motion 未连接");
}
void leapOnExit() {
  // println("Leap Motion 退出");
}


void draw() {
  background(255);
  // ...

  //获取帧频
  int fps = leap.getFrameRate();
  for (Hand hand : leap.getHands ()) {

    // ==================================================
    // 2. 手

    int     handId             = hand.getId();
    PVector handPosition       = hand.getPosition();
    PVector handStabilized     = hand.getStabilizedPosition();
    PVector handDirection      = hand.getDirection();
    PVector handDynamics       = hand.getDynamics();
    float   handRoll           = hand.getRoll();
    float   handPitch          = hand.getPitch();
    float   handYaw            = hand.getYaw();
    boolean handIsLeft         = hand.isLeft();
    boolean handIsRight        = hand.isRight();
    float   handGrab           = hand.getGrabStrength();
    float   handPinch          = hand.getPinchStrength();
    float   handTime           = hand.getTimeVisible();
    PVector spherePosition     = hand.getSpherePosition();
    float   sphereRadius       = hand.getSphereRadius();

    // --------------------------------------------------
    // 绘制
    hand.draw();

    // ==================================================
    // 3. 手臂

    if (hand.hasArm()) {
      Arm     arm              = hand.getArm();
      float   armWidth         = arm.getWidth();
      PVector armWristPos      = arm.getWristPosition();
      PVector armElbowPos      = arm.getElbowPosition();
    }

    // ==================================================
    // 4. 手指

    Finger  fingerThumb        = hand.getThumb();
    // or                        hand.getFinger("thumb");
    // or                        hand.getFinger(0);

    Finger  fingerIndex        = hand.getIndexFinger();
    // or                        hand.getFinger("index");
    // or                        hand.getFinger(1);

    Finger  fingerMiddle       = hand.getMiddleFinger();
    // or                        hand.getFinger("middle");
    // or                        hand.getFinger(2);

    Finger  fingerRing         = hand.getRingFinger();
    // or                        hand.getFinger("ring");
    // or                        hand.getFinger(3);

    Finger  fingerPink         = hand.getPinkyFinger();
    // or                        hand.getFinger("pinky");
    // or                        hand.getFinger(4);


    for (Finger finger : hand.getFingers()) {
      // or              hand.getOutstretchedFingers();
      // or              hand.getOutstretchedFingersByAngle();

      int     fingerId         = finger.getId();
      PVector fingerPosition   = finger.getPosition();
      PVector fingerStabilized = finger.getStabilizedPosition();
      PVector fingerVelocity   = finger.getVelocity();
      PVector fingerDirection  = finger.getDirection();
      float   fingerTime       = finger.getTimeVisible();

      // ------------------------------------------------
      // 绘制

      // Drawing:
      // finger.draw();  // Executes drawBones() and drawJoints()
      // finger.drawBones();
      // finger.drawJoints();

      // ------------------------------------------------
      // 选择

      switch(finger.getType()) {
      case 0:
        // System.out.println("thumb");
        break;
      case 1:
        // System.out.println("index");
        break;
      case 2:
        // System.out.println("middle");
        break;
      case 3:
        // System.out.println("ring");
        break;
      case 4:
        // System.out.println("pinky");
        break;
      }


      // ================================================
      // 5. 骨
      // --------
      // https://developer.leapmotion.com/documentation/java/devguide/Leap_Overview.html#Layer_1

      //骨末节
      Bone    boneDistal       = finger.getDistalBone();
      // or                      finger.get("distal");
      // or                      finger.getBone(0);

      Bone    boneIntermediate = finger.getIntermediateBone();
      // or                      finger.get("intermediate");
      // or                      finger.getBone(1);

      //骨近端
      Bone    boneProximal     = finger.getProximalBone();
      // or                      finger.get("proximal");
      // or                      finger.getBone(2);

      //骨掌骨
      Bone    boneMetacarpal   = finger.getMetacarpalBone();
      // or                      finger.get("metacarpal");
      // or                      finger.getBone(3);

      // ------------------------------------------------
      // 触摸仿真

      int     touchZone        = finger.getTouchZone();
      float   touchDistance    = finger.getTouchDistance();

      switch(touchZone) {
      case -1: // 无
        break;
      case 0: //悬停
        // println("Hovering (#" + fingerId + "): " + touchDistance);
        break;
      case 1: // 触摸
        // println("Touching (#" + fingerId + ")");
        break;
      }
    }


    // ==================================================
    // 6. 工具

    for (Tool tool : hand.getTools()) {
      int     toolId           = tool.getId();
      PVector toolPosition     = tool.getPosition();
      PVector toolStabilized   = tool.getStabilizedPosition();
      PVector toolVelocity     = tool.getVelocity();
      PVector toolDirection    = tool.getDirection();
      float   toolTime         = tool.getTimeVisible();

      // ------------------------------------------------
      // 绘制:
      // tool.draw();

      // ------------------------------------------------
      // 触摸仿真

      int     touchZone        = tool.getTouchZone();  //触摸区域
      float   touchDistance    = tool.getTouchDistance();  //触摸距离

      switch(touchZone) {
      case -1: // 无
        break;
      case 0: // 悬停
        // println("Hovering (#" + toolId + "): " + touchDistance);
        break;
      case 1: // 触摸
        // println("Touching (#" + toolId + ")");
        break;
      }
    }
  }

  // ====================================================
  // 7. 设备

  for (Device device : leap.getDevices()) {
    float deviceHorizontalViewAngle = device.getHorizontalViewAngle();  //设备水平视角
    float deviceVericalViewAngle = device.getVerticalViewAngle();  //设备垂直视角
    float deviceRange = device.getRange();  //设备范围
  }
}

(2)Gestures

以下示例显示如何识别预定义的手势:

import de.voidplus.leapmotion.*;

// ======================================================
// 目录:
// ├─ 1. 滑动手势
// ├─ 2. 转圈手势
// ├─ 3. 屏幕点击手势
// └─ 4. 按键手势
// ======================================================

LeapMotion leap;

void setup() {
  size(800, 500);
  background(255);
  // ...

  leap = new LeapMotion(this).allowGestures();  // 所有手势
  // leap = new LeapMotion(this).allowGestures("circle, swipe, screen_tap, key_tap");
  // leap = new LeapMotion(this).allowGestures("swipe");  // Leap只检测滑动
}

void draw() {
  background(255);
  // ...
}

// ======================================================
// 1. 滑动手势

void leapOnSwipeGesture(SwipeGesture g, int state) {
  int     id               = g.getId();
  Finger  finger           = g.getFinger();
  PVector position         = g.getPosition();
  PVector positionStart    = g.getStartPosition();
  PVector direction        = g.getDirection();
  float   speed            = g.getSpeed();
  long    duration         = g.getDuration();
  float   durationSeconds  = g.getDurationInSeconds();

  switch(state) {
  case 1: // 开始
    break;
  case 2: // 更新
    break;
  case 3: // 停止
    println("滑动手势: " + id);
    break;
  }
}

// ======================================================
// 2.转圈手势

void leapOnCircleGesture(CircleGesture g, int state) {
  int     id               = g.getId();
  Finger  finger           = g.getFinger();
  PVector positionCenter   = g.getCenter();
  float   radius           = g.getRadius();
  float   progress         = g.getProgress();
  long    duration         = g.getDuration();
  float   durationSeconds  = g.getDurationInSeconds();
  int     direction        = g.getDirection();

  switch(state) {
  case 1: // 开始
    break;
  case 2: // 更新
    break;
  case 3: // 停止
    println("转圈手势: " + id);
    break;
  }

  switch(direction) {
  case 0: // 逆时针/左手
    break;
  case 1: // 顺时针/右手
    break;
  }
}

// ======================================================
// 3. 屏幕点击手势

void leapOnScreenTapGesture(ScreenTapGesture g) {
  int     id               = g.getId();
  Finger  finger           = g.getFinger();
  PVector position         = g.getPosition();
  PVector direction        = g.getDirection();
  long    duration         = g.getDuration();
  float   durationSeconds  = g.getDurationInSeconds();

  println("屏幕点击手势: " + id);
}

// ======================================================
// 4. 按键手势

void leapOnKeyTapGesture(KeyTapGesture g) {
  int     id               = g.getId();
  Finger  finger           = g.getFinger();
  PVector position         = g.getPosition();
  PVector direction        = g.getDirection();
  long    duration         = g.getDuration();
  float   durationSeconds  = g.getDurationInSeconds();

  println("按键手势: " + id);
}

(3)Camera-Images

此外,你可以访问原始相机图像:

import de.voidplus.leapmotion.*;

LeapMotion leap;

void setup() {
  size(640, 480);
  background(255);
  leap = new LeapMotion(this);
}

void draw() {
  background(255);
  // ...

  if (leap.hasImages()) {
    for (Image camera : leap.getImages()) {
      if (camera.isLeft()) {
        // Left camera
        image(camera, 0, 0);
      } else {
        // Right camera
        image(camera, 0, camera.getHeight());
      }
    }
  }
}

Creating a Simple Sketch

终于可以自己动手了,可怜的苉雅,看到长长的理论知识,忍不住打起了盹。

现在,我们使用 Leap Motion for Processing 库创建一个简单的草图来确保其正常运行吧:

  • 创建一个新的草图。
  • 在Processing菜单中,选择Sketch> Import Library>Leap Motion for Processing
import de.voidplus.leapmotion.*;
  • 添加代码以创建一个 Controller 对象:
Controller controller = new Controller();
  • 在草图draw()功能中,从控制器获取一帧跟踪数据:
Frame frame = controller.frame();
  • 最后,通过添加以下代码来显示手和手指的数量:
text( frame.hands().count() + " Hands", 50, 50 );
text( frame.fingers().count() + " Fingers", 50, 100 );

合在一起,你的代码应该是这样的:

import de.voidplus.leapmotion.*;

Controller controller = new Controller();

void setup(){
  size( 200, 200 );
}

void draw(){
  background(0);
  Frame frame = controller.frame();
  text( frame.hands().count() + " Hands", 50, 50 );
  text( frame.fingers().count() + " Fingers", 50, 100 );
}

Last…

倘若你遇到了无可描述的问题,欢迎和我一起探讨。

posted @ 2020-01-12 10:00  升卿  阅读(643)  评论(1编辑  收藏  举报