AirSim无人机自主飞行仿真:从环境搭建到避障算法验证
简介:这份资源面向无人机算法、无人驾驶与智能机器方向的开发者与学习者,聚焦在AirSim仿真平台下实现复杂环境中的无人机自主飞行。内容围绕仿真环境搭建、六自由度建模与PID/滑模控制、多传感器感知与避障、GPS与视觉SLAM定位导航、A*与RRT路径规划以及多机协同等关键环节展开,帮助读者把控制算法与真实飞行场景衔接起来。压缩包共2个文件,包含1个Python脚本与1个Markdown说明文档,整体约7KB,脚本对应核心算法实现,文档用于梳理项目结构与使用说明,便于快速上手与二次修改。目前已有342人学习下载。通过阅读源码与说明,读者可掌握AirSim环境配置、算法调试与性能评估的完整思路,并借助实时仿真反馈迭代优化飞行策略,为无人机自主飞行研究与工程实践打下基础。
1. 复杂环境下的无人机自主飞行:AirSim 能帮你验证到什么程度
城市楼宇间的 GPS 拒止区域、山区突发的风切变、室内走廊的狭窄通道——这些场景下让无人机自己飞,最怕的不是算法写不出来,而是根本没有一个安全、可重复、成本可控的地方去验证它。AirSim 仿真平台就是干这个的:它把 Unreal Engine 的视觉渲染能力和物理引擎绑在一起,给无人机提供一个带真实动力学响应和传感器噪声的虚拟试飞场。你可以在里面反复测试路径规划、避障、视觉感知模块,炸机了按一下重置就行,不用捡碎片。这个方案适合做无人机算法验证的研究生、做飞控原型的工程师,以及需要快速迭代自主飞行逻辑但苦于外场测试成本太高的团队。它不能替代真机,但能让你在真机之前把大部分逻辑错误筛掉。
2. AirSim 环境搭建与无人机模型配置:从零到能起飞
2.1 为什么选 AirSim 而不是纯数学仿真
做无人机自主飞行验证,常见做法有三类:纯 MATLAB/Simulink 数学仿真、Gazebo 加 PX4 软件在环、以及 AirSim 这类带高保真视觉渲染的平台。纯数学仿真跑得快,但传感器数据是理想化的,视觉算法根本没法测;Gazebo 生态成熟,但场景视觉真实度有限,做视觉感知时域随机化很吃力。AirSim 的核心优势在于它把 Unreal Engine 的渲染管线接进来了,摄像头能输出带光照、纹理、遮挡的真实感图像,IMU 和气压计也带噪声模型。如果你的自主飞行方案里包含视觉避障、目标识别或者视觉里程计,AirSim 是目前开源方案里比较省事的选择。
选版本时注意:AirSim 有 Unreal 插件版和 Unity 版,社区里做无人机自主飞行绝大多数用 Unreal 插件版。Windows 下建议用 Visual Studio 2019 或 2022 编译,Linux 下用 clang 更顺。显卡方面,NVIDIA GTX 1060 以上能跑动基础场景,想开高质量渲染建议 RTX 2070 起步。
2.2 搭建最小可飞环境
下面以 Windows 加 Unreal Engine 4.27 为例,走一遍从拉取代码到无人机起飞的流程。Linux 下命令基本一致,把路径换成对应目录即可。
# 1. 克隆 AirSim 仓库(包含 Unreal 插件和 Python 客户端) git clone https://github.com/microsoft/AirSim.git cd AirSim # 2. 运行构建脚本,生成插件和 Python 包 # Windows 下用 build.cmd,Linux 下用 ./build.sh ./build.sh # 3. 下载一个预编译的 Unreal 场景(Blocks 环境最轻量) # 从 AirSim 官方 releases 页面获取 Blocks.zip,解压到任意目录 # 注意:不要放在中文路径下,Unreal 对中文路径支持有问题构建完成后,把 AirSim 插件复制到你的 Unreal 项目 Plugins 目录,或者直接用官方 Blocks 场景。启动 Blocks.exe 后按以下步骤配置无人机:
// 在 Documents/AirSim/settings.json 中写入 { "SettingsVersion": 1.2, "SimMode": "Multirotor", "Vehicles": { "Drone1": { "VehicleType": "SimpleFlight", "AutoCreate": true, "X": 0, "Y": 0, "Z": 0, "Sensors": { "Imu": { "SensorType": 2, "Enabled": true, "AngularRandomWalk": 0.0001, "GyroBiasStabilityTau": 500, "GyroBiasStability": 4.0 }, "Barometer": { "SensorType": 1, "Enabled": true, "PressureFactorSigma": 0.001 }, "CameraFront": { "SensorType": 0, "Enabled": true, "CaptureSettings": [ { "ImageType": 0, "Width": 640, "Height": 480, "FOV_Degrees": 90 } ] } } } } }这段配置做了三件事:把仿真模式设为多旋翼,创建一架默认无人机,并挂载 IMU、气压计和前视摄像头。IMU 的AngularRandomWalk和GyroBiasStability参数决定了陀螺仪噪声水平,做视觉惯性里程计验证时这两个值直接影响算法鲁棒性。气压计的PressureFactorSigma控制高度噪声,室内飞行可以调小到 0.0005 减少高度抖动。摄像头ImageType: 0表示场景彩色图,改成 1 是深度图,2 是分割图,做视觉感知时按需切换。
2.3 用 Python 客户端让无人机动起来
AirSim 提供 Python 客户端,方便你写自主飞行脚本。先安装依赖:
pip install airsim msgpack-rpc-python然后跑一个最简单的起飞加悬停脚本:
import airsim import time # 连接仿真器,默认端口 41451 client = airsim.MultirotorClient() client.confirmConnection() # 解锁电机并起飞到 5 米高度 client.enableApiControl(True) client.armDisarm(True) client.takeoffAsync().join() # 飞到指定位置:x=10, y=0, z=-5(NED 坐标系,z 负值表示向上) client.moveToPositionAsync(10, 0, -5, 5).join() # 悬停 3 秒,读取 IMU 数据 time.sleep(3) imu_data = client.getImuData() print("角速度:", imu_data.angular_velocity) print("线加速度:", imu_data.linear_acceleration) # 降落并锁定 client.landAsync().join() client.armDisarm(False) client.enableApiControl(False)这里的关键是 NED 坐标系:x 朝北,y 朝东,z 朝下。所以z=-5表示离地 5 米。moveToPositionAsync的第四个参数是速度,单位米每秒。getImuData()返回的角速度单位是弧度每秒,线加速度单位是米每二次方秒,和真实 IMU 一致。如果你发现无人机起飞后漂移严重,先检查 settings.json 里 IMU 噪声参数是不是设得太大,再确认仿真器帧率是否稳定——帧率波动会导致物理积分误差累积。
3. 复杂环境场景构建:把楼宇、树木和风加进去
3.1 用 Unreal 编辑器搭建自定义场景
Blocks 场景太干净,验证不了避障算法。你需要一个带障碍物的环境。常见做法是在 Unreal Engine 里新建一个 Level,从自带资源库拖入建筑、树木、围栏等静态网格体,然后调整 AirSim 插件的生成点。具体步骤:
- 打开 Unreal 项目,在 Content Browser 里搜索
SM_Building或SM_Tree这类资源,拖到视口中。 - 用缩放工具调整障碍物尺寸,确保无人机能穿过间隙。一般走廊宽度留 2 到 3 米比较接近真实楼宇。
- 在 World Settings 里确认 AirSim 插件已启用,GameMode 设为
AirSimGameMode。 - 把无人机起始点放在障碍物前方 5 米处,避免一起飞就撞。
如果不想自己搭,AirSim 官方还提供 Neighborhood 和 City 场景,建筑密度更高,适合测试城市环境下的路径规划。下载后直接运行对应 exe,settings.json 不用改,客户端连接方式一样。
3.2 风场扰动配置与参数影响
复杂环境不只是静态障碍物,风是让无人机自主飞行翻车的主要原因之一。AirSim 支持在 settings.json 里配置风场:
{ "Vehicles": { "Drone1": { "VehicleType": "SimpleFlight", "AutoCreate": true, "Wind": { "Enabled": true, "MeanWindSpeed": 5.0, "WindDirection": [1.0, 0.0, 0.0], "GustAmplitude": 2.0, "GustFrequency": 0.5 } } } }MeanWindSpeed是平均风速,单位米每秒,5.0 大概相当于 3 级风,无人机能明显感到侧向推力。WindDirection是风向向量,[1,0,0]表示从北向南吹。GustAmplitude是阵风幅度,GustFrequency是阵风频率,这两个参数模拟突风。做抗风扰算法验证时,我一般从 3 米每秒开始,逐步加到 8 米每秒,观察位置控制器能不能把误差压回 1 米以内。如果一加风就发散,先检查姿态控制环的积分限幅是不是太松。
3.3 传感器噪声与采样率设置
真实 IMU 采样率通常 100 到 200 Hz,AirSim 默认按仿真帧率输出,可能远高于这个值。如果你的算法假设固定采样周期,需要做降采样或者直接在 settings.json 里限制:
"Imu": { "SensorType": 2, "Enabled": true, "AngularRandomWalk": 0.0002, "GyroBiasStabilityTau": 300, "GyroBiasStability": 5.0, "LinearAccelerationRandomWalk": 0.001, "AccelBiasStabilityTau": 400, "AccelBiasStability": 8.0 }AngularRandomWalk和LinearAccelerationRandomWalk分别控制陀螺仪和加速度计的随机游走噪声,值越大噪声越明显。做视觉惯性里程计时,这两个参数要和真实 IMU 数据手册对标,否则仿真里跑通的算法上真机可能直接崩。摄像头帧率在CaptureSettings里用Framerate字段控制,默认 30 帧,做高速视觉伺服可以调到 60。
4. 自主飞行算法接入:路径规划与避障的仿真验证
4.1 用 Python 写一个 A* 路径规划器并接入 AirSim
AirSim 本身不提供路径规划算法,但你可以用 Python 在外部算好路径点,再通过moveToPositionAsync逐个飞过去。下面是一个简化的 A* 实现,假设你已经从仿真器拿到了障碍物栅格地图:
import airsim import numpy as np import heapq def astar(grid, start, goal): # grid: 0 可通行, 1 障碍物 rows, cols = grid.shape open_set = [(0, start)] came_from = {} g_score = {start: 0} f_score = {start: heuristic(start, goal)} while open_set: _, current = heapq.heappop(open_set) if current == goal: return reconstruct(came_from, current) for dx, dy in [(-1,0),(1,0),(0,-1),(0,1),(-1,-1),(-1,1),(1,-1),(1,1)]: neighbor = (current[0]+dx, current[1]+dy) if not (0 <= neighbor[0] < rows and 0 <= neighbor[1] < cols): continue if grid[neighbor] == 1: continue tentative_g = g_score[current] + np.hypot(dx, dy) if tentative_g < g_score.get(neighbor, float('inf')): came_from[neighbor] = current g_score[neighbor] = tentative_g f_score[neighbor] = tentative_g + heuristic(neighbor, goal) heapq.heappush(open_set, (f_score[neighbor], neighbor)) return [] def heuristic(a, b): return np.hypot(a[0]-b[0], a[1]-b[1]) def reconstruct(came_from, current): path = [current] while current in came_from: current = came_from[current] path.append(current) return path[::-1] # 接入 AirSim client = airsim.MultirotorClient() client.confirmConnection() client.enableApiControl(True) client.armDisarm(True) client.takeoffAsync().join() # 假设栅格分辨率 1 米,起点 (0,0),终点 (20,15) grid = np.zeros((30, 30)) grid[10:15, 5:20] = 1 # 模拟一堵墙 path = astar(grid, (0,0), (20,15)) for wp in path: x, y = wp[0], wp[1] client.moveToPositionAsync(x, y, -5, 3).join()这段代码的核心是 A* 搜索,grid里 1 表示障碍物,0 表示可通行。heuristic用欧几里得距离,比曼哈顿距离更适合无人机斜向飞行。接入 AirSim 时,把栅格坐标直接映射到 NED 坐标系的 x 和 y,z 固定为 -5 米。每飞到一个路径点用.join()阻塞等待,确保飞控跟得上。如果发现无人机在拐角处切内弯撞墙,把路径点加密或者加一个膨胀层,让障碍物栅格向外扩张 0.5 米。
4.2 避障验证:用深度图做反应式避障
路径规划是全局的,避障需要局部反应。AirSim 的深度图可以直接拿来用:
import airsim import numpy as np import cv2 client = airsim.MultirotorClient() client.confirmConnection() client.enableApiControl(True) client.armDisarm(True) client.takeoffAsync().join() # 请求深度图 responses = client.simGetImages([ airsim.ImageRequest("CameraFront", airsim.ImageType.DepthPlanar, True) ]) depth = airsim.list_to_2d_float_array( responses[0].image_data_float, responses[0].width, responses[0].height ) # 取图像下半部分,计算最近障碍物距离 h, w = depth.shape roi = depth[h//2:, :] min_dist = np.min(roi) print("最近障碍物距离: %.2f 米" % min_dist) # 简单反应式避障:如果太近就后退 if min_dist < 2.0: client.moveByVelocityAsync(-1, 0, 0, 1).join() else: client.moveByVelocityAsync(2, 0, 0, 1).join()ImageType.DepthPlanar返回的是平面深度图,每个像素值就是该点到相机的距离,单位米。取下半部分是因为无人机前飞时,下方障碍物更危险。min_dist小于 2 米就触发后退,这是一个最简反应式避障。实际用的时候,可以把深度图分成左中右三列,分别判断,实现绕行而不是单纯后退。注意深度图有噪声,单帧判断容易误触发,建议做 3 帧滑动平均。
4.3 仿真结果怎么评估
跑完仿真不能只看“没撞”,要量化。我一般记录三个指标:位置跟踪误差、避障最小距离、任务完成时间。位置误差用client.getMultirotorState().kinematics_estimated.position和期望路径点做差,取均方根。避障最小距离从深度图里逐帧取最小值,看有没有低于安全阈值。任务完成时间从起飞到降落的仿真时钟差。这三个指标在 AirSim 里都能用 Python API 拿到,跑 10 次取平均,比单次结果可靠得多。
5. 避坑与排查:AirSim 仿真中常见的 5 个翻车点
5.1 无人机起飞后原地打转或漂移
现象:解锁后无人机不升空,或者升空后水平方向持续漂移,位置控制器压不住。
原因:最常见的是 settings.json 里 IMU 噪声参数设得过大,姿态解算发散;其次是仿真器帧率低于 20 帧,物理积分误差累积;还有一种情况是SimpleFlight控制器的位置环增益不适合当前风场配置。
解决:先把AngularRandomWalk和LinearAccelerationRandomWalk减半,看是否改善。如果无效,在 Unreal 编辑器里按~打开控制台,输入stat fps查看帧率,低于 20 就降低渲染质量或换更简单的场景。风场开启时,把MeanWindSpeed暂时设为 0,确认无风状态下能稳定悬停,再逐步加风。
5.2 Python 客户端连接超时
现象:运行脚本时报msgpackrpc.exceptions.RPCError: Timeout,或者ConnectionRefusedError。
原因:仿真器没启动,或者端口被占用,或者防火墙拦截了 41451 端口。Linux 下还可能是 AirSim 插件没编译进 Unreal 项目。
解决:先确认 Blocks.exe 或你的 Unreal 项目已经运行,并且窗口标题栏显示 AirSim 已加载。然后在命令行跑netstat -ano | findstr 41451(Windows)或lsof -i :41451(Linux),看端口是否监听。如果端口没监听,检查 settings.json 里ApiServerPort是不是被改了。防火墙问题临时关闭防火墙测试,确认后加例外规则。
5.3 深度图全零或全是无穷大
现象:simGetImages返回的深度图所有像素值为 0 或inf,避障逻辑完全失效。
原因:摄像头方向不对,或者ImageType设错了。DepthPlanar和DepthPerspective返回的数据格式不同,前者是平面深度,后者是透视深度,用错会导致数值异常。另外,如果场景里没有障碍物,深度图自然全是无穷大。
解决:先确认ImageRequest里的摄像头名称和 settings.json 里一致,大小写敏感。然后检查ImageType,避障一般用DepthPlanar。如果场景太空,放几个立方体进去再测。深度图数值可以用np.isfinite()过滤无穷大,再取最小值。
5.4 路径规划能跑通但无人机飞不到终点
现象:A* 算出的路径在栅格地图上看着没问题,但无人机飞到一半就停住或者绕圈。
原因:栅格地图和 AirSim 实际场景的坐标系没对齐,或者路径点之间的间距太大,位置控制器在拐角处超调。还有一种情况是路径点 z 值设成了正值,NED 坐标系下正值表示向下,无人机往地下飞了。
解决:在仿真器里用client.simPlotPoints把路径点画出来,和实际场景对比。路径点间距建议不超过 2 米,拐角处加密到 0.5 米。z 值统一用负值,起飞高度 5 米就写 -5。如果位置控制器超调,把moveToPositionAsync的速度参数从 5 降到 2,牺牲时间换稳定。
5.5 仿真跑久了越来越卡
现象:刚开始跑 60 帧,跑十几分钟后掉到 10 帧以下,无人机响应变慢。
原因:Unreal 编辑器在 Play 模式下会持续累积未释放的资源,尤其是频繁调用simGetImages时,图像数据没及时回收。另外,Python 客户端如果每帧都请求高分辨率图像,内存会涨得很快。
解决:降低图像请求频率,避障不需要每帧都拿深度图,隔 3 帧取一次足够。在 Python 脚本里显式删除不再使用的图像数据,用del responses释放引用。如果还卡,把仿真器重启,AirSim 目前没有热重载机制,长时间跑建议分段测试。
6. 从仿真到真机的迁移技巧:参数对标与域随机化
仿真里跑通的算法上真机翻车,十有八九是传感器噪声特性和动力学响应不一致。我一般做两件事:参数对标和域随机化。
参数对标是把真机 IMU 数据手册里的噪声密度、零偏稳定性填进 AirSim 的 settings.json。比如真机陀螺仪零偏稳定性是 10 度每小时,换算成弧度每秒大约是 0.00005,就把GyroBiasStability设成对应量级。加速度计同理。这一步做完,仿真里的姿态解算表现会和真机接近很多。
域随机化是在仿真里随机扰动参数,让算法见过足够多的变化。下面这段代码在每次重置仿真时随机调整风场和 IMU 噪声:
import airsim import random client = airsim.MultirotorClient() client.confirmConnection() for episode in range(20): # 随机风速 0 到 8 米每秒 wind_speed = random.uniform(0, 8) # 随机风向 wind_dir = [random.uniform(-1, 1) for _ in range(3)] # 通过 AirSim API 动态设置风场(需要 AirSim 版本支持) client.simSetWind(airsim.Vector3r( wind_speed * wind_dir[0], wind_speed * wind_dir[1], wind_speed * wind_dir[2] )) # 随机 IMU 噪声(通过重新加载 settings 或使用 API) # 这里简化处理,实际需要修改 settings.json 后重启仿真 client.reset() client.enableApiControl(True) client.armDisarm(True) client.takeoffAsync().join() # 跑你的自主飞行算法 # ... client.landAsync().join() client.armDisarm(False) client.enableApiControl(False)simSetWind是 AirSim 提供的动态风场接口,不用改 settings.json 就能在运行时调整。每轮 episode 随机风速和风向,让路径规划算法适应不同风扰。IMU 噪声的动态调整目前 AirSim 没有直接 API,常见做法是准备多份 settings.json,每轮替换后重启仿真器。20 轮下来,如果算法在 80% 的 episode 里都能完成任务,上真机的把握就大很多。
还有一个血泪经验:仿真里摄像头没有运动模糊,真机高速飞行时图像糊成一片,视觉算法直接失效。如果做视觉避障,在 AirSim 里把摄像头曝光时间调长,或者后期加运动模糊滤镜,提前暴露这个问题。另外,真机电池电压下降会导致动力衰减,仿真里动力恒定,做长航时任务时要在代码里加电压补偿逻辑,或者至少在仿真里模拟动力衰减曲线。
我自己的习惯是,每次改完算法先在 AirSim 里跑 50 次随机场景,记录失败案例,分类是感知问题还是控制问题。感知问题回去调摄像头参数和噪声模型,控制问题回去调增益和限幅。这个流程走顺了,真机测试基本就是验证仿真结论,而不是从头排查。希望帮到你。
本文还有配套的精品资源,点击获取