ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

UE4+AirSim无人机强化学习自主导航:从环境搭建到PPO训练全攻略

UE4+AirSim无人机强化学习自主导航:从环境搭建到PPO训练全攻略 简介基于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. 搭一套能跑强化学习的UE4AirSim环境版本匹配、编译与最小验证2.1 为什么选UE4AirSim而不是Gazebo或其他模拟器只要在无人机导航圈子里待过就绕不开Gazebo和AirSim的选择。GazeboROS生态成熟算法库里全是SLAM、路径规划、自主导航的现成轮子但它的图像渲染真实度明显偏弱材质、光照、反射都带有一股“仿真味”。如果你的策略最终要靠视觉感知来导航训练时的画面和实机差距太大迁移时就会翻车。ROS小车、AGV路径规划那套在Gazebo里做很顺手但换成无人机还要额外处理飞控层和动力学的耦合。AirSim的优势正好补在视觉这一环。它跑在UE4上光照、阴影、材质是游戏引擎级别的深度相机、双目相机、激光雷达、IMU这些传感器都有现成实现而且通过RPC对外提供统一的Python/C接口。凤凰无人机模拟器偏向航模遥控练习没有可编程的强化学习APISpacedrone这类项目本质上是社区对AirSim的改装和扩展说明AirSim的接口开放度已经成了事实标准。对比下来做视觉导航的强化学习UE4AirSim是第一选择。对比项UE4AirSimGazeboROS凤凰模拟器视觉渲染真实度高游戏引擎级中低偏实验室风高但不是给算法用强化学习接口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.241.3/1.4老接口旧不推荐4.261.6稳定资料多首推4.271.7可用注意日志接口变化5.x1.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(fx{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)把飞行控制权从遥控器切换到APIarmDisarm(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, dtypenp.float32) self.action_space spaces.Box(low-1.0, high1.0, shape(3,), dtypenp.float32) self.observation_space spaces.Box(low0.0, high1.0, shape(1, 84, 84), dtypenp.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], dtypenp.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参数必须为Truedef _get_obs(self): request airsim.ImageRequest( front_center, airsim.ImageType.DepthPerspective, pixels_as_floatTrue, compressFalse ) responses self.client.simGetImages([request]) response responses[0] depth np.array(response.image_data_float, dtypenp.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, duration0.2)moveByVelocityAsync让AirSim飞控自己去跟踪目标速度策略网络只需给出三轴速度。duration0.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_distNone): 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_ch1, n_actions3): super().__init__() self.encoder nn.Sequential( nn.Conv2d(obs_ch, 32, kernel_size8, stride4), nn.ReLU(), nn.Conv2d(32, 64, kernel_size4, stride2), nn.ReLU(), nn.Conv2d(64, 64, kernel_size3, stride1), 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、学习率调度都封装好了。如果自己写PPOGAE部分的计算逻辑可以对照David Silver的强化学习讲义理解但没必要从零造轮子。SB3的配置如下from stable_baselines3 import PPO model PPO( CnnPolicy, env, learning_rate3e-4, n_steps2048, batch_size64, gae_lambda0.95, gamma0.99, clip_range0.2, ent_coef0.01, verbose1, ) model.learn(total_timesteps2_000_000) model.save(airsim_nav_depth_ppo)n_steps2048是每轮策略更新的交互步数这个值太小时GAE估计不稳定clip_range0.2是PPO更新幅度上限保持默认即可。ent_coef0.01给一点熵正则防止策略过早收敛成一个确定性动作导致探索不足。ent_coef如果设成0训练后期容易陷入局部最优比如无人机永远只往一个方向飞。训练过程中每1000轮保存一次checkpoint是必须的。强化学习训练曲线波动很大可能第800轮出了一个很好的策略第1200轮就崩了。没有checkpoint崩了只能从头再来这就是没有后悔药。保存时我习惯连环境配置和超参一起存下来model.save(fcheckpoints/ppo_step_{step})4.4 评估指标到达率、碰撞率、平均距离训练过程中如果只盯着TensorBoard里的平均奖励你会被误导。奖励是距离成形项、碰撞惩罚、到达奖励的加权混合策略优化了奖励不一定代表任务成功率高。所以每训练几万步就要冻结策略做一次离线评估def evaluate(model, env, episodes20): 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, deterministicTrue) 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评估时deterministicTrue不要用随机采样否则结果方差太大。三个指标我一般定个门槛到达率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(ip127.0.0.1, port41419)并在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训练到域随机化验证整个源码体系就完整了。仿真里踩过的坑都是为实机省下的真金白银。希望帮到你。本文还有配套的精品资源点击获取
返回列表