2013年7月30日火曜日

LEAP MOTION + openFrameworks 手のモデル解説



LEAP MOTIONの最初の導入時に、下のスクリーンショットの様に手を動かすチュートリアルがありました。


LEAP MOTIONのSDKを介して、センサーから手に関する様々なステータスを取得する事が出来ますが、具体的にどのようにすれば、上記のチュートリアルの様なボーンを作る事が出来るのか調べてみました。
LEAP MOTIONから取得できる情報
LEAP MOTIONのデバイスから取得する事が出来る情報は、以下の構造になっています。


Frameは瞬間的な手に関する情報を保持したオブジェクトで、最大60個まで遡って参照する事が出来ます。遡って一連の手の動きがどのようなものだったかを検証する事によって、独自のジェスチャーを検出すると言った事が可能なのでしょう。

Frameオブジェクトを取得するには、以下の様なコードを書きます。
// Leap::Controllerから最新のFrameを取得
const Frame frame = controller.frame();


また、過去のFrameオブジェクトを取得するには、引数に何フレーム前であるかのパラメーターを指定します。
// Leap::Controllerから10個前のFrameを取得
const Frame frame = controller.frame(10);

Handオブジェクト
FrameオブジェクトからはHandオブジェクトのリストを取得する事が出来ます。
これは検知した手の数だけ(最大4個)取得する事が可能です。


Handオブジェクトはfingers()を介して、その手に紐付くFinger(指)のリストを取得する事が出来ます。また手でペンや棒など持っていた場合、Toolオブジェクトとして取得する事も出来ます。

例として、検知した手と指に対して描画処理をする場合、以下の様なコードでループして処理をする事が可能です。
    // フレームを取得
    Frame frame = controller.frame();
    // Handをあるだけ列挙
    for(int i=0; i<frame.hands().count(); i++) {
        Hand hand = frame.hands()[i];
        // Hand内のFingerをあるだけ描画
        for(int j=0; j<hand.fingers().count(); j++) {
            Finger finger = frame.fingers()[j];
            drawFinger(finger);
        }
        // Handを描画
        drawPalm(hand);
    }
Handオブジェクトの状態
Handオブジェクトの状態を表すステータスとして、Handオブジェクトから以下のプロパティを取得する事が出来ます。


palmPosition
掌の中心部のXYZ座標を表しています。

palmNormal
掌のLEAP MOTIONの水平面に対する角度(ベクトル)を表しています。

direction
掌が指す先の角度を表しています。
Fingerオブジェクトの状態
指の状態を表すFingerオブジェクトのステータスとしては、以下のプロパティを取得する事が出来ます。左手の小指を例にします。



tipPosition
指先のXYZ座標を表しています。

direction
指先の角度(ベクトル)を表しています。

direction
指の長さを表しています。
Handの状態を3Dオブジェクトに反映
ここまでのHandとFingerの状態を入力値として、openFrameworkの3Dオブジェクトに反映してみました。動画をご覧下さい。

 

LEAP MOTIONのチュートリアルまでは再現出来ていませんが、以前よりも手に近い動きを表す事が出来るようになりました。
サンプルコード
参考までに、上記のデモのコードです。
LEAP MOTION + openFrameworksの開発環境の構築については、前回の記事をご覧ください。

testApp.h
#pragma once

#include "ofMain.h"
#include "Leap.h"

using namespace Leap;

class testApp : public ofBaseApp{
    
public:
    void setup();
    void update();
    void draw();
    
    void keyPressed  (int key);
    void keyReleased(int key);
    void mouseMoved(int x, int y );
    void mouseDragged(int x, int y, int button);
    void mousePressed(int x, int y, int button);
    void mouseReleased(int x, int y, int button);
    void windowResized(int w, int h);
    void dragEvent(ofDragInfo dragInfo);
    void gotMessage(ofMessage msg);
    
    Controller controller;
    ofCamera camera;
    float camdistance;
    float camdegree;
    void drawFinger(Finger finger);
    void drawPoint(ofPoint point);
    void drawFingerBox(Finger finger, ofPoint tip, ofPoint base);
    void drawPalm(Hand hand);
};

testApp.cpp
#include "testApp.h"
#include "Poco/Mutex.h"

//--------------------------------------------------------------
void testApp::setup(){
    // カメラの初期位置と方向を指定
    camdistance = ofGetWidth()/4;
    camdegree = 90;
    camera.setFov(60);
    camera.setPosition(0, 200, camdistance);
    camera.lookAt(ofVec3f(0, 200, 0));
}

