「dane」タグアーカイブ

ここでは、ダンス中転んでも起き上がって続きからダンスを踊る NAO (頑張るダンス)アプリの作り方を説明します。

対象

この記事は、NAO 向けアプリ開発ツール「Choregraphe」を使ってダンスアプリを作ったことがある方を対象としています。 そのため NAO の開発環境、Choregraphe の基本的な操作方法などに関する説明はここでは行いません。

はじめに

2本足で立ち、全身を動かすことができる NAO はとてもダイナミックな動きをすることができるのが最大の特徴の一つですが、同時に転倒のリスクに常に気を使う必要があります。 ダイナミックなダンスを披露するときなど、バランスを崩して転ばないかといつもヒヤヒヤします。 一方見ていただいた方からは小さいのに一生懸命がんばって踊っている感じがけなげでいいといったような意見をよく聞きます。 ならば転んでも起き上がって続きから踊るぐらいまでがんばってもいいのでは。  そんな思いから作ってみたのです。

サンプル動画

実装、動かしてみた例です。どうです、頑張っている感出てませんか?

原理

原理は結構単純です。 Choregraphe のタイムラインで作ったアニメーション、アニメーションの時間経過にしたがって各関節の角度をどう変化させればいいのかを配列の形式で記述した、スクリプトコードに落とし込むことができるのですが、このスクリプトコードでアニメーションを再生することにします。 途中で転んだら安全機能が働きアニメーションの再生が自動で止まるのですが、これを検出してまず起き上がり、無事立ち上がることができたら、次にアニメーションの再生を開始してからどれぐらい時間が経過したかを調べ、各関節の角度を収めている配列を、過去の情報を切り捨てて再構築、これを再生すれば途中からダンスを再開できるというわけです。

サンプルアプリ

是非皆さんも試してみてください。サンプルアプリを次からダウンロードできます。なお、ビデオは FC東京のマスコットキャラクター ドロンパ君のダンスを踊っていますが、サンプルアプリは、NAO 標準のダンス、太極拳ダンスを踊ります。

https://github.com/Smartrobotics/nao_fallrecoverydance/

あなたのダンスアプリを「頑張る化」する方法

サンプルアプリを変更してあなたのオリジナルダンスを倒れても起き上がってダンスにしてみましょう。

必要なもの

「頑張る化」に必要なのは次の2つです
* Choregraphe のタイムラインボックスで作られたアニメーション
* タイムラインのアニメーション再生中に再生する音楽ファイル

手順

次に手順を説明します

1. タイムラインをスクリプトとしてエクスポート

「頑張る化」したいアニメーションのタイムラインを開きます。 次の図の手順でタイムラインをスクリプトに変換します

timeline1

2. サンプルアプリのダンススクリプト部を入れ替える

サンプルアプリを開きます。この中のスクリプトを先ほどエクスポートしたものに入れ替えます。 まずエクスポートしたコードですが、この中で必要な箇所は

	names = list()

で始まる行から

	try:
	   # uncomment the following line and modify the IP if you use this script outside Choregraphe.
	   # motion = ALProxy("ALMotion", IP, 9559)

の行の前の行までです。 この部分で時間経過にしたがって各関節の角度をどうコントロールすればいいのかを配列に詰めています。この配列を使って独自の手法でダンスの再生をしたいわけです。 上記コードブロックをクリップボードにコピーしておきます。次に「Ganbaru Dance」ボックス

ganbaru_dance_box1

の中の「Timeline」ボックスをダブルクリック
timelinebox

スクリプトエディタが開くのでコメント部 「# put animation code here」 から 「#animation code end」 の部分をクリップボードにコピーした内容で入れ替えます

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

	    def onLoad(self):
	        self.bIsRunning = False
	        self.player = ALProxy('ALAudioPlayer')
	        self.playerStop = ALProxy('ALAudioPlayer', True) #Create another proxy as wait is blocking if audioout is remote
	        self.soundId = None

	    def onUnload(self):
	        if self.soundId != None:
	            self.playerStop.stop(self.soundId)
	        while( self.bIsRunning ):
	            time.sleep( 0.2 )

	    def onInput_onPrepare(self,p):
	        self.soundFile = p

	        # put animation code here

	        	:     この箇所を入れ替える    :

	        #animation code end

	        self.names = names
	        self.times = times
	        self.keys = keys

	        if p != None and len(p) > 0:
	            self.soundId = self.player.loadFile(p)

