今日は茶番はありません。
さて、前回、運動を知覚・認知する処理についてざっくり説明しました。今度は運動を表現する処理について説明します。
ということで、今回の実装は、その名も
じゃんけんに絶対勝つスーパー後出し☆ロ☆ボ!
本業はロボット屋さんなので、なんかやっとまともに専門的なところに入ります。
今回は いまだ使ったことがない サーボモータをついに使います。
その前に人間の運動のメカニズムについて説明します。
#運動のメカニズム(行動編)
運動には随意運動と不随意運動の2つに大きく分かれます。
随意運動はいわゆる意識して体を動かす運動になります。たとえば、歩く、ボールを投げるなどが随意運動に当たります。
不随意運動は無意識のうちに体が動いてしまう運動になります。たとえば、心臓の拍動、腸の蠕動運動がこれになります。不随意運動には脊髄反射も含まれます。
一方、随意運動か不随意運動か場合によって別れる行動が、呼吸と瞬きです。
随意運動は運動野から脊髄を通って、各筋肉に司令が出されます。
一方、不随意運動は随意運動とは異なる経路を通ることが多くあります。
以上を踏まえると、じゃんけんとは相手の随意運動を把握しつつ、自分の随意運動を表現し、その後、表現された記号を元に勝敗を決める記号操作を行うという高度な処理を行っていることがわかります。
#運動による非言語処理(行動編)
運動を記号表現する方法は大きく2つに別れます。
1つは実世界に物質がある方法です。もう1つは実世界に見えるものの触ることができない方法です。
前者は主にロボットを用いて表現されます。後者は主にバーチャルエージェントを用いて表現されます。
今回前者だけ詳しく説明していきます。
ロボットによる運動の表現
ロボットとはセンサと制御部、アクチュエータを備えた機械のことを言います。
特に人型をしたロボットのことをヒューマノイドロボット、またはアンドロイドといいます。
ロボットのセンサにはいろいろなものがありますが、特に運動の表現に関わるのは、
主にロータリーエンコーダになります。ロータリーエンコーダとは主にモーターの回転を測定するセンサです。
ロータリーエンコーダには光学式や機械式の2つがあります。
また、人間と同じように自分の姿勢や位置を認識するためにモーションキャプチャーを使うことがあります。
制御部には主にFPGAやマイコン、通常のコンピュータが使われます。
モーターの制御などリアルタイム性と省電力性を求める場合はFPGAが用いられます。
ちなみにFPGAとマイコンを組み合わせたZynqという製品もあります。
一方で、映像センサの処理など重たい処理は通常のコンピュータが用いられます。
アクチュエータとはモーターなどの入力されたエネルギーを力やトルクに変換する機械のことを指します。ロボットに使われるのは通常サーボモータという回転角度が指定できるモータを使います。サーボモータの中にはロータリーエンコーダや減速機、モータが含まれています。ドローンのようなハイパワーな出力が必要な場合はブラシレスモーターを用いることがあります。また、人工筋肉などのリニアアクチュエータも今後活発に使われるかもしれません。
ロボットのソフトウェアとして、有名なのはROS(Robot OS)です。
ROSは画像認識やモーター制御などの機能をローカルネットワークで管理するミドルウェアです。
日本ではRT Middlewareも使われています。
でも、世界のデファクトスタンダードはROSだそうです。
技術としては制御理論、機械学習(特に深層強化学習)などがあります。
#実装: じゃんけんに絶対勝つスーパー後出しロボ
時間不足で雑なのができた。
じゃんけんの手の認知をLeapにやらせて、
じゃんけんの手の表現をArduinoにつなげたサーボでやらせています。
回路
サーボモータを指の分だけArduinoにつけます。
今回は5こです。
#ソースコード
/*
* author: alfredplpl
*/
#include <Servo.h>
Servo thumb;
Servo index;
Servo middle;
Servo ring;
Servo little;
int state;
char PWMPin[5]={3,5,6,9,10};
const int ON=90;
const int OFF=0;
const int GU=0;
const int CHOKI=1;
const int PA=2;
void setup() {
Serial.begin(9600);
thumb.attach(PWMPin[0]);
index.attach(PWMPin[1]);
middle.attach(PWMPin[2]);
ring.attach(PWMPin[3]);
little.attach(PWMPin[4]);
state=GU;
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
}
void loop() {
if (Serial.available() > 0) {
char tmp;
tmp = Serial.read();
state=tmp-'0';
}
switch(state){
case GU:
thumb.write(OFF);
index.write(OFF);
middle.write(OFF);
ring.write(OFF);
little.write(OFF);
break;
case CHOKI:
thumb.write(OFF);
index.write(ON);
middle.write(ON);
ring.write(OFF);
little.write(OFF);
break;
case PA:
thumb.write(ON);
index.write(ON);
middle.write(ON);
ring.write(ON);
little.write(ON);
break;
default:
break;
}
}
PC側はこちらになります。なお、音声ファイルは「あみたろの声素材工房」の声素材を使用しました。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os, sys, inspect
src_dir = os.path.dirname(inspect.getfile(inspect.currentframe()))
arch_dir = 'lib/x64' if sys.maxsize > 2**32 else 'lib/x86'
sys.path.insert(0, os.path.abspath(os.path.join(src_dir, arch_dir)))
import Leap
import cv2
import winsound
import serial
import time
images = {}
images["choki"] = cv2.imread("img/janken_choki.png")
images["gu"] = cv2.imread("img/janken_gu.png")
images["pa"] = cv2.imread("img/janken_pa.png")
class SampleListener(Leap.Listener):
state=None
images={}
images["choki"]=cv2.imread("img/janken_choki.png")
images["gu"]=cv2.imread("img/janken_gu.png")
images["pa"]=cv2.imread("img/janken_pa.png")
def on_init(self, controller):
print("Initialized")
def on_connect(self, controller):
print("Connected")
def on_disconnect(self, controller):
# Note: not dispatched when running in a debugger.
print("Disconnected")
def on_exit(self, controller):
print("Exited")
def on_frame(self, controller):
# Get the most recent frame and report some basic information
frame = controller.frame()
# Get hands
for hand in frame.hands:
isPointToFront=[]
for finger in hand.fingers:
if(finger.direction[2]<0.0):
isPointToFront.append(True)
else:
isPointToFront.append(False)
if(isPointToFront[0] and isPointToFront[1] and isPointToFront[2] and
not isPointToFront[3] and not isPointToFront[4]):
cv2.imshow("Your Hand", self.images["choki"])
self.state="CHOKI"
if(isPointToFront[0] and not isPointToFront[1] and not isPointToFront[2] and
not isPointToFront[3] and not isPointToFront[4]):
cv2.imshow("Your Hand", self.images["gu"])
self.state="GU"
if(isPointToFront[0] and isPointToFront[1] and isPointToFront[2] and
isPointToFront[3] and isPointToFront[4]):
cv2.imshow("Your Hand", self.images["pa"])
self.state="PA"
cv2.waitKey(32)
if not frame.hands.is_empty:
print("")
def getState(self):
return self.state;
def main():
listener = SampleListener()
controller = Leap.Controller()
controller.add_listener(listener)
robohand = serial.Serial("COM3", 9600)
#main
winsound.PlaySound("se\\ikuyo_01.wav",winsound.SND_FILENAME)
cv2.imshow("Robot Hand", images["gu"])
cv2.waitKey(2000)
winsound.PlaySound("se\\jankenpon_01.wav",winsound.SND_FILENAME)
while(listener.getState() is None):
time.sleep(0.032)
controller.remove_listener(listener)
if(listener.getState()=="CHOKI"):
robohand.write('0')
cv2.imshow("Robot Hand", images["gu"])
cv2.waitKey(1)
winsound.PlaySound("se\\guu_01.wav", winsound.SND_FILENAME)
elif(listener.getState()=="PA"):
robohand.write('1')
cv2.imshow("Robot Hand", images["choki"])
cv2.waitKey(1)
winsound.PlaySound("se\\choki_01.wav", winsound.SND_FILENAME)
elif(listener.getState()=="GU"):
robohand.write('2')
cv2.imshow("Robot Hand", images["pa"])
cv2.waitKey(1)
winsound.PlaySound("se\\paa_01.wav", winsound.SND_FILENAME)
winsound.PlaySound("se\\kattakatta_01.wav", winsound.SND_FILENAME)
cv2.waitKey(5000)
robohand.close()
if __name__ == "__main__":
main()
動作結果
じゃんけんの手をLeapで認識して、じゃんけんの手の表現をサーボでやらせています。これで運動によるコミュニケーションができます。今日のデモ動画です。 pic.twitter.com/RpQZ7Y6atD
— あるふ (@alfredplpl) 2017年12月12日
感想
なんか負けると、わろえてくる。
透明でロボットの手が見づらいですが、
本来は水性スプレーで肌色に塗る予定でした。
もうちょっとロボハンド感が出てほしかったですね・・・。
#まとめ
運動によるコミュニケーションについて今回2回に渡り紹介しました。
正直語り足りないことが多すぎますが、本アドベントカレンダーではここまでにします。
次回は人間の認知を機械で実装するにはという話をします。
時間をください。