Webots講座の8回目です.引き続きWebotsの自動運転シミュレータを使っていきます.今回はキーボードによるマニュアル操作を導入します.これでゲームのように車を操作できますね.
レファレンス
- Keyboard (Cyberbotics Ltd.)
- Webots for Automobiles (Cyberbotics Ltd.)
- 参考にしたコード
- Webots/projects/vehicle/controllers/autonomous_vehile/autnomous_vehicle.c
キーボードの使い方
- キーボードの使い方は次のとおりです.
- 有効化
- keyboard.enable(TIME_STEP)メソッドでキーボードを有効化する.TIME_STEPはサンプリングの間隔[ms].
- 入力キーの取得
- メインループの中でkeyboard.getKey()を呼び出して入力されたキーを取得する.
- pythonの場合は入力されたキー値はUnicodeになっているので,比較するキーの値もordメソッドで変換する必要がある.
- 有効化
API
- class Keyboard:
# キーの値
END, HOME, LEFT, UP, RIGHT, DOWN, PAGEUP,
PAGEDOWN, NUMPAD_HOME, NUMPAD_LEFT, NUMPAD_UP,
NUMPAD_RIGHT, NUMPAD_DOWN, NUMPAD_END, KEY, SHIFT,
CONTROL, ALT-
- def enable(self, samplingPeriod):キーボード入力を有効化する.
- def disable(self):無効化する.
- def getSamplingPeriod(self):サンプリング期間を取得する
- def getKey(self):キーの値を取得する.
-
サンプルコード
- キーボード入力に関する部分だけ説明します.その他は以前とあまり変わっていません.
- キーボードを使うために7行目でcontrollerモジュールからKeyboardクラスをインポートしています.63行目でKeyboardクラスのインスタンスkeyboardを作っています.
- 179~203行目のcheckKeyboard()メソッドでキーボードからの入力されたキーの値(Unicode)とUP,Down, Left, Right, ’A’,’a’, ‘S’, ‘s’を比較して次の必要な処理をしています.このメソッドはrun()メソッドのメインループの235行目で呼び出されています.
- UP: 速度指令値を5[km/h]増加
- DOWN: 速度指令値を5[km/h]減速
- LEFT: 操舵指令値を左に1[°]増加
- RIGHT: 操舵指令値を右に1[°]増加
- ‘A’, ‘a’:自動運転モード
- ‘S’, ‘s’:速度指令値を0[km/h]に設定.停止.
"""robot_car controller."""
import cv2
import math
import numpy as np
from vehicle import Driver
from controller import GPS, Node
from controller import Keyboard
""" ロボットカークラス """
class RobotCar():
CRUSING_SPEED = 20 # 巡行速度[km/h]
MAX_SPEED = 250 # 最大速度[km/h]
LIMIT_STEERING_ANGLE = 0.5 # 操舵角の最大値[rad]
UNKNOWN = 99999.99 # 黄色ラインを検出できない時の値
FILTER_SIZE = 3 # 黄色ライン用のフィルタ
TIME_STEP = 30 # センサのサンプリング時間[ms]
CAR_WIDTH = 2.015 # 車幅[m]
CAR_LENGTH = 5.0 # 車長[m]
DEBUG = False # デバッグ用の情報表示
""" コンストラクタ """
def __init__(self):
# Welcome message
self.welcomeMessage()
self.cmd_speed = self.CRUSING_SPEED # 速度指令値
self.cmd_steering_angle = 0 # 操舵角指令地
# Driver
self.driver = Driver()
self.driver.setSteeringAngle(self.cmd_steering_angle)
self.driver.setCruisingSpeed(self.cmd_speed)
self.driver.setDippedBeams(True) # ヘッドライト転倒
# LIDAR (SICK LMS 291)
self.lidar = self.driver.getDevice("Sick LMS 291")
self.lidar.enable(self.TIME_STEP)
self.lidar_width = self.lidar.getHorizontalResolution()
self.lidar_range = self.lidar.getMaxRange()
self.lidar_fov = self.lidar.getFov();
# GPS
self.gps = self.driver.getDevice("gps")
self.gps.enable(self.TIME_STEP)
# Camera
self.camera = self.driver.getDevice("camera")
self.camera.enable(self.TIME_STEP)
self.camera_width = self.camera.getWidth()
self.camera_height = self.camera.getHeight()
self.camera_fov = self.camera.getFov()
print("camera: width=%d height=%d fov=%g" % \
(self.camera_width, self.camera_height, self.camera_fov))
# Display
self.display = self.driver.getDevice("display")
self.display.attachCamera(self.camera) # show camera image
self.display.setColor(0xFFFFFF)
self.display.setFont('Arial', 12, True)
# Keyboard
self.keyboard = Keyboard()
# Steer Angle
self.manual_steering = 0.0
# sdrive
self.auto_drive = True
""" ウエルカムメッセージ """
def welcomeMessage(self):
print("*********************************************")
print("* Welcome to a simple robot car program *")
print("*********************************************")
print("*** Auto Drive Mode ***")
print("Input [A] key to Auto Drive Mode")
""" 移動平均フィルタを実装しよう."""
def maFilter(self, new_value):
"""
ここに移動平均フィルタのコードを書く
"""
return new_value
""" ステアリングの制御: wheel_angleが正だと右折,負だと左折 """
def control(self, steering_angle):
# ここにPID制御などをコーディングする.
return steering_angle
""" 画素と黄色の差の平均を計算 """
def colorDiff(self, pixel, yellow):
d, diff = 0, 0
for i in range (0,3):
d = abs(pixel[i] - yellow[i])
diff += d
return diff/3
""" 黄色ラインを追従するための操舵角の計算 """
def calcSteeringAngle(self,image):
YELLOW = [95, 187, 203] # 黄色の値 (BGR フォーマット)
sumx = 0 # 黄色ピクセルのX座標の和
pixel_count = 0 # 黄色の総ピクセル数
for y in range(int(1.0*self.camera_height/3.0),self.camera_height):
for x in range(0,self.camera_width):
if self.colorDiff(image[y,x], YELLOW) < 30: # 黄色の範囲
sumx += x # 黄色ピクセルのx座標の和
pixel_count += 1 # 黄色ピクセル数の和
# 黄色ピクセルを検出できない時は no pixels was detected...
if pixel_count == 0:
return self.UNKNOWN
else:
y_ave = float(sumx) /(pixel_count * self.camera_width)
steer_angle = (y_ave - 0.5) * self.camera_fov
# print("dev=%g y_ave=%g sumx=%d pixel_count=%d" % (dev, y_ave, sumx, pixel_count))
return steer_angle
""" 障害物の方位と距離を返す. 障害物を発見できないときはUNKNOWNを返す.
ロボットカー正面の矩形領域に障害物がある検出する
"""
def calcObstacleAngleDist(self, lidar_data):
OBSTACLE_HALF_ANGLE = 20.0 # 障害物検出の角度.中央の左右20[°].
OBSTACLE_DIST_MAX = 20.0 # 障害物検出の最大距離[m]
OBSTACLE_MARGIN = 0.1 # 障害物回避時の横方向の余裕[m]
sumx = 0
collision_count = 0
obstacle_dist = 0.0
for i in range(int(self.lidar_width/2 - OBSTACLE_HALF_ANGLE), \
int(self.lidar_width/2 + OBSTACLE_HALF_ANGLE)):
dist = lidar_data[i]
if dist < OBSTACLE_DIST_MAX:
sumx += i
collision_count += 1
obstacle_dist += dist
if collision_count == 0 or obstacle_dist > collision_count * OBSTACLE_DIST_MAX: # 障害物を検出できない場合
return self.UNKNOWN, self.UNKNOWN
else:
obstacle_angle = (float(sumx) /(collision_count * self.lidar_width) - 0.5) * self.lidar_fov
obstacle_dist = obstacle_dist/collision_count
if abs(obstacle_dist * math.sin(obstacle_angle)) < 0.5 * self.CAR_WIDTH + OBSTACLE_MARGIN:
return obstacle_angle, obstacle_dist # 衝突するとき
else:
return self.UNKNOWN, self.UNKNOWN
""" 速度の設定 """
def setSpeed(self, speed):
if speed > self.MAX_SPEED: # [km/h]
speed = self.MAX_SPEED
elif speed < - self.MAX_SPEED:
speed = -self.MAX_SPEED
self.driver.setCruisingSpeed(speed)
""" ステアリング角度の手動変更 """
def setSteeringAngle(self, angle):
if self.auto_drive == False:
print("*** Manual Drive Mode ***")
print("Input [A] key to Auto-Drive Mode")
if angle > self.LIMIT_STEERING_ANGLE: # [rad]
angle = self.LIMIT_STEERING_ANGLE
elif angle < -self.LIMIT_STEERING_ANGLE:
angle = - self.LIMIT_STEERING_ANGLE
self.driver.setSteeringAngle(angle)
""" キーボードの入力チェック """
def checkKeyboard(self):
key = self.keyboard.getKey()
if key == self.keyboard.UP:
#print("UP key is pushed")
self.cmd_speed += 5.0
elif key == self.keyboard.DOWN:
#print("DOWN key is pushed")
self.cmd_speed -= 5.0
elif key == self.keyboard.RIGHT:
#print("Right key is pushed")
self.auto_drive = False
self.cmd_steering_angle += math.pi/ 180.0 # 1[deg]をradianに変換
elif key == self.keyboard.LEFT:
#print("LEFT key is pushed")
self.auto_drive = False
self.cmd_steering_angle -= math.pi/ 180.0
elif key == ord('A') or key == ord('a'): # ord関数は文字のUnicode値を返す
self.auto_drive = True
#print("A/a key is pushed")
print("*** Auto Drive Msode ***")
elif key == ord('S') or key == ord('s'): # ord関数は文字のUnicode値を返す
self.auto_drive = False
#print("S/s key is pushed ")
self.cmd_speed = 0
""" 描画更新 """
def updateDisplay(self): # 3Dシミュレーションウインドウに文字を表示
gps_speed = self.gps.getSpeed()* 3.6 # [km\h]
if gps_speed > self.MAX_SPEED:
return
self.display.setColor(0x000000)
self.display.fillRectangle(10,10, 90,20)
txt = "%.1f [km/h]" % float(self.gps.getSpeed()* 3.6)
# print(txt)
self.display.setColor(0xFFFFFF)
self.display.drawText(txt, 18, 15)
""" 実行 """
def run(self):
step = 0
# キーボー入力の有効化
self.keyboard.enable(self.TIME_STEP)
# main loop
while self.driver.step() != -1:
step +=1
BASIC_TIME_STEP = 10 # シミュレーションの更新は10[ms]
# センサの更新をTIME_STEP[ms]にする.この例では60[ms]
if step % int(self.TIME_STEP / BASIC_TIME_STEP) == 0:
# キーボード入力
self.checkKeyboard()
# Liar
lidar_data = self.lidar.getRangeImage()
# GPS
values = self.gps.getValues()
# Camera
camera_image = self.camera.getImage()
# OpenCVのPythonは画像をnumpy配列で扱うので変換する.
cv_image = np.frombuffer(camera_image, np.uint8).reshape((self.camera_height, self.camera_width, 4))
# 障害物回避
obstacle_angle, obstacle_dist = self.calcObstacleAngleDist(lidar_data)
STOP_DIST = 5 # 停止距離[m]
if obstacle_dist < STOP_DIST:
if self.DEBUG == True:
print("%d:Find obstacles(angle=%g, dist=%g)" % \
(step, obstacle_angle, obstacle_dist))
self.cmd_speed = 0 # 障害物があるので速度指令値を0に設定
else:
if self.auto_drive == False:
pass
else:
# 操作量(ステアリング角度)の計算
steering_angle = self.maFilter(self.calcSteeringAngle(cv_image))
if steering_angle != self.UNKNOWN:
if self.DEBUG == True:
print("%d:Find the yellow line" % step)
self.cmd_speed = self.CRUSING_SPEED # 速度指令値の設定
self.cmd_steering_angle = self.control(steering_angle) # ステアリングの制御(比例制御の簡易版)
else:
if self.DEBUG == True:
print("%d:Lost the yellow line" % step)
self.cmd_speed = 0 # 追従する黄色線をロストしたので速度指令値を0に設定する.
# self.driver.setBrakeIntensity(0.5) # 追従する黄色線をロストしたので停止する.
# 描画更新
self.updateDisplay()
# 速度指令
self.setSpeed(self.cmd_speed)
# 操舵指令
self.setSteeringAngle(self.cmd_steering_angle)
""" メイン関数 """
def main():
robot_car = RobotCar()
robot_car.run()
""" このスクリプトをモジュールとして使うための表記 """
if __name__ == '__main__':
main()
ハンズオン
- Webots入門講座3:自動運転シミュレータ (Python)を実施した要領で,新規プロジェクトrobot_car,コントローラrobot_car_lidar.pyを作成し,サンプルコードを実行しましょう.
- サンプルコードを実行すると初めは自動運転モードでロボットカーが進みます.RIGHT(→),LEFT(←),’S’,’s’キーを押すとマニュアル運転モードになります.マニュアルモードでロボットをしばらく走らせてみましょう!’A’,’a’キーを押すと自動運手モードに切り替わります.
- このサンプルでは障害物回避は組み込まれていないので,自動運転モードにそれを組み込んでコースを1周するプログラムを作成して,マニュアル運転モードと比較してください.
終わり
コメント