手のボーントラッキングが可能になった Leap Motion SDK V2 Beta の Unity サンプルについて調べてみた - 凹みTips

凹みTips

C++、JavaScript、Unity、ガジェット等の Tips について雑多に書いています。

手のボーントラッキングが可能になった Leap Motion SDK V2 Beta の Unity サンプルについて調べてみた

はじめに

Leap Motion SDK V2 Beta がリリースされました。

最大の特徴は Skeletal Tracking (手のボーンをベースとしたトラッキング)が可能になった点で、Leap Motion の問題点として挙げられていた、すぐにロストしてしまう問題(例えば手を縦にすると指が見えなくなる等)がこれにより大幅に改良されました。

公式では、JavaScript、Unity/C#C++JavaPythonObjective-C の 6 つの環境での SDK を配布していますが、本エントリでは簡単な新機能紹介と、Unity 向けの SDK に注目して、簡単に調べてみた内容を紹介したいと思います。

デモ

サンプルを Oculus Rift と組み合わせてみただけですが、かなり気持ちいいです。

インストールと概要

Leap Motion のアカウントでログインして V2 Beta のインストーラを実行するだけです。Leap Motion のデータは、Leap Service という裏で立ち上がっているサービスが USB から直接やってきたデータを処理・送出する役目を果たしており、そこからのデータを主に以下の2つの方法でハンドルする形になっています。

  1. C++ / Objective-C の DLL およびそのラッパーライブラリ(JavaC#Python)からデータを取得
  2. Leap Service がポート 6437 で立ち上げている WebSocket サーバを経由してデータを取得(JavaScript

参考: System Architecture — Leap Motion C# SDK v3.2 Beta documentation

セットアップ後は、各環境に応じたライブラリをダウンロードして使用する形になります。Unity 用としては Pro 版と Free 版が以下に用意されています。

Pro 版は unitypackage になっているため、自プロジェクトを開いた状態で開いてインポート、Free 版はサンプルプロジェクトが zip で固められた形になっているので、解凍後に適当に自分のプロジェクトに必要なファイルを移動する形になります。ファイル構成は以下に記載があります。

(追記:2014/05/19) Asset Store にもあがっています。

余談ですが、Free 版は WebSocket で通信しているのかなと思ったのですがどうやら違うようで、どうやって Free 版で Managed でない部分をハンドルしている(もしくはしていない?)のか不明だったのでご存知のかたいらっしゃいましたら教えて下さい。

新機能一覧

下記サイトでエミュレーションされた動きを見ることが出来ます。

「Edit in JavaScript」をクリックすると、実際にオンラインエディタで編集したり、編集した結果を動かしたり出来ます。

見るのが面倒な人用に、以下ざっと画像でまとめます。

手のモデル認識

f:id:hecomi:20140517191308p:plain

ピンチの認識

f:id:hecomi:20140517191318p:plain

グラブの認識

f:id:hecomi:20140517191320p:plain

信頼度

f:id:hecomi:20140517191326p:plain

左右の手の識別

f:id:hecomi:20140517191333p:plain

指の識別

f:id:hecomi:20140517191337p:plain

関節の識別

f:id:hecomi:20140517191340p:plain

サンプルから色々理解する

ドキュメントについて

以下にドキュメントがあります。

量が多いので読むのは大変ですが、API Overview を読むと概要がつかめるので、ここは目を通しておいた方が良いと思います。

サンプルについて

Assets/LeapMotion/Scenes 下に6種類のサンプルシーンが含まれています。サンプルはApache 2.0 ライセンスになっています。

Cube Wave

f:id:hecomi:20140518154319p:plain

手で押すとブロックが下に下がって色の変わるキレイな波を表現できるサンプルです。構造を詳しく見ていきます。

まず、エントリポイントは MonoBehaviour を継承した HandController です。HandController は他のサンプルでも共通で使われているスクリプトで、手の生成から動きの制御、パラメタの取得などを担っています。ここでは以下の 5 つの public メンバが公開されています。

  • separateLeftRight
    • チェックすると左手と右手を別々のモデルで扱います。本サンプルではチェックが ON になっています。これはエディタスクリプトHandControllerEditor 内で使われています。
  • leftGraphicsModel
    • 左手のモデル、デフォルトではシーンの MetallicRiggedLeftHand
  • leftPhysicsModel
    • 左手の物理モデル(コライダ)、デフォルトでは ThickRigidHand
  • rightGraphicsModel
    • 左手のモデル、デフォルトではシーンの MetallicRiggedRightHand
  • rightPhysicsModel
    • 右手の物理モデル(コライダ)、デフォルトでは ThickRigidHand

Graphics モデルの方は、Update() で処理され、Physics モデルの方のが FixedUpdate() で処理されており、手が衝突判定とともに動く、というのが主な仕組みになっています。

そして CubeWave スクリプトで大量の立方体(Hierarchy 内にある RigidBody が付加された Cube オブジェクト)を生成、Update() でバネの挙動をエミュレーションしている形になります。

Kaleidoscope

f:id:hecomi:20140518155200p:plain

カレイドスコープとは万華鏡のことです。手のひらを複製して円状に並べてなんとも言えない気味の悪い画を作り出すことが出来ます。

仕組みは恐らく最も簡単で、Prefab 化した HandController を複製して円状に並べているだけです。Physics モデルはセットしていないので衝突も起こらず手が重なる形になります。

HandController の位置や向きを調整すれば手のオブジェクトも自動的にその座標系の中で動くようになるので、Leap Motion 本体の位置が正面に向いていない場合(縦に置いたり、HMD 等につけて見下ろした場合)も、わざわざ Script で座標変換しなくとも、Transform の調整で済む、という知見が得られます。

Lots of Blocks

f:id:hecomi:20140518155310p:plain

当たり判定のある手で大量のブロックをすくったり掴んだり出来るサンプルです。

これは Cube Wave よりシンプルで、Unity 組み込みの物理エンジンを使って立方体と手の間の衝突判定でわしゃわしゃしているだけになります。手のモデルは SkeletalHand を使用しています。

Magnetic Pinch

f:id:hecomi:20140518155357p:plain

ピンチ機能のサンプルになっており、つまむ動作でそこに引力が発生し、キューブを引きつけて持つことが出来ます。

手の Physics モデルに Hierarchy にある MagneticPinchHand を指定しています。このオブジェクトには MagneticPinch スクリプトがアタッチされていて、ここでピンチの検出をしています。少しシンプルにしたコードを示すと以下になります。

void Update()
{
    // 手の情報を取得
    Hand hand = GetComponent<HandModel>().GetLeapHand();

    // 親指の指先の位置
    Vector leap_thumb_tip = hand.Fingers[0 /* 親指 */].TipPosition;

    // ピンチを判断するためにトリガとなる距離は
    // 親指の付け根から第一関節までの距離を基準に計算する
    float proximal_length = hand.Fingers[0].Bone(Bone.BoneType.TYPE_PROXIMAL).Length;
    float trigger_distance = proximal_length * TRIGGER_DISTANCE_RATIO;

    // 親指以外の指の関節位置と親指の指先の位置の距離を調べて、
    // トリガとなる値よりも近かったらピンチフラグを ON にする
    bool trigger_pinch = false;
    for (int i = 1 /* 親指はスキップ */; i < HandModel.NUM_FINGERS && !trigger_pinch; ++i) {
        Finger finger = hand.Fingers[i];
        for (int j = 0; j < FingerModel.NUM_BONES && !trigger_pinch; ++j) {
            Vector leap_joint_position = finger.Bone((Bone.BoneType)j).NextJoint;
            if (leap_joint_position.DistanceTo(leap_thumb_tip) < trigger_distance) {
                trigger_pinch = true;
            }
        }
    }

    // ピンチしている位置を吸着点とする
    // この際、計算していた座標を Leap の世界から Unity の世界の座標に変換
    Vector3 pinch_position = transform.TransformPoint(leap_thumb_tip.ToUnityScaled());

    // ピンチしている最中でない且つピンチを検知したら OnPinch を発火
    // 逆の場合は OnRelease を発火
    if (trigger_pinch && !pinching_) {
        OnPinch(pinch_position);
    } else if (!trigger_pinch && pinching_)
        OnRelease();
    }
}

後は、OnPinch()Physics.OverlapSphere() したオブジェクトを拾ってきて Update() でそのオブジェクトに対して pinch_position 方向へ RigidBody.AddForce() すれば吸着されるというわけです。

なお、ToUnityScaled()Transform の拡張メソッドとして Assets/LeapMotion/Scripts/Utils/LeapUnityExtensions.cs 内にて定義されていて、Leap で扱う mm(ミリメートル)基準から Unity の m(メートル)基準に変換し、かつ z 方向をフリップしています。

namespace Leap
{
    public static class UnityVectorExtension
    {
          public const float INPUT_SCALE = 0.001f;
          public static readonly Vector3 Z_FLIP = new Vector3(1, 1, -1);

          public static Vector3 ToUnityScaled(this Vector leap_vector)
          {
            return INPUT_SCALE * FlipZ(ToVector3(leap_vector));
          }
    }
}

それにしても親指とその他の指の関節の位置が閾値以下だったら、みたいな直接的なコードが書けるの、なんかスゴイですね。

Pretty Poly Hand

f:id:hecomi:20140518155452p:plain

指の関節の動きをよく見れるサンプルになっています。なお、手のモデルとして指定されているのは PolyHand3 ですが、PolyHand2PolyHand1Prefabs/HandGraphics に入っており、数字が小さくなるほど太いモデルになります。

Transparency Confidence

f:id:hecomi:20140518160156p:plain

手の信頼度の検出のサンプルになっています。信頼度が低い(= 手っぽくない)ほど透明に近づいていきます。

Hierarchy View にある AlphaHand が手の Graphics モデルとして指定されていますが、これには ConfidenceTransparency スクリプトがアタッチされています。仕組みはとてもシンプルで以下の様な Update() を記述しているだけです。

void Update() {
    Hand leap_hand = GetComponent<HandModel>().GetLeapHand();

    if (leap_hand != null) {
        Color new_color = material.color;
        new_color.a     = leap_hand.Confidence;
        material.color  = new_color;
    }
}

おわりに

V2.0 Beta になり、より繊細かつロバストな手の認識が可能となったことから、Leap Motion の応用の幅が大幅に広がったと思います。精度面で諦めてしまった過去のネタとかを掘り起こして色々やってみたいと思います。