Leap Motionで PaPeRo i に人の手の状況を発話させる

手を触れずにPCを操作できるデバイスとして話題となった Leap Motion を使って PaPeRo i に人の手を数えさせたり、伸びている指の名前を言わせたりする方法の一例を紹介します。
Leap Motionは、WindowsPCに接続して使用します。
使用したLeap MotionSDKのバージョンは、3.2.1です。
作業は Windows10 Home がインストールされたノートPC上で主に行いましたが、低価格で入手できるスティックPCを使用した場合の状況についても紹介します。

手順(PaPeRo i 側)

(1) PaPeRo i 制御用WebSocket通信アドオンシナリオをまだインストールしていない場合は、「PaPeRo iをRaspberry Pi上のpythonから操作する」の「PaPeRo iにアドオンシナリオをインストール」に従ってインストール作業を行います。

手順(WindowsPC 側)

ここでは Windows10 Home がインストールされたノートPC上で行った作業手順を紹介します。

(1) https://developer.leapmotion.com/get-started から、Leap MotionのSDKをダウンロードします(要ユーザー登録)。

(2) ダウンロードしたzipファイル(LeapDeveloperKit_3.2.1_win.zip)を、任意のフォルダに解凍します。

(3) 解凍してできた LeapDeveloperKit_3.2.1_win というフォルダの中の、Leap_Motion_Orion_Setup_win_3.2.1.exeを実行し、画面の指示に従ってインストール作業を行います。

(4) Pythonをまだインストールしていない場合は、https://www.python.org/ からダウンロードしてインストールします。

(5) ws4py をまだインストールしていない場合は、コマンドプロンプトから下記のコマンドを入力する事によりインストールします。

pip install ws4py

(6) 「PaPeRo i でPythonを使えるようにする」にあるリンクから「PaPeRo i 制御用Pythonライブラリ」をダウンロードし、zipファイルの中身(pypapero.py)を任意のフォルダに置きます。

(7) numpyをインストールします。

pip install numpy

pipでのインストールが失敗する場合は、https://sourceforge.net/projects/numpy/files/NumPy/ からインストーラーをダウンロードしてインストールします。

(8) Leap MotionをPCに接続し、タスクバーの右端付近の ^ の部分をクリックして隠れているインジケーターを表示し、その中のLeap Motionのアイコンが緑色になっている事を確認します。

(9) 下記の内容をコピー・ペーストして、ppr_lmc_fingers.py を作成し、pypapero.py と同じフォルダに置きます。

import sys
import time
import json
from enum import Enum

import numpy as np
from ws4py.client.threadedclient import WebSocketClient

import pypapero


class State(Enum):
    st0 = 10
    st1 = 11
    st2 = 12
    st3 = 13
    end = 999


class LeapMotionController(WebSocketClient):
    def opened(self):
        self.connected = True
        print("Connected to LeapMotion")

    def closed(self, code, reason):
        print("Disconnected from LeapMotion")

    def received_message(self, msgrcv):
        message = str(msgrcv)
        self.lastMsg = message


def get_hands_data(msg_dic_rcv):
    hands_data = []
    if "hands" in msg_dic_rcv:
        hands = msg_dic_rcv["hands"]
        for i in range(len(hands)):
            hand_data = {}
            hand_data["id"] = hands[i]["id"]
            hand_data["direction"] = hands[i]["direction"]
            hand_data["palmNormal"] = hands[i]["palmNormal"]
            hand_data["type"] = hands[i]["type"]
            hand_data["fingerBases"] = [None, None, None, None, None]
            if "pointables" in msg_dic_rcv:
                pointables = msg_dic_rcv["pointables"]
                for i in range(len(pointables)):
                    if pointables[i]["handId"] == hand_data["id"]:
                        finger_type = pointables[i]["type"]
                        hand_data["fingerBases"][int(finger_type)] = pointables[i]["bases"]
                hands_data.append(hand_data)
    return hands_data


def get_hcount_fingers():
    msg_dic_rcv = json.loads(ws_lmc.lastMsg)
    hands_data = get_hands_data(msg_dic_rcv)
    hcount = len(hands_data)
    fingers = []
    if hcount == 1:
        for i in range(5):
            vec_bone = (-1) * np.array(hands_data[0]["fingerBases"][i][2][2])
            vec_hand = np.array(hands_data[0]["direction"])
            if i == 0:
                vec_hand = np.cross(hands_data[0]["direction"], hands_data[0]["palmNormal"]);
                if hands_data[0]["type"] == "left":
                    vec_hand = (-vec_hand)
            val_dot = np.dot(vec_bone, vec_hand)
            if val_dot > 0.0:
                fingers.append(i)
    return hcount, fingers


def get_speech_from_hcount_fingers(hcount, fingers):
    finger_names = ["親指", "ひとさし指", "中指", "薬指", "小指"]
    speech = ""
    if (hcount > 1):
        speech = "手が" + str(hcount) + "本あります"
    elif (hcount == 1):
        speech_fingers = ""
        for i in fingers:
            if(speech_fingers != ""):
                speech_fingers += "と、"
            speech_fingers += finger_names[i]
        if speech_fingers == "":
            speech_fingers = "ありません"
        else:
            speech_fingers += "です"
        speech = "伸びている指は、" + speech_fingers
    return speech


