简介:基于UE4与AirSim的无人机强化学习自主导航系统源码,是一份面向计算机科学、电子信息工程、智能科学与技术、控制工程等专业方向的毕业设计完整项目。它针对虚拟仿真环境下无人机自主路径规划与动态目标追踪难题,提供可执行的算法实现、完整代码工程及配套学术论文文档,相关成果在毕业答辩中获得98分的优异成绩。资源包共775个文件,总大小约149.52MB,其中Python与C++源码负责算法主体与仿真交互,C++头文件与工程配置保证模块可编译,PNG图像和Markdown文档记录实验可视化与过程笔记,另有环境配置脚本、论文LaTeX模板和参考文献,便于复现与二次开发。目前已有60人次学习浏览,适合具备一定编程基础的专业师生进行课程实践、毕业设计课题开发或科研预研;所有程序模块均已通过完整性测试,在此框架上还可继续扩展传感器融合、改进决策模型或提升轨迹规划精度,具备良好的学术参考与技术示范价值。
1. 在UE4与AirSim里跑无人机强化学习:先解决什么,再解决什么
做无人机自主导航,直接把代码烧进实机试错,是最贵的一种做法。一架四轴炸机的损失往往不是一块桨叶或一个电调,而是机架、相机、飞控一起交代掉,然后花两周等配件。基于UE4与AirSim的无人机强化学习自主导航系统,核心思路就是在碰真机之前,先在虚幻引擎里搭一个能承载动力学、视觉感知、碰撞和天气的仿真沙盘:UE4负责渲染出接近实机的画面和物理场景,AirSim负责把多旋翼飞行动力学、深度相机、激光雷达、碰撞检测等封装成可编程接口,策略网络通过Python API不断尝试、获取奖励、更新参数,最终学会从起点飞向目标点并避开障碍。
这套系统解决的是视觉避障、端到端导航、未知场景泛化这类问题,适合正在做无人机自主导航、深度强化学习落地验证,以及sim-to-real方向的工程师和学生。它不负责电机选型、电调调参,但它能让你用最小的硬件代价,把一套强化学习导航策略从“能飞”打磨到“敢飞”。
2. 搭一套能跑强化学习的UE4+AirSim环境:版本匹配、编译与最小验证
2.1 为什么选UE4+AirSim,而不是Gazebo或其他模拟器
只要在无人机导航圈子里待过,就绕不开Gazebo和AirSim的选择。Gazebo+ROS生态成熟,算法库里全是SLAM、路径规划、自主导航的现成轮子,但它的图像渲染真实度明显偏弱:材质、光照、反射都带有一股“仿真味”。如果你的策略最终要靠视觉感知来导航,训练时的画面和实机差距太大,迁移时就会翻车。ROS小车、AGV路径规划那套在Gazebo里做很顺手,但换成无人机,还要额外处理飞控层和动力学的耦合。
AirSim的优势正好补在视觉这一环。它跑在UE4上,光照、阴影、材质是游戏引擎级别的,深度相机、双目相机、激光雷达、IMU这些传感器都有现成实现,而且通过RPC对外提供统一的Python/C++接口。凤凰无人机模拟器偏向航模遥控练习,没有可编程的强化学习API;Spacedrone这类项目本质上是社区对AirSim的改装和扩展,说明AirSim的接口开放度已经成了事实标准。对比下来,做视觉导航的强化学习,UE4+AirSim是第一选择。
| 对比项 | UE4+AirSim | Gazebo+ROS | 凤凰模拟器 |
|---|---|---|---|
| 视觉渲染真实度 | 高,游戏引擎级 | 中低,偏实验室风 | 高但不是给算法用 |
| 强化学习接口 | Python/C++ RPC | 需要自己搭gazebo环境 | 无 |
| 动力学模型 | SimpleFlight/PX4固件可选 | 可用Pixhawk等 | 面向航模 |
| 典型用途 | 视觉导航、避障、RL训练 | SLAM、轮式导航、多机 | 手动飞行练习 |
2.2 版本匹配:UE4.26配AirSim 1.6,别急着追最新
AirSim是开源的UE4插件,需要自己编译。第一步先把版本锁死,否则后面全是玄学问题。我踩过的组合里,UE4.26 + AirSim 1.6最稳,社区资料也最多;UE4.27配1.7也可以用,但有些RPC接口行为略微变化。UE5之后AirSim虽然逐步支持,但针对UE4写的很多示例代码、settings配置和物理表现并不完全兼容,第一版不要追新。
| UE4版本 | AirSim版本 | 我的判断 |
|---|---|---|
| 4.24 | 1.3/1.4 | 老,接口旧,不推荐 |
| 4.26 | 1.6 | 稳定,资料多,首推 |
| 4.27 | 1.7+ | 可用,注意日志接口变化 |
| 5.x | 1.8+ | 视觉好,但插件兼容问题多 |
编译步骤很固定。Windows下准备好Visual Studio 2019和CMake,克隆AirSim源码后直接跑编译脚本:
git clone https://github.com/microsoft/AirSim.git cd AirSim ./setup.sh ./build.shWindows环境则用:
.\build.cmd编译完成后,把AirSim/Unreal/Plugins/AirSim整个目录复制到你创建的UE4工程Plugins目录下,重新生成工程文件。这一步常见的翻车点是Visual Studio版本不对,AirSim要求VS2019及以上的C++工具链;另一个坑是只拷贝了插件但没重新编译UE4工程,导致插件加载失败。打开工程后,如果能看到AirSim的工具栏菜单,说明插件挂载成功。
2.3 最小验证:让无人机用Python飞起来再降落
环境搭好先别急着上强化学习,先做一次最小飞行验证。这步不通,后面训练代码报错时你根本分不清是环境问题还是算法问题。在UE4编辑器里打开你的场景,点Play,然后在Python里执行:
import airsim client = airsim.MultirotorClient() client.confirmConnection() # 建立RPC连接,默认本地41419端口 client.enableApiControl(True) # 开启API控制,关闭遥控器介入 client.armDisarm(True) # 上锁,允许电机响应 client.takeoffAsync().join() # 起飞,等待完成 client.moveToPositionAsync(-5, 0, -3, 5).join() # 飞到指定坐标 state = client.getMultirotorState() pos = state.kinematics_estimated.position print(f"x={pos.x_val:.2f} y={pos.y_val:.2f} z={pos.z_val:.2f}") client.landAsync().join() client.armDisarm(False) client.enableApiControl(False)这段代码是之后所有训练环境的地基。confirmConnection()负责AirSim客户端和UE4场景之间的RPC握手;enableApiControl(True)把飞行控制权从遥控器切换到API;armDisarm(True)是给电机上电。moveToPositionAsync里的坐标要注意:AirSim用的是NED坐标系,Z轴向下,所以z=-3表示飞行高度3米。如果你发现无人机往反方向飞,先检查坐标系,这是新手最容易翻车的地方。
2.4 settings.json:训练前先调好这5个参数
AirSim的行为由工程目录下的settings.json控制。训练强化学习前,我一般会先写好一份基线配置,避免后面每个episode都被随机天气、遥控器输入干扰:
{ "SettingsVersion": 1.2, "SimMode": "Multirotor", "ClockSpeed": 3, "Vehicles": { "Drone1": { "VehicleType": "SimpleFlight", "DefaultVehicleState": "Armed", "RC": { "RemoteControlType": "NoRemoteControl" } } }, "CameraDefaults": { "CaptureSettings": [ { "ImageType": 0, "Width": 84, "Height": 84, "FOV_Degrees": 90 } ] } }SimMode必须设成Multirotor,否则Python客户端连上的是一个ComputerVision模式,没有无人机可以控制。ClockSpeed是仿真加速倍数,设成3能让训练跑快一些,但不要超过5,物理步进会不稳。RC.RemoteControlType设成NoRemoteControl是为了禁用外接遥控器和手柄的映射输入,避免训练过程中有人碰一下摇杆导致动作指令被覆盖。CameraDefaults里的84x84分辨率是给视觉策略用的——训练期间不需要高分辨率,图像越小,网络前向和仿真渲染的负担都越小。
3. 把AirSim封装成强化学习环境:观测、动作与奖励的十个关键参数
3.1 按gym.Env封装,让算法直接吃
AirSim只提供飞行控制和传感器读取API,不会替你做强化学习接口。所以要自己写一层环境封装,把“起飞、读图、执行动作、判断碰撞、给奖励”这些逻辑收敛进一个gym.Env里。这样PPO、SAC这些现成算法库可以直接make进来,不用每个算法都重写仿真交互。
import gym from gym import spaces import numpy as np import airsim class AirSimNavEnv(gym.Env): def __init__(self, target_pos=(20.0, 0.0, -3.0)): super().__init__() self.client = airsim.MultirotorClient() self.client.confirmConnection() self.target = np.array(target_pos, dtype=np.float32) self.action_space = spaces.Box(low=-1.0, high=1.0, shape=(3,), dtype=np.float32) self.observation_space = spaces.Box(low=0.0, high=1.0, shape=(1, 84, 84), dtype=np.float32) def step(self, action): self._apply_action(action) state = self.client.getMultirotorState() pos = state.kinematics_estimated.position pos = np.array([pos.x_val, pos.y_val, pos.z_val], dtype=np.float32) dist = np.linalg.norm(pos - self.target) collision = self.client.simGetCollisionInfo().has_collided done = dist < 1.0 or collision reward = self._reward(dist, collision) obs = self._get_obs() return obs, reward, done, {"dist": dist, "collision": collision} def reset(self): self.client.reset() self.client.enableApiControl(True) self.client.armDisarm(True) self.client.takeoffAsync().join() time.sleep(0.5) return self._get_obs()封装的关键在于让环境对外界只暴露一个稳定的接口。动作空间定义为3维连续向量,对应无人机在NED三轴上的速度指令;观测空间是1通道84x84图像。step里先执行动作,再读取状态、碰撞、距离,最后计算奖励。reset()每次把无人机恢复到初始位置并起飞,注意reset()之后要重新enableApiControl和armDisarm,否则下一轮很可能无人机不响应控制。
3.2 观测设计:RGB还是深度图
视觉导航的观测来源主要有三种:RGB图、深度图、激光雷达点云。点云信息密度高但训练慢,而且实机上用激光雷达做自主导航的成本不小;RGB图包含语义和纹理信息,但对光照变化敏感,训练难度更高。深度图是避障任务最稳的起点——它直接告诉你前方障碍的远近,网络不需要自己从阴影和纹理里反推深度。
读取AirSim深度图的正确方式是用simGetImages的DepthPerspective类型,注意pixels_as_float参数必须为True:
def _get_obs(self): request = airsim.ImageRequest( "front_center", airsim.ImageType.DepthPerspective, pixels_as_float=True, compress=False ) responses = self.client.simGetImages([request]) response = responses[0] depth = np.array(response.image_data_float, dtype=np.float32) depth = depth.reshape(response.height, response.width) depth = depth / 100.0 # 厘米转米 depth = np.clip(depth, 0, 20) # 限制有效距离 depth = depth / 20.0 # 归一化到0~1 return depth[np.newaxis, :, :].astype(np.float32)这里有一个常见的血泪经验:不要用response.image_data_uint8去读深度图,8位编码会丢失大量距离信息,训练出来的策略在真实深度相机前基本失灵。用image_data_float拿到的才是未经量化的深度值。单位换算也很重要,AirSim深度值单位是厘米,所以要除以100变成米,再裁剪出0~20米的有效量程。
3.3 动作空间:用速度指令,别直接碰电机和姿态控制
很多新手会把动作空间设计成四旋翼四个电机的PWM值,这完全搞错了层次。AirSim内部有飞控层,SimpleFlight自身带PID控制器。强化学习策略应该做的是“导航层”决策——决定往哪个方向飞多快,而不是替代飞控去调整电机转速。
实践中我倾向用速度指令作为动作空间,更接近实机“外环导航+内环飞控”的主流架构:
def _apply_action(self, action): max_speed = 5.0 vx = float(action[0]) * max_speed vy = float(action[1]) * max_speed vz = float(action[2]) * max_speed self.client.moveByVelocityAsync(vx, vy, vz, duration=0.2)moveByVelocityAsync让AirSim飞控自己去跟踪目标速度,策略网络只需给出三轴速度。duration=0.2是控制周期,这个值不能太短——仿真物理步进和RPC往返都有延迟,控制周期短了动作根本执行不完;也不能太长,太长机动性差,反应慢。速度上限5m/s对室内和园区场景都够用,训练初期千万不要给到10m/s以上,否则碰撞检测经常因为速度太快穿透物体。
另外,如果你后续要接PX4这类真实飞控,这个动作空间设计可以直接沿用,只需要把输出映射成PX4的期望速度消息,迁移成本很低。
3.4 奖励设计:稀疏大奖励加距离势函数
奖励设计是强化学习里最容易被低估的部分。只用“到达给100,碰撞给-100”的稀疏奖励,前期探索效率极低,无人机在原地乱转半天也碰不到一次正反馈。我一般用“稀疏奖励+距离势函数-成形”的组合:
BETA_DIST = 0.5 COLLISION_PENALTY = -10.0 REACH_REWARD = 20.0 STEP_PENALTY = -0.01 def _reward(self, dist, collision, prev_dist=None): if collision: return COLLISION_PENALTY if dist < 1.0: return REACH_REWARD shaping = BETA_DIST * (prev_dist - dist) if prev_dist is not None else 0.0 return shaping + STEP_PENALTY距离势函数的核心思想是:无人机比上一步更接近目标,就给正向奖励;偏离目标,就给负向奖励。BETA_DIST是距离成形系数,0.5左右是个稳定起点,太大会让策略变成“贪心跟踪GPS点”而忽略避障,太小则起不到引导作用。碰撞惩罚不要给太大,-10已经足够;太大策略会变得极度保守,停在原地不敢动。到达奖励20和碰撞惩罚形成明显对比,让策略倾向于优先保命再谈任务。
4. 用PPO训练看深度图飞向目标点:网络、超参与评估指标
4.1 算法选型:PPO为什么是默认起点
AirSim自主导航属于高维视觉输入+连续控制的典型问题。DQN只能处理离散动作,如果把速度空间离散化,航向和速度组合会指数增长,动作精度也不够。SAC样本效率高,但温度系数和双Q网络有额外调参压力,在仿真速度不稳定的环境里更容易发飘。PPO则是最稳的起点:它对学习率和奖励尺度不那么敏感,Clip机制限制了策略更新的步长,训练曲线相对平滑。所以第一版策略直接用PPO。
离线强化学习(IQL这类)适合已经有大量飞行日志、不想再在线交互的场景。但AirSim的采样成本很低,直接在线交互更直接,离线强化学习反而要处理数据分布和策略不匹配的问题。因果强化学习目前还偏研究阶段,等你的基础策略跑通、需要解决特定因果混杂问题时再考虑,第一版不需要上。
| 算法 | 动作空间 | 样本效率 | 调参压力 | AirSim导航稳定性 |
|---|---|---|---|---|
| DQN | 离散 | 中等 | 低 | 只适合航向决策 |
| PPO | 连续 | 中等 | 中等 | 高,首选 |
| SAC | 连续 | 高 | 较高 | 中高,需细调 |
| IQL离线RL | 连续 | 高 | 中 | 需要离线数据 |
4.2 PPO网络结构:三层CNN加Actor-Critic
以84x84深度图作为输入,一个经典的视觉策略网络是三层层CNN编码器,再接两个头:Actor输出动作均值,Critic输出状态价值。编码器提取障碍轮廓和空间结构,Actor和Critic共享这些特征,可以节省参数:
import torch import torch.nn as nn class VisualPolicy(nn.Module): def __init__(self, obs_ch=1, n_actions=3): super().__init__() self.encoder = nn.Sequential( nn.Conv2d(obs_ch, 32, kernel_size=8, stride=4), nn.ReLU(), nn.Conv2d(32, 64, kernel_size=4, stride=2), nn.ReLU(), nn.Conv2d(64, 64, kernel_size=3, stride=1), nn.ReLU(), nn.Flatten(), ) self.fc = nn.Sequential( nn.Linear(64 * 7 * 7, 256), nn.ReLU(), ) self.actor = nn.Linear(256, n_actions) self.critic = nn.Linear(256, 1) def forward(self, obs): x = self.encoder(obs) x = self.fc(x) mean = self.actor(x) value = self.critic(x) return mean, value输入84x84的深度图,经过步长4的第一次卷积后变成20x20,第二次变成9x9,第三次变成7x7,所以全连接层的输入维度是6477。如果改了输入分辨率,这个数值要重新算,否则网络直接报维度错误。注意Actor输出的是均值,实际采样时还需要一个独立的log_std对角高斯分布,这部分用PyTorch自带的Normal实现即可。
4.3 训练循环与checkpoint:每1000步存一次后悔药
环境封装好了、网络结构定了,最省事的做法是直接上Stable-Baselines3的PPO实现,它把GAE、Clip、学习率调度都封装好了。如果自己写PPO,GAE部分的计算逻辑可以对照David Silver的强化学习讲义理解,但没必要从零造轮子。SB3的配置如下:
from stable_baselines3 import PPO model = PPO( "CnnPolicy", env, learning_rate=3e-4, n_steps=2048, batch_size=64, gae_lambda=0.95, gamma=0.99, clip_range=0.2, ent_coef=0.01, verbose=1, ) model.learn(total_timesteps=2_000_000) model.save("airsim_nav_depth_ppo")n_steps=2048是每轮策略更新的交互步数,这个值太小时GAE估计不稳定;clip_range=0.2是PPO更新幅度上限,保持默认即可。ent_coef=0.01给一点熵正则,防止策略过早收敛成一个确定性动作,导致探索不足。ent_coef如果设成0,训练后期容易陷入局部最优,比如无人机永远只往一个方向飞。
训练过程中每1000轮保存一次checkpoint是必须的。强化学习训练曲线波动很大,可能第800轮出了一个很好的策略,第1200轮就崩了。没有checkpoint,崩了只能从头再来,这就是没有后悔药。保存时我习惯连环境配置和超参一起存下来:
model.save(f"checkpoints/ppo_step_{step}")4.4 评估指标:到达率、碰撞率、平均距离
训练过程中如果只盯着TensorBoard里的平均奖励,你会被误导。奖励是距离成形项、碰撞惩罚、到达奖励的加权混合,策略优化了奖励,不一定代表任务成功率高。所以每训练几万步,就要冻结策略做一次离线评估:
def evaluate(model, env, episodes=20): successes = 0 collisions = 0 avg_dist = 0.0 for _ in range(episodes): obs = env.reset() done = False info = {} while not done: action, _ = model.predict(obs, deterministic=True) obs, _, done, info = env.step(action) if not info["collision"] and info["dist"] < 1.0: successes += 1 if info["collision"]: collisions += 1 avg_dist += info["dist"] return successes / episodes, collisions / episodes, avg_dist / episodes评估时deterministic=True,不要用随机采样,否则结果方差太大。三个指标我一般定个门槛:到达率80%以上、碰撞率5%以下才算合格。到达率太低说明策略没学会导航;碰撞率太高说明避障没过关。平均距离是辅助指标,用来观察策略是不是总是半路停下——如果平均距离长期停在某个障碍点附近但不碰撞,说明策略学会了“保命”但没学会“完成任务”。
5. AirSim自主导航训练避坑:光照发黑、RPC超时、穿墙与设备映射
5.1 UE4构建光照后场景发黑
现象:在UE4编辑器里点击Build Lighting构建完光照后,整个AirSim场景变成纯黑或暗到看不见障碍物,深度图也受影响。
原因:绝大多数情况下不是AirSim的问题,而是UE4的光照设置。场景里没有有效的Directional Light和SkyLight,或者光源被设成Stationary且Lightmass烘焙没有跑完,材质就会显示成黑色。另外,PostProcessVolume的自动曝光参数如果有问题,构建后也会让画面看起来死黑。
解决:在场景里添加一个Directional Light作为主光源、一个SkyLight作为环境光,并把光源设成Movable,避免依赖静态烘焙。然后重新Build Lighting,等Lightmass进度彻底跑完。如果只是画面暗但不是全黑,调整PostProcessVolume的Auto Exposure,把Min/Max EV值放宽到-2到14之间。我这里就吃过亏:一度以为是AirSim的渲染坏了,最后发现只是场景里少了一个SkyLight。
5.2 confirmConnection() 一直打印RPC超时
现象:Python客户端执行client.confirmConnection()后,控制台不断输出连接超时,或者卡住不动。
原因:最常见的是UE4编辑器里的场景根本没点Play。AirSim的RPC服务是在运行时启动的,编辑器停留在待机界面时没有服务可连。其次,settings.json里SimMode设成了ComputerVision,此时AirSim只提供图像采集,不提供多旋翼控制。还有一个可能:默认RPC端口41419被占用,或者你在同一台机器上开了多个场景。
解决:先确认UE4场景已经点击Play运行,再检查settings.json的SimMode为Multirotor。端口排查用:
netstat -ano | findstr 41419如果端口被占,可以在Python客户端指定其他端口:airsim.MultirotorClient(ip="127.0.0.1", port=41419),并在settings.json里同步修改LocalHostIp和ApiPort。
5.3 外接设备映射异常:遥控器或手柄干扰API控制
现象:连了遥控器或手柄之后,无人机不听Python指令,或者起飞后自己偏航,动作指令被覆盖。这和热词里“ue4外接设备映射”是同一类问题的不同表现。
原因:AirSim的RC模块默认会监听外接遥控器输入,当遥控器摇杆有信号时,它会覆盖API控制指令。UE4自身也可能把手柄输入映射给场景里其他Actor,造成意外干扰。
解决:训练时在settings.json里把RC.RemoteControlType设成NoRemoteControl,彻底关闭RC输入。如果必须用遥控器做实验,至少保证摇杆回中,并且在enableApiControl(True)之后再执行armDisarm(True),确保API控制权已经拿到手。UE4侧的手柄映射不用在训练时管,训练环境本身就应该是完全干净、不受外部输入干扰的。
5.4 无人机“穿墙而过”,碰撞检测失效
现象:无人机碰到了建筑或障碍物,但simGetCollisionInfo().has_collided仍然返回False,或者视觉上已经穿进墙体,物理上却没有阻拦。
原因:两类情况。第一,UE4场景里静态网格体的碰撞体设置过粗,用了简单的盒子碰撞,小型无人机从缝隙穿过时没有碰撞事件。第二,飞行速度太快,物理引擎的步进在两个仿真帧之间跳过了碰撞体,这在强化学习里特别常见——策略早期为了拿到达奖励会猛冲。
解决:在UE4里选中相关静态网格体,Collision Complexity改成Use Complex Collision as Simple,让碰撞精度跟上网格体本身。同时限制无人机最大速度,5m/s是个比较保守的上限。更大的控制周期也能降低穿透概率。碰撞检测失效是最难排查的问题,因为它会让策略学到“穿墙也能到达目标”的坏习惯,换到实机就会直接炸机。我的建议是训练前先手动控制无人机撞一次障碍物,确认碰撞标志位能正常置位。
5.5 训练几万步后环境卡死或reset异常
现象:训练跑到两三万步,RPC响应越来越慢,或者reset()之后无人机姿态异常、起飞后乱飞。
原因:长时间运行时物理世界积累了残差,reset()没有彻底清理上一轮的动力学状态;部分版本AirSim还有纹理流送导致内存增长的问题,图像分辨率越高越明显。
解决:每个episode结束时不要只依赖reset(),最好在reset前后主动控制仿真线程。一个可行的顺序是:先将仿真暂停,再reset,然后继续仿真:
self.client.simPause(True) self.client.reset() self.client.enableApiControl(True) self.client.armDisarm(True) self.client.simContinue(True) self.client.takeoffAsync().join()另外,ClockSpeed不要超过5,过高时物理步进不稳定,长时间运行更容易卡死。如果图像分辨率不需要太高,尽量用84x84或更低,减少纹理流送压力。
6. 从仿真到实机:域随机化与回归验证清单
仿真里训练出来的策略,直接上实机大概率翻车。原因很简单:仿真里的光照是固定的,墙面纹理是干净的,深度相机没有噪声,电机响应也过于理想。要让策略在真机上还能用,关键手段是域随机化——在训练时不断改变环境的外观和物理参数,让网络学到的不是某个固定场景的特征,而是“深度图→安全动作”的通用映射。
AirSim的Python API可以直接控制天气参数,我一般会在每个episode开始时随机化光照和天气:
import random weather = airsim.WeatherParameter client.simSetWeatherParam(weather.Rain, random.uniform(0, 0.3)) client.simSetWeatherParam(weather.Dust, random.uniform(0, 0.2)) client.simSetWeatherParam(weather.Fog, random.uniform(0, 0.15))此外,还可以对观测加噪声,模拟真实深度相机的误差。给深度图加高斯噪声,再随机丢弃一部分像素点,让网络学会忽略传感器毛刺。对动作也要加扰动,因为真实电机的响应不可能像仿真里那样精准。
这里有一个我自己的教训:第一版策略在AirSim默认场景里训练得很好,到达率95%,觉得可以上实机了。结果换到室外停车场,正午阳光直射,深度图在强光下出现大量反光和空洞,策略立刻失效,差点炸机。从那以后,我把随机光照、随机天气、深度噪声当成训练的标配,而不是可选项。每次改动奖励或网络结构,我都要固定三个验证场景:训练场景、同地图不同光照、完全没见过的地图,分别统计到达率和碰撞率,用指标说话,而不是靠“感觉差不多”。
如果你有PX4飞控,最后一步还可以做硬件在环仿真:AirSim作为传感器仿真接入真实飞控固件,策略网络输出速度指令给飞控执行。这样做能验证通信链路和控制接口,但验证不了视觉对真实光照的适应性——域随机化才是视觉迁移的关键。
这套路线做下来,从UE4环境编译、AirSim Python接口、gym封装、PPO训练到域随机化验证,整个源码体系就完整了。仿真里踩过的坑,都是为实机省下的真金白银。希望帮到你。
本文还有配套的精品资源,点击获取