//--------------------------------------------------------------
void testApp::update(){
}

//--------------------------------------------------------------
void testApp::draw(){
    
    camera.begin();
    // 背景を黒に塗りつぶし
    ofBackground(0, 0, 0);
    
    // フレームを取得
    Frame frame = controller.frame();
    // Handをあるだけ列挙
    for(int i=0; i<frame.hands().count(); i++) {
        Hand hand = frame.hands()[i];
        // Hand内のFingerをあるだけ描画
        for(int j=0; j<hand.fingers().count(); j++) {
            Finger finger = frame.fingers()[j];
            drawFinger(finger);
        }
        // Handを描画
        drawPalm(hand);
    }
    camera.end();
}

void testApp::drawFinger(Finger finger) {

    // 指先の点を描画
    ofPoint tip(finger.tipPosition().x, finger.tipPosition().y, finger.tipPosition().z);
    drawPoint(tip);

    // 指の付け根の座標を計算
    ofPoint base = ofPoint(tip.x + finger.direction().x * finger.length() * -1,
                           tip.y + finger.direction().y * finger.length() * -1,
                           tip.z + finger.direction().z * finger.length() * -1);
    // 指の付け根を描画
    drawPoint(base);
    
    // 指先から付け根に線を描く
    ofLine(tip.x, tip.y, tip.z, base.x, base.y, base.z);

    // 付け根から掌に線を描く
    ofLine(base.x, base.y, base.z,
           finger.hand().palmPosition().x,
           finger.hand().palmPosition().y,
           finger.hand().palmPosition().z);
    
    // 指の箱を描画
    drawFingerBox(finger, tip, base);
}

// 点を描画
void testApp::drawPoint(ofPoint point) {

    ofPushMatrix();
    ofTranslate(point);
    ofNoFill();
    ofSetColor(0xFF, 0xFF, 0xFF, 255);
    ofSphere(3);
    ofPopMatrix();
}

// 指の箱を描画
void testApp::drawFingerBox(Finger finger, ofPoint tip, ofPoint base) {
    
    // 指の中間の座標
    ofPoint middle = base.middle(tip);
    ofPushMatrix();
    ofTranslate(middle);
    
    // 指の方向に従い回転
    ofQuaternion quat;
    quat.makeRotate(ofPoint(0, -1, 0), ofPoint(finger.direction().x, finger.direction().y, finger.direction().z));
    ofMatrix4x4 matrix;
    quat.get(matrix);
    glMultMatrixf(matrix.getPtr());
    
    ofNoFill();
    ofSetColor(0xCC,0,0,255);
    ofScale(1, finger.length()/10, 1);
    ofBox(10);
    ofPopMatrix();
}

void testApp::drawPalm(Hand hand) {
    // 掌の描画処理
    
    ofPoint point = ofPoint(hand.palmPosition().x, hand.palmPosition().y, hand.palmPosition().z);
    drawPoint(point);
    
    ofPushMatrix();
    ofTranslate(point);
    
    // 掌を回転
    ofQuaternion quat;
    quat.makeRotate(ofPoint(0, -1, 0), ofPoint(hand.palmNormal().x, hand.palmNormal().y, hand.palmNormal().z));
    ofMatrix4x4 matrix;
    quat.get(matrix);
    glMultMatrixf(matrix.getPtr());

    ofNoFill();
    ofSetColor(0xCC,0x0,0x0,255);
    ofScale(1, 0.25, 1.0);
    ofBox(0, 0, 0, 60);

    ofPopMatrix();
}

//--------------------------------------------------------------
void testApp::keyPressed(int key){

    switch (key) {
        case 358: // 右
            camdegree += 5;
            if(camdegree > 360) camdegree = 0;
            break;
        case 356: // 左
            camdegree -= 5;
            if(camdegree < 0) camdegree = 360;
            break;
        case 357: // 上
            camdistance -= 10;
            break;
        case 359: // 下
            camdistance += 10;
            break;
        default:
            break;
    }
 
    float radian = ofDegToRad(camdegree);
    float x = camdistance * cos(radian);
    float z = camdistance * sin(radian);

    camera.setPosition(x, camera.getY(), z);
    camera.lookAt(ofVec3f(0, 200, 0));
}

//--------------------------------------------------------------
void testApp::keyReleased(int key){
}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y ){
}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){
}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){
}

//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){
}

//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){
}



LEAP MOTION関連記事:
LEAP MOTION + openFrameworksでアプリケーション開発
・LEAP MOTION + openFrameworks 手のモデル解説
LEAP MOTION + openFrameworks 手のモデル改良