AirSim公开了API,可以调用API与仿真器进行交互(核心就是通过RPC向仿真器发消息,进行模拟),可以通过API获取图像、状态和控制车辆等。
API有Python版本和C++版本的,Python更容易上手,方便后续的C++学习。
pip install msgpack-rpc-python
如果没有编译源码,使用pip install airsim
安装airsim,如果从官网下载的代码,可以进入PythonClient
文件夹下,输入下述代码进行安装,Python的许多API在开发中,最好使用pip安装,获得最新的包。
python setup.py install
官方已经封装好了一些仿真环境,直接去releases 下载即可,然后打开仿真器,进入PythonClient\car\
并执行,就可以看到车动起来了。
python hello_car.py
下面是如何用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控制
下面是个例子来控制无人机,代码在文件夹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)
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。
可以使用pause(True)
实现暂停。可能会遇到这样的情况,特别是在使用强化学习时,会在指定的时间内运行模拟,然后自动暂停。当模拟暂停时,可以执行一些昂贵的计算,发送一个新命令,然后在指定的时间内再次运行模拟。这个也可以使用continueForTime(seconds)
实现(非常重要,太关键了),执行一段时间然后暂停。这个例子的使用,可以参考 pause_continue_car.py 和 pause_continue_drone.py.
simGetCollisionInfo
返回一个碰撞信息结构体,该结构体不仅包含是否发生碰撞的信息,还包含碰撞位置、曲面法线、穿透深度等信息。
AirSim 利用天空球体模拟时间。默认太阳在模拟器的位置是不会动的。可以通过setting.json文件来进行配置。当然,也可以使用API来设置太阳位置。
Python函数声明如下所示,is_enabled
为True
则启动时间效果,其他参数跟setting文件用法一样。
simSetTimeOfDay(self, is_enabled, start_datetime = "", is_start_datetime_dst = False, celestial_clock_speed = 1, update_interval_secs = 60, move_sun = True)
默认天气效果是关闭的,如果想启用天气效果,首先调用函数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
, RoadSnow
和 RoadLeaf
效果需要自己添加 材质 。
具体细节可以参考对应示例文件。
AirSim 支持通过API控制多辆车,未来单独开一个博客介绍。
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.
主要参考前面的hello_car.py相关代码。
多旋翼可以通过指定角度、速度矢量、目标位置或这些组合来控制,使用move*
这个函数可以实现这个目标。当进行位置控制时候,需要使用一些路径跟踪算法。默认情况下,Airsim使用carrot跟踪算法,这通常被称为“高级控制”,因为您只需要指定高级目标,其余的由固件负责。当前最低等级控制方法是使用moveByAngleThrottleAsync
API
调用一次则返回一次无人机状态。该状态包括碰撞(collision)、估计运动学(estimated kinematics)(即通过融合传感器计算的运动学)和时间戳(timestamp)(从纪元开始的纳秒)。运动学包含6个量:位置(position)、方向(orientation)、线速度和角速度、线加速度和角加速度。请注意,simple_slight目前不支持状态估计器,这意味着对于simple_flight飞行,估计和地面真实运动学值是相同的。然而,除了角加速度,估计的运动学可用于PX4。所有的量都在NED坐标系中,除了角速度和加速度在body框架中之外,在世界坐标系中使用国际单位制。
许多API有 duration
或 max_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。
有两个方法可以用于多旋翼飞行drivetrain
可以设置在airsim.DrivetrainType.ForwardOnly
或airsim.DrivetrainType.MaxDegreeOfFreedom
。当指定ForwardOnly,无人机的正前方永远指向轨迹的方向,所以如果想让无人机左转,它将首选旋转前向点向左,这个模式很有用,可以让相机指向操作方向。这多少有点像开车旅行,你总是能看到前面的风景。MaxDegreeOfFreedom 意味着不关心指向方向,左转时候就像个螃蟹一样横行(hahaha)。
yaw_mode
是结构体 YawMode
,其包含两个变量 yaw_or_rate
和is_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 = true
,yaw_mode.yaw_or_rate = 20
这将导致无人机在旋转时进入其路径,这可能允许进行360度扫描。
在大多数情况下,不需要改变偏航,可以通过设置偏航率0,airsim.YawMode.Zero()
(C++: YawMode::Zero()
).
当要求车辆跟随路径时,AirSim使用“carrot following”算法。该算法通过向前看路径并调整其速度矢量来运行。可以通过制定lookahead
和adaptive_lookahead
来实现。在大多数情况下,希望算法自动确定值,可以设置lookahead = -1
和adaptive_lookahead = 0
实现。
总体来说,Airsim实现了大量无人机的基本功能,这样可以专心解决图像算法问题,Airsim看似是个大工程,实际上工程很少(UE4C++开发似乎真的很难),我总结几个目前使用中存在的问题吧,期待未来的版本。
其他的等慢慢发现吧。