Airsim Python API文档整理(1.3.1版本)

AirSim公开了API,可以调用API与仿真器进行交互(核心就是通过RPC向仿真器发消息,进行模拟),可以通过API获取图像、状态和控制车辆等。

API有Python版本和C++版本的,Python更容易上手,方便后续的C++学习。

1 Python安装

pip install msgpack-rpc-python

如果没有编译源码,使用pip install airsim安装airsim,如果从官网下载的代码,可以进入PythonClient文件夹下,输入下述代码进行安装,Python的许多API在开发中,最好使用pip安装,获得最新的包。

python setup.py install

官方已经封装好了一些仿真环境,直接去releases 下载即可,然后打开仿真器,进入PythonClient\car\并执行,就可以看到车动起来了。

python hello_car.py

切记,不要强制退出,如果强制退出,环境的车会一直动
Airsim Python API文档整理(1.3.1版本)_第1张图片

2 代码解读:Hello Car

下面是如何用python控制car的代码。

# PythonClient/car/hello_car.py
import airsim
import time

# 连接到Airsim仿真器
client = airsim.CarClient()

client.confirmConnection() # 每1秒检查一次连接状态,并在控制台中报告,以便用户可以查看连接的进度(实际操作只显示一次,奇怪)。
# Connected!
# Client Ver:1 (Min Req: 1), Server Ver:1 (Min Req: 1)

client.enableApiControl(True) # 默认是false,有的车辆不允许用API控制,所以用isApiControlEnabled可以查看是否可以用API控制
# 仿真器左上角会显示:ControlMode: API

car_controls = airsim.CarControls() # 获得车辆控制数据
#  {   'brake': 0,
#    'gear_immediate': True,
#    'handbrake': False,
#    'is_manual_gear': False,
#    'manual_gear': 0,
#    'steering': 0,
#    'throttle': 0}

for idx in range(3):
    # get state of the car
    car_state = client.getCarState() #获取车辆状态,坐标统一是NED坐标,使用国际单位。
	#  {   'gear': 0,
	#    'handbrake': False,
	#    'kinematics_estimated':  {   'angular_acceleration':  {   'x_val': 0.0,
	#    'y_val': 0.0,
	#    'z_val': 0.0},
	#    'angular_velocity':  {   'x_val': 0.0,
	#    'y_val': 0.0,
	#    'z_val': 0.0},
	#    'linear_acceleration':  {   'x_val': 0.0,
	#    'y_val': 0.0,
	#    'z_val': 0.0},
	#    'linear_velocity':  {   'x_val': 0.0,
	#    'y_val': 0.0,
	#    'z_val': -0.0},
	#    'orientation':  {   'w_val': 1.0,
	#    'x_val': 3.1523319194093347e-05,
	#    'y_val': 0.0,
	#    'z_val': 0.0},
	#    'position':  {   'x_val': 0.0,
	#    'y_val': -2.2888182229507947e-06,
	#    'z_val': 0.23400458693504333}},
	#    'maxrpm': 7500.0,
	#    'rpm': 0.0,
	#    'speed': 0.0,
	#    'timestamp': 1597385459957857300}
    print("Speed %d, Gear %d" % (car_state.speed, car_state.gear))

    # 前进,配置car_controls,之后使用setCarControls设置状态
    # 这允许您设置油门(throttle),转向(steering),手制动(handbrake)和自动或手动档位(auto or manual gear)
    car_controls.throttle = 0.5
    car_controls.steering = 0
    client.setCarControls(car_controls)
    print("Go Forward")
    time.sleep(3)   # let car drive a bit

    # 前进+右转
    car_controls.throttle = 0.5
    car_controls.steering = 1
    client.setCarControls(car_controls)
    print("Go Forward, steer right")
    time.sleep(3)   # let car drive a bit

    # 倒车,记得要复原
    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)   # let car drive a bit
    car_controls.is_manual_gear = False; # change back gear to auto
    car_controls.manual_gear = 0  

    # 急刹
    car_controls.brake = 1
    client.setCarControls(car_controls)
    print("Apply brakes")
    time.sleep(3)   # let car drive a bit
    car_controls.brake = 0 #remove brake
    
    # 获取相机图像,图像API后续专门整理
    responses = client.simGetImages([
        airsim.ImageRequest("0", airsim.ImageType.DepthVis),  #深度可视化图像
        airsim.ImageRequest("1", airsim.ImageType.DepthPerspective, True), #获取透视投影深度
        airsim.ImageRequest("1", airsim.ImageType.Scene), #Png格式的场景图像
        airsim.ImageRequest("1", airsim.ImageType.Scene, False, False)])  #未压缩的RGB图像
    print('Retrieved images: %d', len(responses))

    # 图像存储
    for response in responses:
        filename = 'c:/temp/py' + str(idx)
        if not os.path.exists('c:/temp/'):
            os.makedirs('c:/temp/')
        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 