def main(papero):
    prev_time = time.monotonic()
    past_time = 0
    prev_hcount = 0
    prev_fingers = []
    state = State.st0
    while state != State.end:
        messages = papero.papero_robot_message_recv(0.1)
        now_time = time.monotonic()
        delta_time = now_time - prev_time
        prev_time = now_time
        if messages is not None:
            msg_dic_rcv = messages[0]
        else:
            msg_dic_rcv = None
        if papero.errOccurred != 0:
            print("------Error occured(main()). Detail : " + papero.errDetail)
            break
        if state == State.st0: 
            papero.send_start_speech("伸びている指の名前を発話します。" +
                                     "手が2つ以上ある場合は、手の数を発話します。" +
                                     "座布団のボタンで終了します。")
            past_time = 0.0
            state = State.st1
        elif state == State.st1:
            past_time += delta_time
            if past_time > 0.5:
                papero.send_get_speech_status()
                state = State.st2
        elif state == State.st2:
            if msg_dic_rcv is not None:
                if msg_dic_rcv["Name"] == "getSpeechStatusRes":
                    if str(msg_dic_rcv["Return"]) == "0":
                        state = State.st3
                    else:
                        past_time = 0
                        state = State.st1
        elif state == State.st3:
            hcount, fingers = get_hcount_fingers()
            print("hcount=", hcount, " fingers=", fingers)
            speech = ""
            if (hcount != prev_hcount) or (fingers != prev_fingers):
                speech = get_speech_from_hcount_fingers(hcount, fingers)
            if speech != "":
                papero.send_start_speech(speech)
                past_time = 0
                state = State.st1
            prev_hcount = hcount
            prev_fingers = fingers
        if msg_dic_rcv is not None:
            if msg_dic_rcv["Name"] == "detectButton":
                state = State.end


if __name__ == "__main__":
    ws_lmc = LeapMotionController("ws://127.0.0.1:6437/v6.json", protocols=None)
    ws_lmc.lastMsg = "{}"
    ws_lmc.connected = False
    ws_lmc.connect()
    while not ws_lmc.connected:
        time.sleep(0.1)
    ws_lmc.send('{"optimizeHMD": false}')
    ws_lmc.send('{"enableGestures": false}')
    ws_lmc.send('{"background": false}')
    simulator_id, robot_name, ws_server_addr = pypapero.get_params_from_commandline(sys.argv)
    papero = pypapero.Papero(simulator_id, robot_name, ws_server_addr)
    main(papero)
    papero.papero_cleanup()
    ws_lmc.close()

(10) コマンドプロンプトの cd コマンドで、カレントディレクトリを ppr_lmc_fingers.py 及び pypapero.py の置いてあるディレクトリにします。

(11) 下記のコマンドで、ppr_lmc_fingers.py を実行します。

python ppr_lmc_fingers.py -wssvr ws://PaPeRoiのIPアドレス:8088/papero

実行すると、PaPeRo i が「伸びている指の名前を発話します。手が2つ以上ある場合は、手の数を発話します。座布団のボタンで終了します」と発話します。
その後、Leap Motionの上に片手をかざすと伸びている全ての指の名前を発話し、両手をかざすと「手が2本あります」と発話するようになります。
手の角度等によってはうまく認識できない場合もありますが、精度としては70%程度以上あり、瞬時に認識されます。
工夫次第で PaPeRo i とじゃんけんをしたり、アンケートに使ったりと、様々な応用ができそうです。

スティックPCで動かす

Leap MotionをスティックPCに接続し、ppr_lmc_fingers.py をスティックPCで動作させる方法の一例を紹介します。
使用したスティックPCは、ASUS VivoStick PC TS10(CPU:Atom x5-Z8350、メモリ2GB、ストレージ:32 GB)です。
スティックPCにPythonをインストールする手間を省くため、ここではPyinstallerを使用する事にします。

(1) 上記作業を行ったWindowsPCに、以下のコマンドで Pyinstaller をインストールします。

pip install pyinstaller

(2) cd コマンドで ppr_lmc_fingers.py の置いてあるディレクトリに移動し、下記のコマンドを実行します。

pyinstaller ppr_lmc_fingers.py

(3) コマンドを実行したディレクトリの中にできる dist というディレクトリの中の、ppr_lmc_fingers というフォルダを、スティックPCの中の任意のフォルダの中にコピーします。

これ以降の作業は、スティックPC上で行います。

(4) 上記「手順(WindowsPC 側)」の1~3と同じ要領で、スティックPCに Leap Motion Orion をインストールします。

(5) 無線LANでPaPeRo i と接続します。

(6) 下記のコマンドを記載した ppr_lmc_fingers.bat という名前のファイルをメモ帳で作成し、ppr_lmc_fingers フォルダの中に置きます。

ppr_lmc_fingers.exe -wssvr ws://PaPeRoiのIPアドレス:8088/papero

(7) スティックPCに Leap Motion を接続します。スティックPCのUSBポートが(キーボード・マウス等で)全てふさがっている場合は、キーボードを外します。

(8) タスクバーの右端付近の ^ の部分をクリックして隠れているインジケーターを表示し、その中のLeap Motionのアイコンが緑色になっている事を確認します。

(9) エクスプローラーで、 ppr_lmc_fingers.bat をダブルクリックします。

これで、ノートPCでの場合と同様に、Leap Motion の上にかざした手の状況を PaPeRo i が発話するようになります。
実際に動かしてみた所、認識時間・精度共に、ノートPCの場合との違いは感じられませんでした。


0