貼り付けたらインんデントの調整を忘れずに。貼り付けたコードを選択したままの状態で Tab キーを押すとまとめて字下げをすることができると思います。 直前の self.soundFile = p と同じところまで貼り付けたコードを字下げします。

3. ダンスと同時に再生する音楽ファイルをインポート

ダンスと同時に再生する音楽ファイルをプロジェクトにインポートします。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-12-14-1-04-09

4. 音楽ファイルを設定

再生する音楽ファイルを 「Ganbaru Dance」 ボックスのプロパティで設定します。

set_music_file1

5. 再生

変更は以上です。再生してテストしてみてください

簡単に解説

一番の肝は 「Ganbaru Dance」 ボックスの中の 「Timeline」 ボックス、onInput_onResume メソッドの中です。ここであらかじめ定義された配列をもとに再生開始から現在までの経過時間を調べ、配列を再構築、再生しています


                :                   :

	    def onInput_onStart(self):
	        self.bIsRunning = True
	        import time

	        self.startTime = time.time()
	        if self.soundId != None:
	            tid = self.player.post.play(self.soundId)

	        self.onInput_onResume()

	        if self.soundId != None:
	            self.player.wait(tid, 0)

	        self.bIsRunning = False
	        self.onStopped()

	    def onInput_onResume(self):
	        adj_rate = 0.001

	        initialDelayEstimate = len(self.times[0]) * adj_rate

	        import time
	        motion = ALProxy("ALMotion")

	        times = list()
	        keys = list()

	        curTime = time.time() - self.startTime
	        nextTime = curTime + 2.0

	        for i in range(len(self.times)):
	            times0 = list()
	            keys0 = list()
	            for j in range(len(self.times[i])):
	                if self.times[i][j] > nextTime:
	                    times.append(self.times[i][j:]) # This loop may take time. Re-calcualte the time later
	                    keys.append(self.keys[i][j:])
	                    break

	        curTime = time.time() - self.startTime # get current time and re-calculate the time with this


	        currentDelayEstimate = len(times[0]) * adj_rate
	        adjTime = curTime - (initialDelayEstimate - currentDelayEstimate)

	        l = len(times)
	        for i in range(l):
	            times[i] = [x- adjTime for x in times[i]]


	        if len(keys) > 0:
	            motion.angleInterpolationBezier(self.names, times, keys)

	        #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

ちなみに、変数 adj_rate ですが、これは再生時間の微調整をしています。動画を見ていただくとダンス後半でダンスを復帰した時、同時に踊っているもう一台より少しアニメーションが早いタイミングで再生されていることが分かるかと思います。 どうもアニメーションを再生するメソッド angleInterpolationBezier は配列データが多いと再生開始に時間がかかるようで、これが原因で再生が少しだけずれています。ダンス後半でリカバリーするとき、残りのモーションが少なくなってくるので配列のデータ数が少なくなります。 これを再生するとき、アニメーションが実際に開始されるまでの angleInterpolationBezier メソッド内部での処理時間が短くなるので、angleInterpolationBezier メソッド呼び出しまでのタイミングで同期を取ったとき、若干のズレが生じてしまうのです。 このズレを調整しているのが adj_rate です。動画を撮った時点では、この調整が行われていなかったので、若干のズレが入っています。 adj_rate の値は計算結果によるものというより実測によりものになります。ズレが最小限になる値はダンスごとに調整が必要かもしれません。

Ganbaru Dance ボックスの中の fall recovery ボックスが転倒した状態から起き上がる処理をしています。

fallrecoverybox

fall recovery ボックスの中はモータをオンにして、立ち上がっています。 Set Speaker Vol. ボックスは転倒時音量を少し下げています。この辺りは利用シーンなどに応じて作り変える必要があると思います。

fallrecoverybox_inside

これはあくまでサンプル、どういった作りになっているかいろいろ研究して自由に作り変えてみてください。 もっともっと頑張ってる NAO が見れること楽しみにしています!