Leap Motion で PaPeRo i に指の数を数えさせる

手を触れずにPCを操作できるデバイスとして話題となった Leap Motion を使って PaPeRo i に人の手を数えさせたり、伸びている指の名前を言わせたりする方法の一例を、前回記事「Leap Motionで PaPeRo i に人の手の状況を発話させる」で紹介しましたが、今回は伸びている指の数を発話させる方法の一例を紹介したいと思います。
この方法は、例えば「PaPeRo i の発話したメニュー項目を手の指の本数により選択する」といった事に応用する事ができます。

Leap Motionは、前回と同様に、WindowsPCに接続して使用します。

作成したプログラムは、前回使用した Leap MotionSDKバージョン3.2.1でも動作しますが、2019年11月現在ではより新しい 4.0.0 がリリースされているので、こちらを使った手順を紹介します。
ほぼ前回と同様の手順となりますが、4.0.0 ではWebSocketによるデータ取得機能が既定では無効になっている為、設定の変更が必要です。

手順(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ファイル(Leap_Motion_Developer_Kit_4.0.0+52173.zip)を、任意のフォルダに解凍します。

(3) 解凍してできた Leap_Motion_Developer_Kit_4.0.0+52173 というフォルダの中の、Leap_Motion_Setup_4.0.0+52173.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) Leap Motion SDK 4.0.0では、WebSocketによるデータ取得機能が既定では無効になっているので、有効にするために、(8)で確認した Leap Motion のアイコンを右クリックし、「設定…」をクリックします。
Leap Motion コントロールパネルが現れますので、「全般」タブの「システム」グループの「Webアプリケーションを許可」にチェックを入れて、「OK」ボタンをクリックします。

(10) 下記の内容をコピー・ペーストして、ppr_lmc_fcount.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_fcount():
    msg_dic_rcv = json.loads(ws_lmc.lastMsg)
    hands_data = get_hands_data(msg_dic_rcv)
    hcount = len(hands_data)
    fcount = 0
    for i_hand in range(hcount):
        for i_finger in range(5):
            vec_bone = (-1) * np.array(hands_data[i_hand]["fingerBases"][i_finger][2][2])
            vec_hand = np.array(hands_data[i_hand]["direction"])
            if i_finger == 0:
                vec_hand = np.cross(hands_data[i_hand]["direction"], hands_data[i_finger]["palmNormal"]);
                if hands_data[i_hand]["type"] == "left":
                    vec_hand = (-vec_hand)
            val_dot = np.dot(vec_bone, vec_hand)
            if val_dot > 0.0:
                fcount += 1
    return fcount


def get_speech_from_fcount(fcount):
    speech = str(fcount) + "本の指が伸びています。"
    return speech


def main(papero):
    prev_time = time.monotonic()
    past_time = 0
    prev_fcount = 0
    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("伸びている指の数を発話します。" +
                                     "座布団のボタンで終了します。")
            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:
            fcount = get_fcount()
            print("fcount=", fcount)
            speech = ""
            if fcount!= prev_fcount:
                speech = get_speech_from_fcount(fcount)
            if speech != "":
                papero.send_start_speech(speech)
                past_time = 0
                state = State.st1
            prev_fcount = fcount
        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()

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

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

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

実行すると、PaPeRo i が「伸びている指の数を発話します。座布団のボタンで終了します」と発話します。
その後、Leap Motionの上に手をかざすと、伸びている指の数を発話します。
手が2つ以上ある場合は、全ての手の伸びている指の数の合計を発話します。例えば両手を開いてかざすと、「10本の指が伸びています」と発話します。


0