Say
python2.7
Pepper
SLAM

飲み会の誘いを自分の代わりにしてくれるPepperを作ってみた

自己紹介

PepperAdvancedPartnerである
エコー電子工業(株)の井本です!
今まで、チームのみんなと一緒にPepperでいろんなアプリを開発してきたので、
その知識を生かして、日常の問題を解決しちゃいます!

なぜ作るのか

最近こういう悩みを聞くことが多くなりました!
「今日、飲みに行きたい気分なんだけど、自分で誘うのめんどくさいなぁ、誰か私を誘ってほしいなぁ」

ということで、Pepperが参加者を勝手に集めて飲み会を開いてくれる非常に画期的なアプリが必要となったわけです!(*^^)v

必要な機能

今回必要な機能を簡単に分けると次の4つですね

  • 飲みのお誘いのための発話
  • お店の情報を 画像表示
  • SLAMで席まで移動

飲みのお誘いのための発話

Pepperで発話するには,「say」と「AnimatedSay」の二つがあります。
簡単に説明すると次のようになりますね

Say AnimatedSay
発話のみ モーション付きの発話

今回は「モーションをつけるのが、めんどく...」ということで、
勝手にモーションがつく、AnimatedSayを使いたいと思います。

AnimatedSay.PNG

左下にある鍵マークをクリックして、ボックスの編集画面を開き、Textの中に「みんなと飲みに行きませんか?」という飲み会のお誘いワードを入れちゃいましょう!
Voiced shaping:声の高さは、少しテンション高めの「140」
Speed:文字を読むスピードは、「110」くらいに設定してみました!

AnimatedSay_内部変更後.PNG

注)もし、下の画面が出て、再生できないって時には、Autonomousが効いていますので、下の画像のようにハートマークをクリックして、寝かせた後に、☆のようなマークをクリックして、起こしてあげてください!(ハートが白抜きになると思います。)

autonomous.PNG
autonomous2.PNG

お店の情報を画像表示

次はお店の画像を表示させたいと思います。
今回は、OKをもらっていないお店の写真を記事にしちゃうのもどうかと思ったので、お酒の写真にしちゃいたいと思います!(´ω`)

画像を表示する多面いShow Imageボックスを使いましょう!
showIMage.PNG

画像を表示させるにはアプリの中にhtmlフォルダを作成して、画像を入れる必要があるので、
プロジェクトファイルのプラスボタンを押して、新規フォルダを選択!
「html」と入力し、「create」ボタンを押してください(^^)/
プロジェクトファイル.PNG
プロジェクトファイル2.PNG
プロジェクトファイル3.PNG

用意した画像をhtmlフォルダの中に入れましょう!
プロジェクトファイルのプラスボタンを押して、ファイルをインポートを選択!
画像を選んでください!
プロジェクトファイル4.PNG

飲み会の画像をhtmlフォルダ内に入れてください!
プロジェクトファイル5.PNG

それでは、画像を指定して、表示します!!
ShowImageボックスの鍵マークをクリックし、画像名を入れてください( ゚Д゚)
「./画像名」にしてくださいね。
showImage2.PNG

こんな感じになりました!!
すでにかわいく飲みのお誘いはできるようになりましたね!!(^^)/
https://youtu.be/KaFdt8Ai7GI

SLAM

それでは、SLAMの機能を使って行きましょう!(´ω`)
PepperのSLAM機能は大きく分けて3つの工程に分かれます。
それが マップの作成 , 目的地の設定 , 移動 です(*'▽')

マップの作成

探索

マップを作成するには、NaoqiのAPIのALNavigation.explore を実行します。

残念ながら、Navigationのボックスはないので、作成しましょう!
ボックスライブラリでPythonを選択してください
ボックスライブラリ.PNG
PythonScript.PNG

このPythonScriptボックスは何にも書いていないので、ここに、コードを書いていきます!
まずは、onload内でSLAMに必要なNavigationAPIを呼び出すコードを書き込みましょう!

def onLoad(self):
        self.navigation = ALProxy("ALNavigation")

次に、onInput_onStart内に探索するためのAPI ALNavigation.explore を書き込みます

def onInput_onStart(self):
        self.navigation.explore(3)
        self.onStopped()

それでは、実行させましょう!
動画の用に1回転して、動き始めますので、Pepperから離れて実行してください。
注)PepperはSLAMを行う際に赤外線レーザ・ソナーセンサーなどを用いて周辺を探索します。この赤外線の届く範囲が3mなので、3m以上人がいない場所で実行してください。
https://youtu.be/fLhPXeoJjMI

マップデータのロード・現在位置の取得

ALNavigation.exploreAPIは終了した際に返却値として、マップデータをPepperのどこに保存したのかを教えてくれます。
注)マップデータは、Pepperを再起動してもデータは消えません。

そこで、マップデータの取得・Pepperがマップ上のどこにいるのか取得をしたいと思います。
先ほどのボックスを次のように書き換えてください。

class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")


    def onUnload(self):
        self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        self.navigation.explore(3)
        mapDataPath = self.navigation.saveExploration()
        self.navigation.loadExploration(mapDataPath)
        self.navigation.startLocalization()
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        self.onStopped() #activate the output of the box

    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box

ここで使用するAPIの簡単な説明をしたいと思います。

explore saveExploration loadExploration startLocalization getRobotPositionInMap
探索を行う。Pepperのメモリ内にマップデータを作成していく メモリ内にためたマップをテキストファイルとして出力する マップファイルから、データを取得する(/data/home/nao/.local/share/Explorer) Pepperの周りの状況を把握する Pepperがマップ上の現在どの位置にいるか把握する

