Bakulog

獏の夢日記的な何か。

QUMARIONを使ってPepperを操縦する

タイトルの通りです。ソース公開を含みます。

 

もくじ

  • 概要と動画の紹介
  • 再現手順について
  • まとめとか

 

概要と動画の紹介

QUMARIONというのは姿勢や動きを取れる人型入力デバイスです。普通はCGモデルの操作に使うのですが、今回は「人型」かつ「関節のデータを取れる」という特長を生かしてPepperの操縦をやってみました。その動画がコチラです。

 

 

ニコニコ動画のアカウント持ってない方のために動画付きツイートも貼っておきます。

https://twitter.com/baku_dreameater/status/678347488730812416

 

 

 

再現手順

用意するもの

以下の通りです。

お金がかかりそうなもの

無償で入手可能なもの

 

Python NAOqi SDKについてはこちらの開発者向けサイトでアカウント作成を行ってからログインし、ソフトウェアダウンロードのページでWindows用の32bit SDKインストーラ("Python 2.7 SDK 2.4.2 Win 32 Setup"みたいな名前のやつ)を拾ってきて実行すればインストールできます。当然ですがPythonのダウンロードを先に行ってください。

 

またPythonのインストール後にはシステム環境変数のPath変数にPythonインタープリターへのパス("C:\Python27"とかそういうの)を通しておく方が無難です。

 

実行手順

まず前回の記事を参考に"QumarionDotNet"というレポジトリをローカル環境に持ってきてビルドやらなんやらを行い、"QumarionDataViewer.exe"というGUIを実行できるところまで済ませて下さい。

次にPythonスクリプトとして以下の2ファイルを同じディレクトリ(デスクトップとか)に配置します。スクリプト文字コードはどちらもUTF-8にします。一つ目の"quma2pepper.py"についてはスクリプト内にある"pepperip"という変数を実際に操縦するPepperのIPアドレスに書き換えてください。

 

quma2pepper.py

# -*- coding:utf-8 -*-

import struct
import socket
from contextlib import closing

import quma2pepper_sender

#動かしたいPepperのIPアドレス
pepperip = "192.168.xxx.xxx"
#動かしたいPepperのポート(普通9559でOK)
pepperport = 9559

#QumarionクライアントがエンドポイントにしているIPアドレスとポート
udpip = '127.0.0.1'
udpport = 13000

def main():
    bufsize = 4096

    #Qumarionクライアントが送って来るデータ(単精度小数)の個数
    datasize = 32
    unpacker = struct.Struct("f" * datasize)

    quma2pepper_sender.initialize(pepperip, pepperport)

    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    with closing(sock):
        sock.bind((udpip, udpport))
        while True:
            msg = sock.recv(bufsize)
            try:
                data = unpacker.unpack(msg)
                print("received binary")
                quma2pepper_sender.setAngles(data)
            except struct.error:
                print("failed:not binary data")
                print(msg.decode('utf-8'))
                break

if __name__ == '__main__':
    main()

 

quma2pepper_sender.py

# -*- coding:utf-8 -*-

import math
from threading import Thread, Lock

from naoqi import ALProxy

_deg2rad = math.pi / 180.0

#ALMotionを入れるための変数
_motion = None

#同時に主処理を行うスレッドが最大1つになるように絞るためのフラグ
_lockBusy = Lock()
_isBusy = False
def _getIsBusy():
    with _lockBusy:
        return _isBusy

def _setIsBusy(isBusy):
    global __isBusy
    with _lockBusy:
        _isBusy = isBusy

#Qumarionクライアントから飛んでくるセンサ値に順に対応づけられてるセンサー名
def _getQumaSensorNames():
    return (
        "WaistV_MY",
        "WaistV_MZ",
        "L_Thigh_X",
        "L_Thigh_MZ",
        "L_Thigh_MY",
        "L_Leg_MX",
        "L_Foot_MZ",
        "L_Foot_X",
        "R_Thigh_MX",
        "R_Thigh_MZ",
        "R_Thigh_MY",
        "R_Leg_X",
        "R_Foot_MZ",
        "R_Foot_MX",
        "WaistH_MX",
        "WaistH_Z",
        "Neck_MZ",
        "Neck_MX",
        "Head_X",
        "Head_Y",
        "L_Shoulder_X1",
        "L_Shoulder_MZ",
        "L_Shoulder_X2",
        "L_Elbow_MY",
        "L_Hand_X",
        "L_Hand_MZ",
        "R_Shoulder_MX1",
        "R_Shoulder_MZ",
        "R_Shoulder_MX2",
        "R_Elbow_MY",
        "R_Hand_MX",
        "R_Hand_MZ"
        )

