目次
下の図のようなコースを各チームで作成し、「ミッション」を遂行するためのロボットを作成せよ。(私はコース2を担当した。)
ミッション
1.ロボットを正方形Y内におき、Lをスタート
2.Eを一時停止して直進
3.Iを一時停止して左折
4.Hを直進
5.Kを直進
6.Jを左折
7.Cを一時停止して右折
8.Eを一時停止して直進
9.Gを一時停止して直進
10.長方形X内に入って停止
また、一時停止の指定がある場所は、1秒間停止し、ボールはロボットが弧IHKJ上にある時にP地点の空き缶に当てることとする。
今回はライントレースをするロボットが必要であるということで、単純な直進、旋回をしやすい、車型ロボットを採用した。車体にはライントレースをするために必要なセンサ類(超音波センサ、タッチセンサ、カラーセンサ)を取り付けている。超音波センサは、センサの位置から認識した物体までの距離を1mmの単位で読み取り表示する。カラーセンサは、自分の出した光がどれほど反射されているのかを読み取り、0から100の値で表示する。タッチセンサは、押されているときは1、押されていないときは0を表示する。
以下で、センサ類の扱いとボール投射機構の説明をする。
カラーセンサは車輪と離れすぎていると少しの車輪の動きでカラーセンサの白黒の値が急激に変化する。従ってライントレースのジグザグした動きが目立ち、安定したトレースが失われる可能性がある。そうしたことを防ぐために、カラーセンサの値が車輪の動きに左右されないよう、車輪の近くにカラーセンサを設置した。
超音波センサはボールを空き缶に正確にぶつけるために、投射機構のすぐ下、つまり車体の真ん中に設置する必要があった。
MediumMotorを利用し、滑り台のイメージでボールを投射する。車体がボールを投射する該当場所(今回はKとした)に到達し、空き缶をサーチすると、そこに向かって、MediumMotorに繋がったはしごが下を向き、ボールを転がす。
事前に行ったカラーセンサの値の調査では、黒を映しているときに12から15の値をとり、白を映しているときは少なくとも50以上の値をとることが判明した。また、白と黒の境界上では少なくとも20以上40以下の値をとり、交差点に差し掛かると、カラーセンサが黒を立て続けに読み取ることで、ほとんど確実に13以下の値をとっていることがわかった。
今回の課題で肝となるライントレースだが、白と黒線の境界を判別して進むプログラムをした。全部で4種類のライントレースのプログラムを作った。以下に一例を挙げる。
def linetrace_rin(): c1=cs.value() while c1>13: #カラーセンサの値が14以上なら次の動きを繰り返す。 c1=cs.value() x=(abs(c1-10)/70)*100 #xの値を決める。 if x>100: #xの値の最大を100とする。 x=100 l=(x/100)*210-30 #左車輪の速度lを定める。 r=150-l #右車輪の速度rを定める。 ml.run_forever(speed_sp=l,stop_action='brake') #与えられた速度lで進む。 mr.run_forever(speed_sp=r,stop_action='brake') #与えられた速度rで進む。 stop() #カラーセンサの値が13以下なら動きをやめる。
このプログラムでライントレースを行う本質は左右の車輪の速度調節である。カラーセンサが白を検知しているときには、xの値は約70である。(c1=60として概算)これをlに代入すると、l=117であり、更にrはr=33となる。つまり、車体はやや右前に進み続ける。次にカラーセンサが黒と白の境界を検知したとき(c1=30ほどとする)には、x=28,l=30,r=120となり、やや左前に進む続ける。そしてまた右前、左前・・・と続けることで交差点に来るまでライントレースを行う。ライントレースを始めた直後はlとrの差が大きくなり、多少車体がジグザグした動きになるが、この演算を繰り返すうちにlとrの値の差異がほとんどなくなり、ジグザグせず、まるで真っすぐ進んでいるかのように見える。
上記のプログラムは車の進行方向に対し、左に白、右に黒線がある場合でしかライントレースができない。もちろん、そのために左に黒線、右に白がある場合を想定したプログラムを別で用意した。
def linetrace_lin(): c1=cs.value() while c1>13: #カラーセンサの値が14以上なら次の動きを繰り返す。 c1=cs.value() x=abs((c1-10)/70)*100 #xの値を決める。 if x>100: #xの値の最大を100とする。 x=100 r=(x/100)*210-30 #右車輪の速度rを定める。 l=150-r #左車輪の速度lを定める。 ml.run_forever(speed_sp=l,stop_action='brake') #与えられた速度lで進む。 mr.run_forever(speed_sp=r,stop_action='brake') #与えられた速度rで進む。 stop() #カラーセンサの値が13以下なら動きをやめる。
これは、一つ目のプログラムのlとrを入れ替えただけであるので、説明は省略する。
こうして、黒線がどちら側にあっても、プログラムを場合によって変えることでライントレースできる。以下の画像が、ライントレースのイメージである。
以下を入力した。
#!/usr/bin/env python3 from ev3dev.ev3 import * import time ml=LargeMotor('outA') #左側の車輪を動かすモータをmlとした。 mr=LargeMotor('outD') #右側の車輪を動かすモータをmrとした。 mm=MediumMotor('outC') #投射機構を上下させるモータをmmとした。 cs=ColorSensor('in4') #カラーセンサをcsとする。 ts=TouchSensor('in1') #タッチセンサをtsとする。 us=UltrasonicSensor('in2') #超音波センサをusとする。
それぞれのモータを止める。適宜、利用するために作った。
def stop(): ml.stop() mr.stop()
指定した秒数、走らせる関数。扱いやすくするためにスピードは遅く設定した。
def runtime(l,r): ml.run_timed(time_sp=l*1000,speed_sp=70,stop_action='brake') #l秒間進む。 mr.run_timed(time_sp=r*1000,speed_sp=70,stop_action='brake') #r秒間進む。 time.sleep((l+r)/2)
指定した角度だけ、車輪を回転させる関数。バッテリー残量によって異なるが、バッテリーが満タンに近いときはl=700,r=-700にすることで360度近く時計回りに一回転できる。バッテリー残量が少ないときはlとrの値をもう少し大きくする必要がある。
def angle(l,r): ml.run_to_rel_pos(position_sp=l,speed_sp=70,stop_action='hold') #左の車輪をl度回転させる。 mr.run_to_rel_pos(position_sp=r,speed_sp=70,stop_action='hold') #右の車輪をr度回転させる。
ボールを転がすために、はしごを降ろす関数である。降ろした後にはしごを再び元の位置に戻す。
def shoot(): mm.run_to_rel_pos(position_sp=-60,speed_sp=150,stop_action='hold') #はしごを降ろす。 time.sleep(3) mm.run_to_rel_pos(position_sp=60,speed_sp=150,stop_action='hold') #はしごを戻す。 time.sleep(3)
その場で一回転して最も近い位置にある物体にボールをぶつける関数。ぶつけた後にコースに復帰する。
def discover(): t0=time.time() #計測開始時間をt0とする。 u1=us.value() #u1を超音波センサが計測した値とする。 ml.run_to_rel_pos(position_sp=-700,speed_sp=150,stop_action='hold') #一回転する。 mr.run_to_rel_pos(position_sp=700,speed_sp=150,stop_action='hold') t2=time.time() while t2-t0<4.7: #t2-t0が4.7より小さいなら、最小値を更新し続ける。 u2=us.value() #超音波センサで計測した値をu2とする。 if u2<u1: #計測値が小さくなる。つまり、より車体に近い物体を計測した場合、そこまでの距離を測ることができた場合、以下に進む。 u1=u2 #最小値を更新する。 t1=time.time() #更新したときの時間をt1とする。 t2=time.time() #t2を計測する。 time.sleep(1) ml.run_timed(time_sp=(t1-t0)*1000,speed_sp=-150,stop_action='hold') #最も近い物体がある向きに向く。 mr.run_timed(time_sp=(t1-t0)*1000,speed_sp=150,stop_action='hold') #同上 time.sleep(t1-t0) time.sleep(1) shoot() #ボールを転がす。 ml.run_timed(time_sp=(t1-t0)*1000,speed_sp=150,stop_action='hold') #先程向いた分だけ、元に戻る。(一回転した後にあった位置と同じ位置に戻る。) mr.run_timed(time_sp=(t1-t0)*1000,speed_sp=-150,stop_action='hold') #同上 time.sleep(t1-t0)
先程紹介した二つのライントレースする関数は省略し、残る二つの紹介をする。これらは、交差点にいる時にセンサが黒の値(確実に40よりは小さい)のみをとることを利用して、黒線から白の部分に脱出し、白の値(40よりは大きい)を認識するまでライントレースの一部として使用した関数である。先の二つ同様、黒線が右にあるときと左にあるときで2パターン関数が存在する。
黒線が右にある場合。
def linetrace_rout(): c1=cs.value() #計測値をc1とする。 while c1<40: #c1<40ならば以下を繰り返す。 c1=cs.value() #c1を計測。 x=(abs(c1-10)/70)*100 #xの値を求める。 if x>100: #xの値の最大値を100とする。 x=100 l=(x/100)*120-40 #lの値を決める。 r=40-l #rの値を決める。 ml.run_forever(speed_sp=l,stop_action='brake') #lの速度で左車輪を動かす。 mr.run_forever(speed_sp=r,stop_action='brake') #rの速度で右車輪を動かす。 stop() #止める。
黒線が左にある場合。
def linetrace_lout(): c1=cs.value() #計測値をc1とする。 while c1<40: #c1<40ならば以下を繰り返す。 c1=cs.value() #c1を計測。 x=(abs(c1-10)/70)*100 #xの値を求める。 if x>100: #xの値の最大値を100とする。 x=100 r=(x/100)*120-40 #rの値を求める。 l=40-r #lの値を求める。 ml.run_forever(speed_sp=l,stop_action='brake')#lの速度で左車輪を動かす。 mr.run_forever(speed_sp=r,stop_action='brake')#rの速度で右車輪を動かす。 stop() #止める。
先述のプログラムとこのプログラムの計4つを組み合わせることで、1つのコースのライントレースを繰り返していく。
課題の指定通り、交差点ごとにひとつづつ関数を作った。また、実験段階で、どこで失敗、成功しているか明らかにするために、交差点までの動きが終わった時にprintをしている。
LからEまでの関数。
def LtoE(): runtime(2,2) #START位置のすぐそばの黒線でライントレースしないように、カラーセンサに依存せずに直進させる。 linetrace_rin() #黒線の左側でライントレース。Eの交差点でwhileの条件を満たせずに止まる。 time.sleep(1) #課題の指示通り、1秒停止する。 print("LtoE")
EからIまでの関数。
def EtoI(): runtime(2,2) #交差点内の黒線でライントレースしないように、カラーセンサに依存せずに直進させる。 linetrace_rin() #黒線の左側でライントレース。Iの交差点でwhileの条件を満たせずに止まる。 time.sleep(1) #課題の指示通り、1秒停止する。 print("EtoI")
IからHまでの関数。
def ItoH(): linetrace_rout() #明確な白を検知するまで反時計回りにライントレース。 linetrace_rin() #黒線の左側でライントレース。Hの交差点でwhileの条件を満たせずに止まる。 print("ItoH")
HからKまでの関数。
def HtoK(): angle(70,0) #交差点内の黒線でライントレースして、Gに行かないように、黒線HGを越す。 time.sleep(2) linetrace_rin() #黒線の左側でライントレース。Kの交差点でwhileの条件を満たせずに止まる。 print("HtoK")
KからJまでの関数。
def KtoJ(): angle(90,30) #交差点内の黒線でライントレースして、Bに行かないように、黒線BKを越す。 time.sleep(2) linetrace_rin() #黒線の左側でライントレース。Jの交差点でwhileの条件を満たせずに止まる。 print("KtoJ")
JからCまでの関数。
def JtoC(): angle(40,100) #今後のコースを考えると、逆(黒線の右側)のライントレースをした方が良いので、黒線を超える。 time.sleep(2) linetrace_lin() #黒線の右側でライントレース。Cの交差点でwhileの条件を満たせずに止まる。 time.sleep(1) #課題の指示通り、1秒停止する。 print("JtoC")
CからEまでの関数。
def CtoE(): angle(120,0) #今後のコースを考えると、逆(黒線の左側)のライントレースをした方が良いので、黒線を超える。 time.sleep(2) linetrace_rin() #黒線の左側でライントレース。U字でwhileの条件を満たせずに次に移る。 linetrace_rout() #黒線の左側でライントレース。直角路でwhileの条件を満たせずに次に移る。 linetrace_rin() #黒線の左側でライントレース。Eの交差点でwhileの条件を満たせずに止まる。 time.sleep(1) #課題の指示通り、1秒停止する。 print("CtoE")
EからGまでの関数。
def EtoG(): runtime(2,2) #交差点内の黒線でライントレースしないように、カラーセンサに依存せずに直進させる。 linetrace_rin() #黒線の左側でライントレース。Fでwhileの条件を満たせずに次に移る。 linetrace_rout() #黒線の左側でライントレース。U字でwhileの条件を満たせずに次に移る。 linetrace_rin() #黒線の左側でライントレース。Gの交差点でwhileの条件を満たせずに止まる。 time.sleep(1) #課題の指示通り、1秒停止する。 print("EtoG")
GからGOALまでの関数。
def GOAL(): runtime(2,2) #交差点内の黒線でライントレースしないように、カラーセンサに依存せずに直進させる。 linetrace_rin() #黒線の左側でライントレース。GOALの黒線の枠でwhileの条件を満たせずに止まる。 runtime(5,5) #交差点内の黒線でライントレースしないように、カラーセンサに依存せずに直進させる。車体をGOALの枠内に完全に入れる。 print("GOAL")
LtoE() EtoI() ItoH() HtoK() discover() KtoJ() JtoO() OtoE() EtoG() GOAL()
私の担当したコースでは本番は行っていないが、相方が成功していたので良かった。他の班より滑らかに動いていたのでライントレースのプログラムが良かったのだと思う。
今回最も苦労した部分は、ライントレースの方法である。当初は、車体が動いた時の、カラーセンサの値の変化率を用いて車輪の速度を変化させることでライントレースを試みた。この方法は、安定性に欠け、激しいジグザグ運動をしたり、交差点で曲がることができない欠陥があったために断念した。
変化率でのライントレースを断念した次は、変化率を既存の関数に代入して制御することを試みた。シグモイド関数や双曲線正接関数(tanhx)を利用して、緩やかにジグザグさせようと試みたが、pythonでの数式の扱いがよくわからなかったことと思った以上にジグザグが激しかったことで断念した。tanhxを例にとって説明する。
tanhx=(exp(x)-exp(-x))/(exp(x)+exp(-x)) であり、概形は下図である。
このグラフが示すように、xの値が極端に大きかったり小さかったりしても、y軸の値域が-1<=y<=1と、小さい範囲で限定されているため、変化率が極端な数字をとっても大きなジグザグが起こらないだろうと考えた。実際に試したプロブラムは以下である。なお、黒線の左側をライントレースすることを想定している。
def forward(l,r): ml.run_forever(speed_sp=l,stop_action='brake') #lの速度で進む。 mr.run_forever(speed_sp=r,stop_action='brake') #rの速度で進む。 def linetrace(): while ts.value()==0: #試験的にこの定義で動かしていたので、タッチセンサで止まるようにした。 t1=time.time() #t1とc1を同時に計測 c1=cs.value() time.sleep(0.05) #変化率の分母は0.05秒 t2=time.time() #t2とc2を同時に計測 c2=cs.value() w=((c2-c1)/(t2-t1)) #変化率wは0.05当たりのセンサの値の変化。 x=round(w)/100 #変化率を四捨五入し、/100してxを作る。 y=(math.exp(x)-math.exp(-x))/(math.exp(x)+math.exp(-x)) #xをtanhxの式に代入し、yを求める。 z=round(y*150) #yを*150して四捨五入。基本的に、zが正ならば白から黒に変化したときの計測であり、zが負ならば黒から白に変化したときの計測である。 if z>0: #zの値により、白から黒に移動したとわかり、白の部分へ移動するために右車輪の速度を左よりも大きくして移動する。 a=50-z b=50+z if 0>z: #zの値により、黒から白に移動したとわかり、黒の部分へ移動するために左車輪の速度を右よりも大きくして移動する。 a=50-z b=50+z if z==0: #変化がないならば直進する。 a=50 b=50 forward(a,b) #a,bの速度で車輪を動かす。
上記のように安定した動きができなかった。理由を考察すると、ひとたび大きくジグザグすると、そのタイミングで計測した変化率もおかしい値になり、結果としてよりジグザグしてしまう悪循環に陥ったことが安定性を失う原因ではないかと思う。
そこでたどり着いたのが今の方法だった。x,l,rの関係式の係数や定数項の値を調整に調整を重ね、現在のような、ほとんどジグザグしない、安定してライントレースができるロボットが完成した。
上記以外にも没にした案があった。それは、カラーセンサが黒を認識した回数を計測して、交差点ごとに決められた値を超えたら停止するという案である。この方法を用いても課題は達成できるが、各交差点で、だいたい何回黒を計測するかを事前に見極める必要があり、非常に面倒くさい。そのため、この案より現在のプログラムの方が優れているために没にした。以下にそのプログラムを載せる。
まずはライントレースの定義から。上記のx,r,lの式の中身が異なるが、この式だと、採用したライントレースのプログラムより少し遅い。没案ではスピードを遅くした方が正確に交差点で止まることが可能なため、以下のようにしてある。
def linetrace_l(): c1=cs.value() #カラーセンサの計測値をc1とする。 x=abs((c1-10)/70)*100 #xの値を決定。 if x>100: #xの最大値は100とする。 x=100 r=(x/100)*60-20 #rの値を確定。 l=20-r #lの値を確定。 ml.run_forever(speed_sp=l*5,stop_action='brake') #l*5の速度で進む。 mr.run_forever(speed_sp=r*5,stop_action='brake') #r*5の速度で進む。
当然逆側のライントレースもある。掲載は割愛。
次に問題のプログラム。これはコース1のGからEまでを渡るものである。何回も計測した結果、GからEまでの間にカラーセンサは約85回ほど明確な黒の値(11や12)を取ることがわかったため、この結果を利用している。
def GtoE(): i=0 #明確な黒の値を取った回数をiとし、この回数で制御する。 c2=cs.value() #計測値をc2とする。 while i<85: #明確な黒の値を85回とるまで以下を繰り返す。 c2=cs.value() linetrace_l() #ライントレースする。 if c2<13: #明確な黒の値を取ったときにiを+1する。 i=i+1 else: #明確な黒の値でない場合、iに+1しない。 i=i stop() #止まる。
前述したが、この方法は事前に何回も交差点ごとのiの値を考慮する必要がある。更には充電状況により、EV3の演算能力が変化し、カラーセンサが1秒あたりに計測する回数も異なる。非常に面倒なライントレースであり、成功率も高くないため、没案となった。
前回の課題とは異なり、今回はコースが与えられていて、自分がやるべきことがはっきりしていたために、課題に取り組みやすく、発表期限にも十分に間に合うことが出来た。また、成功率も100%近いので、出来も良いと思う。この部分は、自分でも評価したい。
反省としては、ライントレースの速度だ。できる限り速く動かすようにx,l,rの中身を工夫したつもりだったが、他の班を見る限り、もう少し速度を上げても良さそうであった。ただ、不安定になってライントレースに失敗するようでは本末転倒なので、安定性を失わないギリギリのx,l,rの中の定数や係数の値を模索してみたい。
https://www.youtube.com/watch?v=TXf0GQCezDk
総計:1166 今日:1 昨日:0