@[TOC](强化学习系列文章(二十二):AirSim自动驾驶仿真平台及其Python API分析)
AirSim自动驾驶仿真平台及其Python API分析
最近在做强化学习与自动驾驶的结合,于是接触到AirSim——微软开发的自动驾驶仿真平台,个人觉得做得非常不错,是我目前见过的最棒的自动驾驶仿真环境。原因如下:
- AirSim是一个持续更新的开源项目,现在还在开发迭代中,所以功能在不断完善,出了Bug也有人去维护,相比之下TORCS虽然也很不错,但是最近3年没有看到TORCS的更新了,一方面对现在的操作系统的适配可能不是很好,我配置了许久都没有成功,另一方面学术界工业界用的逐渐少了。
- AirSim的社区做的还不错,中文互联网上也有最近几年新出的经验贴。这也需要我们每位技术人无私而不懈的努力,感谢前辈。
- AirSim的接口写的不错,能以很低的成本封装成Gym接口,代码也很好看懂,而且提供预编译的可执行程序,对于我这种目前只需要在现成环境上测试强化学习算法的人来说,成本是最低的了。面向自动驾驶全场景的仿真平台还有很多,Apollo做的就不错,但是太大太复杂,学起来成本高,所以对我而言暂时不太合适。
我会用几篇文章的篇幅介绍如何使用预编译好的可执行AirSim仿真环境编写Gym风格交互接口,以及如何训练强化学习智能体。
本次首先介绍AirSim环境。
准备预编译环境
配置Unreal Engine4和AirSim需要费一些功夫,而验证强化学习算法的有效性其实不需要这些,因此我会优先选择预编译好的仿真环境。
首先在
https://github.com/microsoft/AirSim/releases上下载对应平台的预编译文件。如下所示,显示v1.3.1版本的Windows平台的环境文件,共提供了Blocks、Landscape Mountains、Coastline、City、Soccer_Field、Zhangjiajie六个场景的环境,将其下载解压到指定位置,运行相应可执行文件即可。
Windows系统的可执行文件就是一级路径下的exe文件,Linux系统则在./Neighborhood/AirSimNH/Binaries/Linux/AirSimNH
。
每次运行会被告知选择载具,yes是一辆SUV,no是四旋翼无人机。进入程序,按F1
呼出快捷键指南,按0
打开所有传感器画面,包括景深图像、目标分割图像、传统镜头图像,按退格键重启环境。
准备AirSim
下载AirSim源文件
git clone https://github.com/Microsoft/AirSim.git
cd AirSim
./setup.sh // 配置依赖
./build.sh //编译airsim库
安装Python-AirSim
pip install msgpack-rpc-python
pip install airsim
安装airsim
时,需要安装opencv-contrib-python
,可能需要很长时间的编译,也可能遇到如下的错误:
fatal error: boostdesc_bgm.i: No such file or directory
No module named skbuild
Could not build wheels for opencv-python which use PEP 517 and cannot be installed directly
Solution:pip install scikit-build
pip install opencv-contrib-python==3.4.2.16
测试Python API
cd /home/xxx/AirSim/PythonClient
# 运行AirSimNH,也就是Neighborhood环境里的可执行文件
python hello_car.py
# 切回NH环境,可以看到汽车在Python文件的控制下运动
hello_car.py分析
import setup_path
import airsim
import cv2
import numpy as np
import os
import time
import tempfile
# 连接到AirSim模拟器,需要事先打开模拟程序
client = airsim.CarClient()
client.confirmConnection()
client.enableApiControl(True)
print("API Control enabled: %s" % client.isApiControlEnabled())
car_controls = airsim.CarControls()
# 在/tmp/airsim_car/路径下保存运行截图
tmp_dir = os.path.join(tempfile.gettempdir(), "airsim_car")
print ("Saving images to %s" % tmp_dir)
try:
os.makedirs(tmp_dir)
except OSError:
if not os.path.isdir(tmp_dir):
raise
# 执行3个动作
for idx in range(3):
# 汽车当前状态
car_state = client.getCarState()
# car_state.collision 碰撞
# car_state.gear 变速箱档位
# car_state.handbrake 手刹
# car_state.maxrpm 最大转速
# car_state.rpm 当前转速
# car_state.speed 当前车速
# car_state.timestamp 当前时间戳
print("Speed %d, Gear %d" % (car_state.speed, car_state.gear))
# 直行
car_controls.throttle = 0.5 # 油门
car_controls.steering = 0 # 转向
client.setCarControls(car_controls)
print("Go Forward")
time.sleep(3) # 动作执行3秒
# 右前方
car_controls.throttle = 0.5
car_controls.steering = 1
client.setCarControls(car_controls)
print("Go Forward, steer right")
time.sleep(3) # 动作执行3秒
# 倒车
car_controls.throttle = -0.5
car_controls.is_manual_gear = True # 设置为手动挡
car_controls.manual_gear = -1 # 倒车档
car_controls.steering = 0
client.setCarControls(car_controls)
print("Go reverse, steer right")
time.sleep(3) # 动作执行3秒
car_controls.is_manual_gear = False # 设置为自动挡
car_controls.manual_gear = 0
# 刹车
car_controls.brake = 1
client.setCarControls(car_controls)
print("Apply brakes")
time.sleep(3) # 动作执行3秒
car_controls.brake = 0 # 移除刹车
# 获取图像,获取4个图像
responses = client.simGetImages([
airsim.ImageRequest("0", airsim.ImageType.DepthVis), #深度图像
airsim.ImageRequest("1", airsim.ImageType.DepthPerspective, True), #depth in perspective projection
airsim.ImageRequest("1", airsim.ImageType.Scene), #scene vision image in png format
airsim.ImageRequest("1", airsim.ImageType.Scene, False, False)]) #scene vision image in uncompressed RGB array
print('Retrieved images: %d' % len(responses))
for response_idx, response in enumerate(responses):
filename = os.path.join(tmp_dir, f"{idx}_{response.image_type}_{response_idx}")
if response.pixels_as_float:
print("Type %d, size %d" % (response.image_type, len(response.image_data_float)))
airsim.write_pfm(os.path.normpath(filename + '.pfm'), airsim.get_pfm_array(response))
elif response.compress: #png format
print("Type %d, size %d" % (response.image_type, len(response.image_data_uint8)))
airsim.write_file(os.path.normpath(filename + '.png'), response.image_data_uint8)
else: #uncompressed array
print("Type %d, size %d" % (response.image_type, len(response.image_data_uint8)))
img1d = np.fromstring(response.image_data_uint8, dtype=np.uint8) # get numpy array
img_rgb = img1d.reshape(response.height, response.width, 3) # reshape array to 3 channel image array H X W X 3
cv2.imwrite(os.path.normpath(filename + '.png'), img_rgb) # write to png
#restore to original state
client.reset()
client.enableApiControl(False)
重点解读simGetImages
函数和ImageRequest
函数。simGetImages
函数是从模拟器获取当前时刻图像的函数。其输入是一个list,list长度表示本次申请的图像数量,list每一个元素都由ImageRequest
函数确定;其输出是ImageResponse变量构成的list,list每个元素都有如下变量:
<ImageResponse> {
'camera_name': '0',
'camera_orientation': <Quaternionr> {
'w_val': 0.48415517807006836, # 应该是旋转角度
'x_val': -5.919639988860581e-06,
'y_val': -3.715734419529326e-05,
'z_val': -0.8749821186065674},
'camera_position': <Vector3r> {
'x_val': -0.1866702288389206,
'y_val': 5.542445659637451,
'z_val': -1.6028411388397217},
'compress': True, # 是否对图像进行压缩,压缩之后的二进制图像序列长度小于width*height
'height': 144,
'image_data_float': [ 0.0],
'image_data_uint8': b'\x89PNG' # 压缩成二进制编码的图像文件,需要转换才能使用
b'\r\n\x1a\n'
b'\x00\x00\x00\r'
b'IHDR'
b'\x00\x00\x01\x00'
b'\x00\x00\x00\x90'
b'\x08\x06\x00\x00'
b'\x00\xe7c\xb5'
b'\x91\x00\x00\x0e'
...
b'2\xf0?\xf3'
b'\xad\x84\xad['
b'\xdf\x19q\x00'
b'\x00\x00\x00I'
b'END\xae'
b'B`\x82',
'image_type': 3,
'message': '',
'pixels_as_float': False,
'time_stamp': 1609491667056476000,
'width': 256}
例如,可以这样查看图像中的变量:
responses = client.simGetImages([airsim.ImageRequest("0", airsim.ImageType.DepthVis)])
responses[0].width # 图像宽度
responses[0].height # 图像高度
下一节介绍AirSim图像处理