#Pepper側の操縦対象になってる角度一覧
def _getPepperAngleNames():
    return (
        "HeadYaw",
        "HeadPitch",
        "RShoulderPitch",
        "RShoulderRoll",
        "RElbowYaw",
        "RElbowRoll",
        "RWristYaw",
        "RHand",
        "LShoulderPitch",
        "LShoulderRoll",
        "LElbowYaw",
        "LElbowRoll",
        "LWristYaw",
        "LHand"
        )

#Qumarionのセンサデータを使ってPepperの角度を指定する
def _setAngles(*angles): 
    if _motion == None:
        return

    qumaSensorNames = _getQumaSensorNames()
    pepperAngleNames = _getPepperAngleNames()

    #読み取り時に順番を意識しないで済むように辞書化
    qumaAngles = dict(zip(qumaSensorNames, angles))

    pepperAngles = (
        #正面0, 左向き+
        -qumaAngles["Head_Y"] * _deg2rad, 
        #正面0, 下向き+
        -(qumaAngles["Head_X"] - qumaAngles["Neck_MX"] - 20.0) * _deg2rad,
        #正面0, 腕下げて+
        (qumaAngles["R_Shoulder_MX1"] + 100) * _deg2rad,
        #正面0, 開いたとき-のつもり
        -(qumaAngles["R_Shoulder_MZ"] + 75) * _deg2rad,
        #肘内側が内側向きで0, 上を向く方向が+のつもり
        (-qumaAngles["R_Shoulder_MX2"] + 90) * _deg2rad,
        #肘のばして0, まげて+のつもり
        qumaAngles["R_Elbow_MY"] * _deg2rad,
        #手のひら下向きで0, 内転して+のつもり
        qumaAngles["R_Hand_MX"] * _deg2rad,
        #Qumarionの手を手の甲側に曲げると手を開き、逆に曲げると閉じる
        (qumaAngles["R_Hand_MZ"] + 30) / 90.0,
        #正面0, 腕下げて+のつもり
        (-qumaAngles["L_Shoulder_X1"] + 100) * _deg2rad,
        #正面0, 開いたとき+のつもり
        (-qumaAngles["L_Shoulder_MZ"] + 75) * _deg2rad,
        #肘内側が内側向きで0, 上を向く方向が-
        -(qumaAngles["L_Shoulder_X2"] + 80) * _deg2rad,
        #肘伸ばして0, まげて-   
        qumaAngles["L_Elbow_MY"] * _deg2rad,
        #手のひら下向きで0, 内転して+のつもり
        -qumaAngles["L_Hand_X"] * _deg2rad,
        #Qumarionの手を手の甲側に曲げると手を開き、逆に曲げると閉じる
        (-qumaAngles["L_Hand_MZ"] + 30) / 90.0
        )

    #TODO1: pepperAnglesには関節ごとに動かせる値の上限と下限があるので
    #  リミットチェックを行うべき

    #TODO2: 第3引数に0より大きく1以下の値が入ったリスト
    #  (pepperAnglesと同じ長さの)を入れて関節別に反応の良さを調整
    _motion.setAngles(pepperAngleNames, pepperAngles, 0.2)

    _setIsBusy(False)


def initialize(ip, port=9559):
    global _motion
    _motion = ALProxy("ALMotion", ip, port)

def setAngles(angles):
    if _getIsBusy():
        return
    _setIsBusy(True)

    t = Thread(
        target=_setAngles, 
        args=(angles)
        )
    t.start()

 

スクリプトが準備出来たら"quma2pepper.py"のほうをメインスクリプトとして起動します。コマンドプロンプトでquma2pepper.pyのディレクトリに移動し、下記コマンドで実行します。これでPythonスクリプトGUIプログラムからのデータ受信待ち状態になります。

python quma2pepper.py

 

最後の準備としてGUI側の"UDP送信設定"を開き、「送信タイミング」を「ボタンを押している間は送信」、「送信内容」を「センサ値バイナリ」とします。これで準備は完了です。

quma2pepper_gui_setting

以上の準備が整ったらQUMARIONを手で持ち、適当なポーズを取らせて背面のボタンを押します。ボタンを押してる間はPepperがQUMARIONの動きに追随するので、腕や頭を動かしてみてください。送信タイミングのオプションを変更することでボタンを押さないでも常にデータを送りっぱなしにすることなども可能です。

 

まとめとか

まとめ書くようなネタじゃない気もしますが、QUMARIONはPepperのような人型ハードウェア用のゴツいコントローラとしても使えますよ、という話でした。質問等あればTwitterとかでお尋ねください。