注)簡単に書いているので、詳しくは、NaoqiAPIリファレンスをご覧ください。

Pepperで実行すると次のようなログが出力されます。
この[0.0, 0.0, 0.0]というのがPepperのマップ上での現在位置です。

[FATAL] behavior.box :onInput_onStart:17 _Behavior__lastUploadedChoregrapheBehaviorbehavior_11728551872:/Python Script_5: [0.0, 0.0, 0.0] 

目的地の設定

PepperのSLAM機能を使って現在位置の取得までできたので、次は目的地を設定していきたいと思います。

マップの作成 でも少し触れましたが、getRobotPositionInMapを使って、目的地の情報の保存を行いたいと思います。

  1. マップデータの確認

マップデータを毎回作成するのは、非常に手間なので、マップデータパスをPepperのメモリに保存して、メモリが存在しなければ、探索することにしましょう。

exploreボックス

class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")

    def onUnload(self):
        #self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        try:
            self.navigation.stopLocalization()
        except:
            self.logger.info("finish localize")
        try:
            mapDataPath = self.memory.getData("pepper/slam/mapDataPath")
        except Exception as e:
            self.navigation.explore(3)
            mapDataPath = self.navigation.saveExploration()
            self.memory.insertData("pepper/slam/mapDataPath",mapDataPath)

        self.navigation.loadExploration(mapDataPath)
        self.navigation.startLocalization()
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        self.onStopped() #activate the output of the box

    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box
  1. Pepperの頭をタッチしたら、現在位置を取得し、メモリに保存する

Pepperの頭をタッチしたら、Pepperの現在位置を取得し、メモリにリスト形式で格納していくことにします。
Tactile Headボックスを使って、頭のタッチを検出しましょう!
Pepperには頭に3つのタッチセンサーがついているので、何度も保存されないようにonlyOnceボックスも使いましょう!

heatTouch.PNG

他にも保存処理が終わったら、Pepperが「保存しました」と発話するようにして、Pepperの左右のbumperのどちらかにタッチしたら、アプリが終了することにしましょう!

データ保存.PNG

次は、このgetPostionボックスを作っていきたいと思います。
このボックスで行いたいことは
getRobotPositionでPepperの現在位置を取得して、メモリにリストとして保存することですね。

getPostionボックス

class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")


    def onUnload(self):
        #self.navigation.stopLocalization()
        self.navigation = None

    def onInput_onStart(self):
        pepperPosition = self.navigation.getRobotPositionInMap()[0]
        self.logger.fatal(pepperPosition)
        positionList = list()
        try:
            positionList = self.memory.getData("pepper/slam/positionList")
        except:
            self.memory.insertData("pepper/slam/positionList",positionList)

        positionList.append(self.navigation.getRobotPositionInMap()[0])
        self.memory.insertData("pepper/slam/positionList",positionList)
        self.onStopped() #activate the output of the box


    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box


移動

実際に移動するボックスを作ってみたいと思います。
先ほどまでは、保存処理を行っていましたが、次は、Pepperの手に触れると、保存した場所を周遊してみたいと思います。

navigateボックス

class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")
        self.tts = ALProxy('ALTextToSpeech')

    def onUnload(self):
        #put clean-up code here
        pass

    def onInput_onStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")
        count = 0
        for position in positionList:
            count+=1
            self.navigation.navigateTo(position[0],position[1],position[2]) 
            self.tts.say("目的地"+str(count)+"に到着しました。")

        self.onStopped() #activate the output of the box

    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box

データ保存2.PNG

これを実行してみると
このようになりました!(^_-)-☆

https://youtu.be/qVgjbXgSUI4

全部くっつける!

ついに、同僚のところまでPepperが歩いて行って、飲みに誘うというミッションを行ってもらいます!( *´艸`)

navigateボックスを少し、変更してのみの誘いのボックスとくっつけてしまいたいと思います!!!

navigateボックス

class MyClass(GeneratedClass):
    def __init__(self):
        GeneratedClass.__init__(self)

    def onLoad(self):
        self.navigation = ALProxy("ALNavigation")
        self.memory = ALProxy("ALMemory")
        self.tts = ALProxy('ALTextToSpeech')
        self.count = 0

    def onUnload(self):
        #put clean-up code here
        pass

    def onInput_onStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")
        self.count = 0
        position = positionList[0]
        self.navigation.navigateTo(position[0],position[1],position[2]) 
        if len(positionList) >= self.count:
            self.count+=1
            self.outSay()
        else:
            self.onStopped() 

    def onInput_nextStart(self):
        positionList = self.memory.getData("pepper/slam/positionList")

        if len(positionList) >= self.count+1:
            position = positionList[self.count]
            self.navigation.navigateTo(position[0],position[1],position[2]) 
            self.count+=1
            self.outSay()
        else:
            self.onStopped() 


    def onInput_onStop(self):
        self.onUnload() #it is recommended to reuse the clean-up as the box is stopped
        self.onStopped() #activate the output of the box


ソースと実際に動いた動画

https://youtu.be/O3j3nnhKCOM

gitソース

後輩の長尾君に協力して撮影しました!
残念ながら今回は断られてしまったので、またレベルアップして誘いに行きたいと思います。

まとめ

このPepperアプリを使えば、誰が飲みに誘ったかもばれずに、気分次第で毎日、飲み会が開けちゃいますね!!
実際に使うときは、場所と人を紐づけたり、「行く、行かない」ボタンを表示させたり
こだわりどころたくさんあるので、やってみてくださいね。

注)今回は、位置データをメモリに保存しているので、再起動をかけた場合消去されてしまいます。実際に使いたい方は、htmlフォルダの中にjsonファイルやtextファイルを作成してください(^^♪