@[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

python仿真5G NR python仿真平台_自动驾驶

每次运行会被告知选择载具,yes是一辆SUV,no是四旋翼无人机。进入程序,按F1呼出快捷键指南,按0打开所有传感器画面,包括景深图像、目标分割图像、传统镜头图像,按退格键重启环境。

python仿真5G NR python仿真平台_自动驾驶_02

准备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-buildpip 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图像处理