Ubuntu Weekly Recipe

第302回Ubuntuで3DモーションセンサLeap Motion』使う

『Leap Motion』とはUSBでPCに接続可能な小型で安価な3Dモーションセンサデバイスです。 Leap Motionを使用すれば手や指の動きでマウスやキーボードを使うことなくデスクトップやアプリケーションを操作することが可能です。 今回はUbuntuでLeap Motionを使用する方法を紹介します。

Leap Motionとは

Leap Motionとは、手と指、ペンなどの尖った道具を検出できる3Dモーションセンサです。手の有無や、指の本数、下記のジェスチャなどを検出することができます。

  • Circle(指をくるくる回す動き)
  • Swipe(手を横に振る動き)
  • Key Taps(キーボードのキーを叩くような指の動き)
  • Screen Taps(ディスプレイをつつく、タブレット端末をタップする様な動き)

サイズは、3インチ×1.2インチ×0.5インチとだいたい板ガムより一回り大きめ程度です。Leap Motionはセンサとして2つのカメラと3つの赤外線LEDを備えています。

手や指、ジェスチャの認識は、これらを実現するためには奥行きを含めた3次元の情報を取得することが(とくにジェスチャに関しては)必要です。Leap Motionでは2つのカメラをステレオカメラとして使用することで奥行きの情報を取得可能としています。具体的には、Leap Motion付属のソフトウェアが、2つのカメラが同時に撮影した画像の差分や、LEDが照射した赤外線の影などを手がかりにしてLeap Motion本体上空の左右(x軸⁠⁠・上下(y軸⁠⁠・前後(z軸)3次元情報の抽出を行います。更に3次元情報を付属ソフトウェアが処理して、手や指が存在しているのか、ジェスチャはあったのか、などの検知を行います。

3Dモーションセンサの代表格としてMicrosoftのKinectが存在しますが、Kinectとは下記の違いがあります。

Kinect
  • 全身の動作が対象
  • 分解能はLeap Motionに比べ相対的に低い
  • 公式ドライバ・SDKはWindowsのみ対応
  • TVやディスプレイの上に載せて使用する
Leap Motion
  • 手と指の動作が対象
  • 分解能はKinectと比べ相対的に高い(公称0.01mm単位)
  • 公式ドライバ・SDKがWindows/Mac OS X/Linuxに対応している
  • ディスプレイや、ノートPCの前に置いて使用する

Leap Motionを使用すれば、自分の手の動作のみでデスクトップや、Webブラウザを始めとする各種アプリケーションを操作することが可能です。

図1 Leap Motionの本体
図1 Leap Motionの本体

入手方法

Leap Motionは公式サイト「BUY」ページから入手できます。日本への発送にも対応しています。価格は筆者が確認した時点では本体が日本円で8,400円程度、送料と税金を合わせて1万2,000円程度です[1]⁠。

Amazon.co.jpにも並行輸入品が出品されていますが、価格も公式サイトから購入した場合と同程度なので、海外の通販が不安などの特別な理由がない限りは、公式サイトを利用したほうが良いでしょう。

Leap Motion本体以外にもHPからHP ENVY17-j100 Leap Motion SEが2013年12月9日(月)現在、同社オンラインストアで発売中です。Leap Motion本体と比べ高価であることや、そもそもUbuntuで内蔵のLeap Motionが使用できるのかなどの問題がありますが、興味のある方は購入してみましょう。

Ubuntuへの対応とインストール

前述のとおりLeap Motionにはデバイスドライバとサービスを含んだLeap Packageが公式で準備されており、debファイルをインストールするのみで利用することができます。

まず、公式サイトの「DEVS」ページへアクセスして、開発者として登録を行います。登録に際して必要なのはメールアドレスだけで、登録料や年会費は必要ありません。

アカウントの登録とサインインを済ましたら、インストーラのダウンロードページからLinuxのインストーラを選択してtarファイルをダウンロードします。筆者が確認した時点では、1.0.9.8409が最新バージョンとして利用可能でした。

インストーラをダウンロードしたら、任意の場所でファイルを下記コマンドを入力するか、GUIからアーカイブマネージャーを使用してtarを展開します。展開をしたらLeap_Packagesディレクトリにカレントディレクトリを移します。

$ tar -xvf Leap_Packages_1.0.9+8409_Linux.tar
$ cd Leap_Packages_1.0.9+8409_Linux

Leap_Packagesディレクトリにはi386/amd64向けのdebファイルと、READMEファイルが格納されています。debファイルをインストールすると以下のソフトウェアがインストールされます。

  • デバイスドライバ
  • デーモンとその起動スクリプト
  • コントロールパネル(GUIプログラム)
  • コントロールパネルから起動する検証・設定プログラム

Ubuntu 13.10ではamd64が推奨となっているため、今回はamd64版のdebファイルを用います。下記コマンドを入力するか、GUIからdebファイルをダブルクリックして、Ubuntuソフトウェアセンターからインストールを行います。

$ sudo dpkg -i Leap-1.0.9+8409-x64.deb

インストールに成功したら、Leap Motionを動かすために必要なデーモンプロセスleapdが起動しているか下記コマンドで確認します。

$ sudo service leapd status

「leapd start/running 」という文字が出力されればleapdの起動は成功しています。起動に失敗してしまっている場合は/var/log/syslogを「Leap」で検索して、Leap Motionに関係するプロセスにエラーが発生していないか確認してください。また、Leap Packageのインストールに成功した時点で、leapdはOS起動時に自動起動する設定となっているため、手動でleapdを起動・停止したい場合以外は、これ以降service leapd startなどの起動・停止のためのコマンドを入力する必要はありません。

次にGUIのコントロールパネルであるLeapControlPanelを起動します。この時点でLeap Motion本体をコンピューターに接続していないようであれば接続をしておきましょう。

$ LeapControlPanel &

初回起動時にはソフトウェア使用許諾契約書の受諾画面が表示されますので、内容を読み、理解した上で「受諾」をクリックします。

図2 ソフトウェア使用許諾契約書
図2 ソフトウェア使用許諾契約書

Leap Control Panelが起動すると、右上のインジケーターにLeap Motion本体の形をしたアイコンが現れ、Leap Motionが正常に接続されているとアイコンに緑色のランプが灯ります。

図3 Leap Control Panelのインジケーター。Leap Motion本体との接続に成功していると緑色のランプが点灯する
図3 Leap Control Panelのインジケーター。Leap Motion本体との接続に成功していると緑色のランプが点灯する

インジケーターのアイコンをクリックすると、デバイスの設定や、診断ビジュアライザーの起動、再キャリブレーション[2]が選択可能です。診断ビジュアライザーを起動すると、実際に手や指、ジャスチャーが検出されることが視覚的に確認できます[3]⁠。診断ビジュアライザーが動作すれば、Leap Motionを利用するための準備は完了です。

最後にLeap Control PanelをUnityのDashから呼び出せるように設定をしておきましょう。~/.local/share/applicationsの中にleap_control_panel.desktopというファイルを作成し、ファイルに以下の記述を追加して保存します[4]⁠。また、⁠自動起動するアプリケーション」にLeap Control Panelを登録して、OS起動時・ログイン時に自動で起動させるように設定してしまうのも良いでしょう。

[Desktop Entry]
Comment=Leap Control Panel
Terminal=false
Name=Leap Control Panel
Exec=/usr/bin/LeapControlPanel
Type=Application
Icon=
図4 診断ビジュアライザー。手指の動きや、モードによってはジェスチャーの認識、ペイント(指やペンの軌跡を表示する)が確認できる
図4 診断ビジュアライザー。手指の動きや、モードによってはジェスチャーの認識、ペイント(指やペンの軌跡を表示する)が確認できる

Leap Motionでアプリケーションを動かす

Leap Motionが使用できるようになったので、アプリケーションを操作してみましょう。

Leap MotionのアプリストアであるAir Spaceには沢山のアプリケーションが登録されています。しかし、残念なことにLinuxに対応したアプリケーションは筆者が確認した時点ではまだ存在していないようです[5]⁠。 2013年11月初旬のLinux版SDKのアップデートでメジャーバージョンがWindows版・Mac OS X版と同じになったため、近い将来Linux対応のアプリケーションが出品されるかもしれません。

幸いにして対応しているアプリケーションがまったくないわけではなく、たとえばLinux用のGoogle EarthはLeap Motionに対応しており[6]⁠、メニューバーの[ツール][オプション]でオプション画面を開き、⁠ナビゲーション]タブの[マウス以外のコントローラ][コントローラーを有効にする]にチェックを入れ、適用すると使用可能な状態になります。

また、Google Chrome/Chromiumの拡張機能Leap Motion Integrationは、手指の動きと、ジェスチャーでタブ操作や、スクロールを行うことができます。筆者はGoogle+などの縦に長いページのスクロール操作に重宝しています。

加えて、GitHub上にもUbuntuのデスクトップを操作するためのコードが有志の手によってアップロードされています。これは指先位置によってマウスポインターを移動し、ジェスチャーや掴む動作(手を握る)をトリガーとして、マウスイベントやショートカットキーの押下イベントをジェネレートするものです。

Leap Motionでプログラムを作成する

サンプルコードを動かす

折角SDKが準備されているのですから、Leap Motionを使用したプログラムをしてみましょう。そこで、今回はLeap Motionを使用したプログラムの基礎として、Leap Motion SDKにあるサンプルプログラムをビルドしてみましょう。

Leap MotionのDeveloper KitはC++と、C#、Java、JavaScript、Pythonに対応していますので、用途や連携するライブラリによって好きな言語を選択することができます。今回はC++で書かれたサンプルをビルドしてみます。ビルドを行うためにパッケージのインストールが必要ですので、下記コマンドを入力して予めインストールしておきましょう。

$ sudo apt-get install build-essential libgl1-mesa-dev libglu1-mesa-dev libX11-dev libXi-dev libfreetype6-dev libxinerama-dev libxcursor-dev libasound2-dev

今回はDeveloper KitのダウンロードページからLinux版のDeveloper Kitを選択してtar.gzをダウンロードします。筆者が確認した時点での最新バージョンは1.0.9.8391でした。

ダウンロードしたtgzを以下のコマンドを入力するか、GUIからアーカイブマネージャーを使用して展開します。

$ tar -zxvf LeapDeveloperKit_release_linux_1.0.9+8391.tgz

tgzファイルを展開すると、LeapDeveloperKitという名前のディレクトリが現れます。このLeapDeveloperKit内のLeapSDKディレクトリ内にサンプルコードや、インクルードファイル、共通ライブラリファイルが存在します。

次にサンプルコードのビルドを行います。ビルドの手順はいたって簡単で、DeveloperKit内のLeapSDKディレクトリにあるsamples内のMakefileでmakeし、バイナリを実行するだけです。

$ cd LeapDeveloperKit/LeapSDK/samples
$ make
$ ./Sample

サンプルコードは、Leap Motionが各フレーム毎に取得した手や、指、道具の本数、オブジェクトやジェスチャーがあった場合はその詳細情報を下記のように端末の標準出力に印字するプログラムです。

Frame id: 4439, timestamp: 204738476, hands: 1, fingers: 2, tools: 0, gestures: 2
Hand has 2 fingers, average finger tip position(2.26608, 119.957, 23.326)
Hand sphere radius: 51.9412 mm, palm position: (-39.6446, 104.173, 73.8909)
Hand pitch: 17.9848 degrees, roll: 10.6252 degrees, yaw: 30.5056 degrees
Circle id: 1, state: 2, progress: 6.91424, radius: 28.3288, angle 7.81488, counterclockwise
Circle id: 5, state: 2, progress: 1.08783, radius: 10.1404, angle 5.0886, counterclockwise

サンプルコードはonFrameという関数の中で情報を印字するために指の本数や手の有無、ジェスチャーの認識を行っています。ここに任意の部分で連携したいライブラリの関数や、コマンドを呼び出す操作を追加するとデスクトップや他のデバイスの制御ができます。

簡単な例として、⁠手が存在しかつ、指の本数が0のとき」はグー、⁠手が存在し、かつ指の本数が5のとき」はパーとし、状態が遷移したときにコマンドを発行するようにサンプルコードに処理を追加してやります。このときに割り当てるコマンドをejectコマンドにすると、スイッチやキーボードに触れることなくグーとパーで光学ドライブの開閉ができるようになります。具体的なコード(LeapEject.cpp)は下記です。

#include <stdlib.h>
#include <iostream>
#include "Leap.h"

using namespace Leap;

#define ROCK     0
#define PAPER    1

class MyListener : public Listener {
  public:
    virtual void onInit(const Controller&);
    virtual void onConnect(const Controller&);
    virtual void onDisconnect(const Controller&);
    virtual void onExit(const Controller&);
    virtual void onFrame(const Controller&);
    virtual void onFocusGained(const Controller&);
    virtual void onFocusLost(const Controller&);

  private:
    int before_handstate;
};

void MyListener::onInit(const Controller& controller) {
  before_handstate = ROCK;
  std::cout << "Initialized" << std::endl;
}

void MyListener::onConnect(const Controller& controller) {
  controller.enableGesture(Gesture::TYPE_CIRCLE);
}

void MyListener::onDisconnect(const Controller& controller) {
  std::cout << "Disconnected" << std::endl;
}

void MyListener::onExit(const Controller& controller) {
  std::cout << "Exited" << std::endl;
}

void MyListener::onFrame(const Controller& controller) {

  const Frame frame = controller.frame();

  if (!frame.hands().isEmpty()) {
    // Get the first hand
    const Hand hand = frame.hands()[0];

    // Check if the hand has any fingers
    const FingerList fingers = hand.fingers();
    int handstate;
    if (!fingers.isEmpty()) {
      if(fingers.count() == 5){
        handstate = PAPER;
        if(before_handstate != PAPER){
          std::cout << "パー(指5本)検知" << std::endl;
          system("eject");
        }
      }
    }else{
      handstate = ROCK;
      if(before_handstate != ROCK){
        std::cout << "グー(指0本)検知" << std::endl;
        system("eject -t");
      }
    }

    before_handstate = handstate;
  }
}

void MyListener::onFocusGained(const Controller& controller) {
}

void MyListener::onFocusLost(const Controller& controller) {
}

int main() {

  MyListener listener;
  Controller controller;

  controller.addListener(listener);

  std::cout << "Press Enter to quit..." << std::endl;
  std::cin.get();

  controller.removeListener(listener);

  return 0;
}

ビルドは下記コマンドで行います。/opt/LeapSDK/includeと/opt/LeapSDK/libは環境に合わせて読み替えてください。使用するヘッダファイルと共通ライブラリファイルはそれぞれDeveloperKit内のLeapSDK/includeとLeapSDK/lib/x64[7]に格納されています。

$ g++ -Wall -I/opt/LeapSDK/include LeapEject.cpp -o LeapEject -L/opt/LeapSDK/lib -lLeap

WebSocketを使ってみる

leapdは標準でWebSocketサーバを持っています。 Leap Motionがつながっているコンピューターの6437番ポートへWebSocketクライアントとして接続するだけで、Leap Motionが取得する値をJSON形式で毎フレーム受け取ることができます。

本来はWebブラウザがLeap Motionへアクセスして、手や指の動きでWebブラウザ操作を行うために準備された物だと考えられますが、WebSocketクライアントとして接続できれば、どんなものでもJSONデータを取得できます。 そのため、やや裏技的ではありますが、Raspberry PiやBeagle Bone Black(BBB)のようなGPIOを備えたARMデバイスをWebSocketクライアントとして接続して、手や指の動きでLEDの点灯や、モーターの回転を制御できます。

BBB上で動作しているコード(leap_searvo.py)は、受け取ったJSONデータからcircleジェスチャの有無を読み取り、ジェスチャを認識した場合は指の回転方向に合わせてサーボモーターを回転させるものです。サーボモーターを制御するライブラリをインストールして使用する関係からPythonでコードを作成しています。また、WebSocketのクライアントとするためにwebsocke-clientが必要です。

$ sudo apt-get install python-pip
$ sudo pip install websocket-client
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

from websocket import create_connection
import Adafruit_BBIO.PWM as PWM
import json 
import sys
import os
import signal

# Leap MotionのWebSocketサーバのポートは6437番
LEAP_ADDRESS = "ws://192.168.1.10:6437"

CANNOT_DETECT = -1
CLOCK_WISE = 0
COUNTER_CLOCK_WISE = 1

# サーボモータのsignalはP9_14ピンに接続する
SERVO_PIN = "P9_14"
# 最小デューティ比(0度)
DUTY_MIN = 88.7
# 最大デューティ比(180度)
DUTY_MAX = 97.4
DUTY_SPAN = DUTY_MAX - DUTY_MIN

g_input_angle = 0

def isClockWise(a, b) :
    if a[0] * b[0] + a[1] * b[1] + a[2] * b[2] > 0 :
        return True
    else :
        return False

def doCircle(LeapData, gesture) :
    global g_input_angle

    circle_direction = detectRotationDirection(LeapData, gesture)
    if circle_direction == CLOCK_WISE :
        print('clockwise')
        g_input_angle = g_input_angle + 0.5 if g_input_angle < 180.0 else 180.0
        angleToduty(g_input_angle)
    elif circle_direction == COUNTER_CLOCK_WISE :
        print('counter clockwise')
        g_input_angle = g_input_angle - 0.5 if g_input_angle > 0.0 else 0.0 
        angleToduty(g_input_angle)

def detectRotationDirection(LeapData, gesture) :
    if len(gesture['pointableIds']) >= 1 :
                
        ii = iter(LeapData['pointables'])
        for pointable in ii :
            if pointable['id'] == gesture['pointableIds'][0] :
                direction = pointable['direction']
                normal = gesture['normal']
                if isClockWise(direction, normal) :
                    return CLOCK_WISE 
                else :
                    return COUNTER_CLOCK_WISE
                break

    return CANNOT_DETECT
        
def detectGesture(LeapData) :
    if 'gestures' in LeapData and LeapData['gestures'] :
        gesture, gesture_type = getGesture(LeapData)
        
        if gesture_type == 'circle' :
            print ('circle')
            doCircle(LeapData, gesture)
        elif gesture_type == 'keyTap' :
            print ('keyTap')
        elif gesture_type == 'screenTap' :
            print ('screenTap')
        elif gesture_type == 'swipe' :
            print ('swipe')

def getGesture(LeapData) :
    if 'gestures' in LeapData and LeapData['gestures'] :
        gesture      = LeapData['gestures'][0]
        gesture_type = gesture['type']
        return gesture, gesture_type


def angleToduty(angle) :
    angle_f = float(angle)
    if angle_f > 180.0 : angle_f = 180.0
    if angle_f < 0.0 : angle_f = 0.0
    
    duty = 100 - ((angle_f / 180) * DUTY_SPAN + DUTY_MIN) 
    PWM.set_duty_cycle(SERVO_PIN, duty)



if __name__ == "__main__" :

    # initialize
    ws = create_connection(LEAP_ADDRESS)
    PWM.start(SERVO_PIN, (100 - DUTY_MIN), 50.0)


    pid = os.fork()
    if pid == 0 :
        while 1:
            recv = ws.recv()
            if  recv :
                decode_json_data = json.loads(recv)
                detectGesture(decode_json_data)
                
        sys.exit()

    else :
        while 1 :
            c = sys.stdin.read(1)
            if c :
                os.kill(pid, signal.SIGTERM)

                # finalize
                ws.close()
                PWM.stop(SERVO_PIN)
                PWM.cleanup()
                sys.exit()

まとめ

Leap Motionはその性質上、操作を行うためには常に腕を上げている必要があるため、10分使用しているだけでも腕や肩が疲れてしまいます。また、デスクトップ操作もマウスで行うほど自由にはできません[8]⁠。したがって、現状ではマウスやキーボードを完全に置き換えるような入力デバイスにはなりません。しかしながら、使い所を考えれば充分に可能性を感じられるデバイスとなっていますし、価格も比較的安価であるため興味のある方は購入してみてはいかがでしょうか。

おすすめ記事

記事・ニュース一覧