# 将车辆初始化处理,后续如果需要重新控制则需要再次调用`enableApiControl`或`armDisarm`.
client.reset()
client.enableApiControl(False) # 取消仿真器API控制

3 代码解读:Hello Drone

下面是个例子来控制无人机,代码在文件夹PythonClient\multirotor下。

import setup_path 
import airsim
import numpy as np
import os
import tempfile
import pprint
import cv2

# 连接直Airsim仿真器
client = airsim.MultirotorClient()
client.confirmConnection()
client.enableApiControl(True)
client.armDisarm(True)

state = client.getMultirotorState() # 获取无人机数据
imu_data = client.getImuData() # 获取IMU数据
barometer_data = client.getBarometerData() # 获取气压计数据
magnetometer_data = client.getMagnetometerData() # 获取磁力计数据
gps_data = client.getGpsData() # 获取GPS数据

# 起飞
airsim.wait_key('Press any key to takeoff')
client.takeoffAsync().join()
# 移动到某个位置
airsim.wait_key('Press any key to move vehicle to (-10, 10, -10) at 5 m/s')
client.moveToPositionAsync(-10, 10, -10, 5).join()

client.hoverAsync().join() # 悬停

# 采集图像
airsim.wait_key('Press any key to take images')
responses = client.simGetImages([
    airsim.ImageRequest("0", airsim.ImageType.DepthVis),  #depth visualization image
    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 RGBA array
print('Retrieved images: %d' % len(responses))

tmp_dir = os.path.join(tempfile.gettempdir(), "airsim_drone")
print ("Saving images to %s" % tmp_dir)
try:
    os.makedirs(tmp_dir)
except OSError:
    if not os.path.isdir(tmp_dir):
        raise

for idx, response in enumerate(responses):

    filename = os.path.join(tmp_dir, str(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 4 channel image array H X W X 3
        cv2.imwrite(os.path.normpath(filename + '.png'), img_rgb) # write to png

airsim.wait_key('Press any key to reset to original state')

client.armDisarm(False)
client.reset()
# that's enough fun for now. let's quit cleanly
client.enableApiControl(False)

Airsim Python API文档整理(1.3.1版本)_第2张图片

4 一些通用的API

  • ping: 测试与仿真器是否连通的。
  • simPrintLogMessage: 在仿真器的窗口打印特定消息。如果这个消息前面没有则创建新行,如果有那就直接改掉对应的值。simPrintLogMessage("Iteration: ", to_string(i)) ,如果i变了,直接覆盖之前的值。
  • simGetObjectPose, simSetObjectPose: 设置或获取目标的位姿(只有姿态和位置,没有尺度),目标在UE4中应该是Actor类型,通过名字(tag)进行搜索。名称在每次运行中是自动生成的,不是永久的。如果想通过名字进行检索,必须在UE编辑器中取消自动命名。如果场景中有多个Actor名称一样,返回第一个信息,如果没有返回的是NaN的信息。对于 simSetObjectPose,指定的Actor必须将Mobility设置为Movable。simSetObjectPose有一个参数teleport 意味着物体可以在其他物体中移动moved through other objects ,如果移动成功则返回true。

AirSim提供了全面的图像api,可以从多个摄像机中获取各种图像数据。相机的预设置参考之前的博客《Airsim(1.3.1版本)setting.json帮助文档解析》,还有用于检测碰撞状态的API。

4.1 暂停和继续的API

可以使用pause(True)实现暂停。可能会遇到这样的情况,特别是在使用强化学习时,会在指定的时间内运行模拟,然后自动暂停。当模拟暂停时,可以执行一些昂贵的计算,发送一个新命令,然后在指定的时间内再次运行模拟。这个也可以使用continueForTime(seconds)实现(非常重要,太关键了),执行一段时间然后暂停。这个例子的使用,可以参考 pause_continue_car.py 和 pause_continue_drone.py.

4.2 碰撞API

simGetCollisionInfo返回一个碰撞信息结构体,该结构体不仅包含是否发生碰撞的信息,还包含碰撞位置、曲面法线、穿透深度等信息。

4.3 日期时间API

AirSim 利用天空球体模拟时间。默认太阳在模拟器的位置是不会动的。可以通过setting.json文件来进行配置。当然,也可以使用API来设置太阳位置。

Python函数声明如下所示,is_enabledTrue则启动时间效果,其他参数跟setting文件用法一样。

simSetTimeOfDay(self, is_enabled, start_datetime = "", is_start_datetime_dst = False, celestial_clock_speed = 1, update_interval_secs = 60, move_sun = True)

4.4 天气API

默认天气效果是关闭的,如果想启用天气效果,首先调用函数simEnableWeather(True)

各种天气效果可以使用simSetWeatherParameter方法来实现,输入是一个参数结构体WeatherParameter,例如:

client.simSetWeatherParameter(airsim.WeatherParameter.Rain, 0.25);

第二个参数取值范围是0-1,第一个参数有如下属性。

class WeatherParameter:
    Rain = 0
    Roadwetness = 1
    Snow = 2
    RoadSnow = 3
    MapleLeaf = 4
    RoadLeaf = 5
    Dust = 6
    Fog = 7

注意 Roadwetness, RoadSnowRoadLeaf 效果需要自己添加 材质 。

具体细节可以参考对应示例文件。

4.5 多车

AirSim 支持通过API控制多辆车,未来单独开一个博客介绍。

4.6 坐标系统

All AirSim API uses NED coordinate system, i.e., +X is North, +Y is East and +Z is Down. All units are in SI system. Please note that this is different from coordinate system used internally by Unreal Engine. In Unreal Engine, +Z is up instead of down and length unit is in centimeters instead of meters. AirSim APIs takes care of the appropriate conversions. The starting point of the vehicle is always coordinates (0, 0, 0) in NED system. Thus when converting from Unreal coordinates to NED, we first subtract the starting offset and then scale by 100 for cm to m conversion. The vehicle is spawned in Unreal environment where the Player Start component is placed. There is a setting called OriginGeopoint in settings.json which assigns geographic longitude, longitude and altitude to the Player Start component.

5 不同类型车辆特定的API

5.1 用于Car的API

主要参考前面的hello_car.py相关代码。

5.2 用于多旋翼的API

多旋翼可以通过指定角度、速度矢量、目标位置或这些组合来控制,使用move*这个函数可以实现这个目标。当进行位置控制时候,需要使用一些路径跟踪算法。默认情况下,Airsim使用carrot跟踪算法,这通常被称为“高级控制”,因为您只需要指定高级目标,其余的由固件负责。当前最低等级控制方法是使用moveByAngleThrottleAsyncAPI

(1) getMultirotorState

调用一次则返回一次无人机状态。该状态包括碰撞(collision)、估计运动学(estimated kinematics)(即通过融合传感器计算的运动学)和时间戳(timestamp)(从纪元开始的纳秒)。运动学包含6个量:位置(position)、方向(orientation)、线速度和角速度、线加速度和角加速度。请注意,simple_slight目前不支持状态估计器,这意味着对于simple_flight飞行,估计和地面真实运动学值是相同的。然而,除了角加速度,估计的运动学可用于PX4。所有的量都在NED坐标系中,除了角速度和加速度在body框架中之外,在世界坐标系中使用国际单位制。

(2) Async methods, duration and max_wait_seconds

许多API有 durationmax_wait_seconds等参数,它们的后缀是Async,比如takeoffAsync。这些方法将在AirSim中启动任务后立即返回,以便您的客户端代码在执行该任务时可以执行其他操作(就是多线程,防止堵塞)。如果想等这些任务完成,可以使用 waitOnLastTask ,下面是个例子。

//C++
client.takeoffAsync()->waitOnLastTask();
# Python
client.takeoffAsync().join()

如果启动另一个命令,它会自动取消上一个任务并启动新命令,这样就可以不断更新新的路径命令,执行任务。

所有的Async 方法会返回concurrent.futures.Future(C++中是std::future)。注意这些future类当前不允许检查当前状态或取消任务,他们仅允许等待任务完成。Airsim不提供cancelLastTask的API。

(3) drivetrain

有两个方法可以用于多旋翼飞行drivetrain可以设置在airsim.DrivetrainType.ForwardOnlyairsim.DrivetrainType.MaxDegreeOfFreedom。当指定ForwardOnly,无人机的正前方永远指向轨迹的方向,所以如果想让无人机左转,它将首选旋转前向点向左,这个模式很有用,可以让相机指向操作方向。这多少有点像开车旅行,你总是能看到前面的风景。MaxDegreeOfFreedom 意味着不关心指向方向,左转时候就像个螃蟹一样横行(hahaha)。

(4) yaw_mode

yaw_mode 是结构体 YawMode,其包含两个变量 yaw_or_rateis_rate. 如果is_rate为真,yaw_or_rate则被认为是角速度,单位为度/秒,意味着车辆在移动时以该角速度绕其轴连续旋转。当yaw_mode.is_rate == true,参数drivetrain将不会被设置为 ForwardOnly 因为保持前方指向前方,但也要不断旋转,这是自相矛盾的。但是,如果在ForwardOnly模式出现yaw_mode.is_rate = false,可以做一些有趣的事。比如,你可以让无人机做圆圈,并将yaw_or_rate设置为90,这样相机总是指向中心(“超级酷的自拍模式”)。在MaxDegreeofFreedom,可以通过设置 yaw_mode.is_rate = trueyaw_mode.yaw_or_rate = 20这将导致无人机在旋转时进入其路径,这可能允许进行360度扫描。

在大多数情况下,不需要改变偏航,可以通过设置偏航率0,airsim.YawMode.Zero() (C++: YawMode::Zero()).

(5) lookahead and adaptive_lookahead

当要求车辆跟随路径时,AirSim使用“carrot following”算法。该算法通过向前看路径并调整其速度矢量来运行。可以通过制定lookaheadadaptive_lookahead来实现。在大多数情况下,希望算法自动确定值,可以设置lookahead = -1adaptive_lookahead = 0实现。

6 总结

总体来说,Airsim实现了大量无人机的基本功能,这样可以专心解决图像算法问题,Airsim看似是个大工程,实际上工程很少(UE4C++开发似乎真的很难),我总结几个目前使用中存在的问题吧,期待未来的版本。

  1. 需要高端的笔记本,我的GTX1060都卡,或得图像数据卡的更厉害(画质实在是太高了)
  2. 获得目标的一些属性有限,目前仅可知获得目标的位置和姿态,尺度点云啥的,比较难获得。
  3. 利用网络传数据,能力太有限了,至少得千兆网卡吧,速度不行那就只能降低FPS了。

其他的等慢慢发现吧。

你可能感兴趣的:(Airsim,airsim)