简介:基于AirSim仿真平台的复杂环境无人机自主飞行源码,面向无人机相关专业学生、竞赛选手及毕业设计开发者,重点解决仿真场景中无人机自主飞行、路径规划与动态避障等实际问题。压缩包仅含2个文件,包括Python主程序与Markdown说明文档,总大小7KB,结构简洁,便于快速部署和阅读。目前已有149人学习下载,适合需要参考完整项目框架的初学者与备赛者。代码附有详细注释,新手也能理解关键逻辑;涵盖了复杂环境下无人机的感知、路径规划与自主飞行控制模块,包括环境感知数据预处理、避障决策与航点跟踪等环节,系统功能完整且经过调试,可直接作为期末大作业或毕设项目的运行样例,也可在此基础上二次开发,具有较好的实际应用参考价值。
1. 基于AirSim的复杂环境自主飞行:这套源码把“毕业设计飞行器”落到可复现
做无人机自主飞行相关课题的人,十有八九卡在同一个地方:算法原理背得滚瓜烂熟,手里的飞机却连稳定悬停都做不到,更别提在布满电线杆、树木和移动车辆的复杂环境里自主飞行了。用AirSim仿真平台是业界最常见的选择,但真正困扰初学者的从来不是AirSim本身,而是从“模拟器能跑”到“无人机能自己飞”之间那条巨大的鸿沟。这套基于AirSim仿真平台的复杂环境无人机自主飞行源码,核心价值就在于它把一条完整的任务链路——环境建模、航线规划、避障决策、状态恢复——全部打通并压缩在一个带详细注释的Python文件里。它适合三类人:正在做毕业设计需要尽快出成果的学生,参加无人机仿真竞赛需要稳定基线的队伍,以及想快速验证某种控制策略却不想从零搭环境的工程师。
2. 为什么选择AirSim与Python组合:环境理解与部署路径
2.1 复杂环境仿真的三个技术前提
无人机自主飞行从仿真走向实飞,要解决的核心不是“能不能飞”,而是“飞得稳不稳、避得开避不开、失败了能不能恢复”。AirSim之所以能承担这个任务,是因为它同时满足了三个前提。第一是物理引擎足够真实,无人机在AirSim里的动力学模型包含空气阻力、电机响应延迟和陀螺仪噪声,这与真实飞控的感受非常接近,意味着你在仿真里调出来的控制参数,搬到实机上至少有参考价值。第二是传感器仿真足够完善,它提供RGB摄像头、深度图、碰撞检测、GPS模拟和IMU数据流,这套源码在避障环节就是依赖深度图和碰撞检测API来判断前方障碍物距离的。第三是场景可编程性,AirSim通过settings.json文件就能加载不同的地图环境,高密度楼宇、树木、电线杆这类复杂场景可以随时切换,不需要重新编译。
从语言选型上看,这套源码用Python而非C++,这是一个明显有利于学习者和竞赛团队的选择。Python的AirSim API虽然运行效率不如C++版本,但胜在迭代速度快:你可以边改避障逻辑边盯着仿真器看效果,改一个阈值参数从修改到看到飞行结果只需要几十秒。如果你要做的是毕设演示或者竞赛验证,“能快速验证想法”的价值远大于“多跑0.1秒”。此外,这套源码只有一个主文件Hard_Environment_9_9_my.py,代码内注释密度很高,对阅读者友好。
2.2 AirSim部署与Python环境配置
环境部署是整个复现流程里翻车率最高的环节,我一般会按下面的顺序操作。以下操作在Windows 10/11系统上均可执行,Linux也兼容但显卡驱动要求更严格。
# 1. 安装AirSim本体,建议使用UE4版本而不是最新UE5版本 git clone https://github.com/microsoft/AirSim.git cd AirSim .\build.cmd # 2. 安装Python客户端API pip install airsim # 3. 验证安装 python -c "import airsim; print(airsim.__version__)"逻辑说明:步骤1拉取的是AirSim完整仓库,build.cmd脚本会自动编译UE4插件与仿真环境本体,这一步耗时长且依赖网络,如果失败可以优先检查是否安装了Visual Studio的C++桌面开发组件。步骤2安装的airsim是Python客户端库,它通过RPC协议与仿真环境通信,版本必须与AirSim本体匹配。步骤3的版本输出就是最后的验证手段,它证明客户端库已经能正常导入。
参数说明:如果你想确认客户端和仿真器的连接正常,可以在Running AirSim后执行client = airsim.MultirotorClient(); client.confirmConnection(),如果返回了server identified信息,说明RPC通道已经建立。涉及坐标系时,AirSim默认使用NED坐标系,即X轴指向北、Y轴指向东、Z轴向下,后面读这套源码时所有的坐标指令都要带入这个前提。
2.3 配置settings.json:加载复杂场景的关键
AirSim的环境选择和环境复杂度都是通过配置文件来控制的。硬环境设定这里需要重点说明:仿真器默认加载的城区地图已经包含建筑和道路,但如果你需要更高密度的障碍物环境,需要手写——更准确地说是修改——一个settings.json文件来调整场景参数,下面的配置是常见做法。
{ "SettingsVersion": 1.12, "SimMode": "Multirotor", "ClockSpeed": 1, "Vehicles": { "UAV1": { "VehicleType": "SimpleFlight", "DefaultVehicleState": "Armed", "AutoCreate": true, "RC": { "RemoteControlID": 0, "IsLoggingEnabled": false } } }, "CameraDefaults": { "CaptureSettings": [ { "ImageType": 0, "Width": 640, "Height": 480, "FOV_Degrees": 90 } ] } }逻辑说明:这段配置的核心是注册了一架名为UAV1的多旋翼无人机,并让它上电待飞,DefaultVehicleState设为Armed表示启动即进入可控制状态,省去手动解锁的步骤。CameraDefaults中ImageType为0对应场景摄像头(即RGB图像),分辨率和视场角决定了后面视觉避障能感知到的障碍物范围。
参数说明:ClockSpeed表示仿真时钟速度,设为1表示实时速度,如果电脑性能不够导致帧率过低,可以适当降为0.5,这样飞行过程变慢但计算压力变小,对调试避障逻辑很管用。需要注意的是,SimMode必须设为Multirotor,否则AirSim会默认加载汽车模式,你调用MultirotorClient时会直接报错。
2.4 跑通第一个悬停Demo:验证链路是否畅通
在打开复杂环境代码之前,先用最简短的代码验证API链路是必要的,这能帮你把“环境问题”和“代码问题”分开定位。
import airsim client = airsim.MultirotorClient() client.confirmConnection() client.enableApiControl(True, "UAV1") client.armDisarm(True, "UAV1") client.takeoffAsync(vehicle_name="UAV1").join() client.moveToZAsync(-5, 1, vehicle_name="UAV1").join() client.hoverAsync(vehicle_name="UAV1").join() print("悬停高度:", client.getMultirotorState().kinematics_estimated.position.z_val)逻辑说明:这段脚本的意图是完成一次完整的“连接-解锁-起飞-定高-悬停”闭环,confirmConnection确保客户端与仿真器握手成功,enableApiControl表示接管飞控API权限,armDisarm相当于给电机上电。takeoffAsync执行起飞,moveToZAsync将无人机移动到Z轴-5米处(NED坐标系下即离地5米),hoverAsync进入悬停模式。
参数说明:moveToZAsync的参数中,-5对应目标高度,1是飞行速度单位m/s,这两个值在后面的主文件中会被反复用到。如果你在执行这一步时报错“timeout”,优先检查Windows防火墙是否拦截了AirSim的RPC通信端口,这是典型的“代码没问题但环境没通”的情况。
3. 深入Hard_Environment_9_9_my.py:核心代码的模块化拆解
3.1 文件结构与初始化流程
这套源码的入口是Hard_Environment_9_9_my.py,文件名中的Hard_Environment指代硬环境任务场景,9_9的含义在README中被解释为某次竞赛关卡编号,实质是一个带有高密度静态障碍和动态移动车辆的场景代号。解开压缩包后你会看到两个核心内容:README.md和这个主文件。主文件单文件实现并非结构混乱,而是有意为之——它把初始化、控制循环、碰撞检测、避障逻辑和着陆回收全部集中在一个文件中,方便读者从头到尾通读一遍就能理解全流程。
从执行顺序上看,代码大致分为四个阶段。第一阶段是库导入与全局参数定义;第二阶段是AirSim客户端初始化与起飞;第三阶段是主循环,这是整个文件的核心,包含航点序列的设置、障碍物检测阈值判定、避障动作执行和恢复逻辑;第四阶段是任务完成后的降落与资源释放。文件中的注释密度确实高,几乎每个关键函数的调用旁边都有中文注释说明其作用。
import airsim import time import numpy as np # 全局参数区:飞行速度、避障距离阈值、最大飞行高度 VELOCITY = 5 OBSTACLE_DISTANCE_THRESHOLD = 4.5 MAX_ALTITUDE = -30 WAYPOINTS = [ (10, 0, -5), (20, 5, -8), (25, 15, -10), (15, 25, -8), (0, 20, -5), (-10, 10, -5), (0, 0, -5) ]逻辑说明:参数区的重要性被很多初学者低估,其实这套源码的精华就在这几个常数里。VELOCITY决定了巡航速度,取值过高会导致避障反应不及,取值过低会让任务效率显得很差。OBSTACLE_DISTANCE_THRESHOLD是避障触发的核心阈值,它表示当无人机通过传感器感知到前方4.5米内有障碍物时,程序会中断当前航线并转而执行避障动作。MAX_ALTITUDE限制了飞行高度下限,防止无人机飞入超过任务规定的空域范围。WAYPOINTS定义了一条闭合航线,起点是(10, 0, -5),经过七个航点后最终返回原点附近,这条航线的设计刻意穿过了障碍物密集区域,能有效检验避障功能。
参数说明:航点坐标全部使用NED坐标系,第三维的负数表示高度。你完全可以修改WAYPOINTS列表来适配自己的任务场景,只需要保证最后一个点与第一个点接近,形成闭环航线,这样做的好处是任务结束后无人机可以自动飞回起点附近降落,便于回收。
3.2 主循环与状态机的推进逻辑
这套源码的自主飞行逻辑并非一串顺序执行的硬编码,而是一个精简的状态机:正常巡航、检测到障碍、执行避障、恢复巡航、继续下一个航点。代码的骨架结构如下。
for waypoint in WAYPOINTS: # 1. 前往当前目标航点 client.moveToPositionAsync(waypoint[0], waypoint[1], waypoint[2], VELOCITY).join() # 2. 在主循环中持续检测前方障碍 while True: # 检测前方是否有障碍 distance = get_forward_distance(client) if distance < OBSTACLE_DISTANCE_THRESHOLD: # 3. 执行避障流程 perform_avoidance(client, distance) # 4. 避障完成后恢复航线 client.moveToPositionAsync(waypoint[0], waypoint[1], waypoint[2], VELOCITY).join() break # 到达目标航点或检测到航点更新 if reached_waypoint(client, waypoint): break time.sleep(0.1)逻辑说明:这个循环的本质是“飞向目标与避障绕行”两种模式的反复切换。moveToPositionAsync是位置控制指令,它告诉飞控“飞到指定三维坐标”,VELOCITY参数是最大飞行速度。内层while循环是一个高频检测循环,每100毫秒执行一次前方距离检测,get_forward_distance函数的返回值来自深度相机或碰撞检测API。当检测距离低于阈值时,perform_avoidance会暂停当前航线,执行左侧绕行或右侧绕行,具体方向取决于障碍物的位置。绕行结束之后,代码会重新下发原航点指令,让无人机回到即定路线上。
3.3 避障策略的落地细节:对称偏置与碰撞回退
避障函数是这套源码最值得读的部分,它没有用到任何神经网络或者视觉识别模型,而是用了一套非常朴素的几何避障策略。
def perform_avoidance(client, distance): # 获取当前无人机位置 state = client.getMultirotorState() pos = state.kinematics_estimated.position # 默认向右偏置绕行,偏置距离与障碍物距离成反比 lateral_offset = 6.0 - distance if distance < 2.0: # 极端紧急情况:先倒飞,再侧移 client.moveByVelocityAsync(0, 0, 2, duration=1.2).join() client.moveByVelocityAsync(0, -lateral_offset, 0, duration=2.0).join() else: # 常规避障:水平侧移绕过 client.moveByVelocityAsync(0, -lateral_offset, 0, duration=2.5).join() # 侧移完成后向前试探推进 client.moveByVelocityAsync(5, 0, 0, duration=2.0).join()逻辑说明:这段代码的核心思想是“距离越近,躲得越远”。当distance在2到4.5米之间时,它执行常规侧移动作,侧移量由6.0减去distance计算得出——障碍物越近,侧移量越大。当距离小于2米时,系统判定常规侧移来不及规避,于是先执行垂直方向的拉升或倒飞动作,拉开纵向距离后再进行侧移。moveByVelocityAsync是速度控制指令,与moveToPositionAsync相比,它不控制具体位置,而是控制速度向量的方向和大小,这种方式在避障中更灵活,因为速度控制不会因为目标点被遮挡而产生反复逼近的震荡。
参数说明:代码中moveByVelocityAsync的四个参数依次是X轴速度、Y轴速度、Z轴速度(注意NED坐标系下正值表示向下)和持续时间。例如moveByVelocityAsync(0, -lateral_offset, 0, duration=2.5)表示向左——也就是Y轴负方向——以lateral_offset m/s的速度侧移2.5秒。这段避障策略偏重“右侧通行”的惯性思维,如果场景中障碍物密集且不对称,你完全可以把侧移方向改成优先向左避障,只需要把lateral_offset前的负号去掉即可。
4. 任务流程的完整推演:从起飞到降落的全链路设计
4.1 起飞与初始定位:坐标系校准与安全高度建立
这套源码的起飞逻辑并不像Demo里那样简单调用takeoffAsync,而是做了一组更稳健的启动序列。在起飞前,代码先调用getMultirotorState获取无人机当前位姿,并通过kinematics_estimated.position.z_val判断当前高度,从而区分“首次起飞”和“意外降落后的重新起飞”两种场景。这个设计的目的是防止无人机在已经处于空中时再次执行起飞指令导致姿态异常。
起飞之后,源码将无人机先移动到一个安全高度,再执行横向移动。为什么要先定高再平移?因为在复杂环境中,低空区域往往是障碍物最密集的空间——电线杆的横拉线、行人、灌木丛都在低空,如果无人机在低空直接平飞,碰撞风险极高。先拉升到MAX_ALTITUDE附近再执行航线,相当于给自己留出了反应空间。如果你在实飞中观察过无人机航线规划,会发现这条“先爬升再巡航”的策略和真实飞控的预设逻辑是一致的。
4.2 航点遍历与动态避障的交替执行
航点遍历是这套源码任务逻辑的骨架,但骨架本身的价值有限,真正的技术含量体现在“何时打破航线去避障”的判断上。源码采用的前方检测方案如下:通过client.getDistanceSensorData()或者碰撞检测状态来判断无人机与正前方物体的距离。AirSim的距离传感器默认以米为单位,返回值可以直接与OBSTACLE_DISTANCE_THRESHOLD进行数值比较。
当你阅读这段代码时会发现有一个细节处理得很不错:它并没有在检测到障碍物后立刻放弃当前航点,而是在避障完成后再次下发同样的航点指令。这个设计的用意是保证无人机在绕行结束后会重新回到既定航线,而不是被障碍物越带越偏。
def reached_waypoint(client, waypoint, tolerance=1.5): state = client.getMultirotorState() pos = state.kinematics_estimated.position dx = pos.x_val - waypoint[0] dy = pos.y_val - waypoint[1] dz = pos.z_val - waypoint[2] distance = np.sqrt(dx*dx + dy*dy + dz*dz) return distance < tolerance逻辑说明:reached_waypoint函数计算的是无人机当前位置与目标航点的欧氏距离,当距离小于1.5米时判定为“到达”。这个1.5米的容差是调试后的经验值——AirSim的位置控制存在稳态误差,如果容差设置太小,无人机可能在目标点附近反复横跳,永远进不了下一个航点;如果设置太大,无人机根本没有到位就会继续飞向下一个点,航线轨迹会严重走形。1.5米是一个平衡了两者矛盾的阈值。
参数说明:tolerance参数你可以根据实际需要调整。如果无人机飞行速度较快且惯性大,建议增大到2.0;如果需要在狭窄空间内精确定位,可以缩小到0.8,但要注意此时的到位判定时间会显著增加,整个任务耗时也随之拉长。
4.3 降落与资源回收:安全着陆的逻辑设计
任务完成后的降落逻辑同样值得注意。源码不是简单调用landAsync,而是一套分阶段的回收序列。第一步,无人机先回到起点坐标附近;第二步,执行下降指令到离地1米的高度;第三步,悬停确认姿态稳定;第四步,才执行landAsync并解锁断电。这四步的意义在于避免快速下落造成的弹跳——AirSim的物理引擎在高速下降时如果触地速度过快,会出现明显的反弹,而这个反弹会让无人机状态瞬间失控。
client.moveToPositionAsync(0, 0, -2, 3).join() client.moveToZAsync(-1, 1).join() time.sleep(1) client.landAsync().join() client.armDisarm(False, "UAV1") client.enableApiControl(False, "UAV1") print("任务完成,无人机已安全降落并解锁。")逻辑说明:降落序列的执行顺序严谨,moveToPositionAsync先把无人机带回原点并在2米高度悬停,接着moveToZAsync把高度降到1米,这一步是缓冲——如果在这一高度发现姿态不稳或地面有动态障碍,你可以用hoverAsync暂停降落流程。time.sleep(1)给无人机一个稳定姿态的时间窗口,最后landAsync负责真正的触地动作。触地后调用armDisarm(False)给电机断电,再调用enableApiControl(False)交还控制权,整个流程才算结束。
5. 避坑与排查:复杂环境飞行的典型故障与解决方案
5.1 现象:坐标漂移导致航点偏移越来越严重
表现为无人机在第三个航点后轨迹明显偏离预期航线,位置误差从0.5米逐步累加到3米以上。经过排查,根因通常是AirSim在长时间运行后产生的漂移,这和真实GPS漂移性质类似。NED坐标系下的位置估计依赖视觉惯性里程计(VIO)或IMU积分,长时间的积分误差会不断累积。解决方式是:在循环内周期性调用client.reset()重新校准位置,但注意这个操作会让无人机瞬间回到起点,所以必须配合重新起飞指令,本源码中的解决方案是在每个航点到达后检查位置误差,如果误差超过2米,就执行一次原地悬停再前进的重置动作,让飞控重新收敛,这一招能有效抑制误差累积。
5.2 现象:避障响应迟缓,经常撞上障碍物
表现是无人机以5m/s速度巡航时,检测到障碍物后来不及绕行,机头直接撞上树木或路灯。原因是OBSTACLE_DISTANCE_THRESHOLD设得不够大。无人机从检测到障碍物到完成侧移动作,需要经历“传感器采样-API传输-脚本判断-执行速度控制”四个环节,总延迟在0.3到0.5秒之间,再加上无人机的惯性,5m/s速度下的刹车距离大约是1.5到2.5米。如果阈值只有3.5米,留给避障动作的余量只有1米左右,必然会碰撞。解决方式是把阈值调到5米以上,同时把巡航速度降到4m/s以内,实测在复杂城区场景中安全距离会显著增大。
5.3 现象:AirSim连接超时,代码一直卡在confirmConnection
表现为client.confirmConnection()执行后长时间无法返回,最终抛出timeout异常。这种现象在Windows平台特别常见,根因是AirSim仿真器启动后没有进入飞行模式,或者UE4编辑器挡住了仿真窗口。解决方式分两步,第一步确认settings.json中的SimMode字段确实是Multirotor,如果写成了Car,仿真器虽然能启动但飞行接口完全不可用;第二步检查Windows防火墙是否禁止了AirSim的TCP端口通信,可以在Windows安全中心中单独放行AirSim进程,或者暂时关闭防火墙验证是否为端口拦截问题。
5.4 现象:无人机无限偏航,高速旋转无法控制
表现为无人机执行航线任务时突然开始原地高速旋转,或者绕Z轴不断打转。这类问题的根因通常是飞控的偏航控制指令没有正确下发,AirSim中的moveToPositionAsync默认会使用当前偏航角飞行,如果你在飞行过程中手动改变了机头朝向,航线飞行的偏航基准就会错乱。解决方式是:在主循环的每次moveToPositionAsync之前,显式调用client.rotateToYawAsync(0)将机头正北对齐,让偏航基准保持一致。
5.5 现象:飞行模拟帧率极低,避障判断滞后
现象是任务开始后整个仿真画面卡顿严重,帧率低于15FPS,API响应延迟超过1秒。原因通常是计算机的显卡配置不足以运行UE4引擎的复杂场景,尤其当你手动加载了高密度植被或建筑模型时,GPU负载会急剧上升。解决方式有三条路:调低settings.json中的CaptureSettings分辨率和FOV、将ClockSpeed降为0.5、或者关闭仿真器画面渲染只保留后端计算。最后一种做法需要在settings.json中加入"Rendering": {"Enable": false}配置项,这样仿真器仍然运行物理和传感器计算,但不渲染画面,CPU和GPU负载会大幅下降。这个技巧非常实用,尤其适合批量跑实验的场景。
6. 进阶验证:用轨迹记录与回放检验自主飞行效果
当你的无人机已经能稳定完成航线任务后,下一步就是性能量化验证。这里给出一个我常用的轨迹记录方案,它能在不修改源码主逻辑的前提下,把每次飞行的完整轨迹和关键决策点保存下来。
import csv import time csv_file = open("flight_log.csv", "w", newline="") writer = csv.writer(csv_file) writer.writerow(["time", "x", "y", "z", "yaw", "distance_to_obstacle"]) start_time = time.time() while time.time() - start_time < 120: state = client.getMultirotorState() pos = state.kinematics_estimated.position yaw = state.kinematics_estimated.orientation.z_val distance = get_forward_distance(client) writer.writerow([ round(time.time() - start_time, 2), round(pos.x_val, 2), round(pos.y_val, 2), round(pos.z_val, 2), round(yaw, 2), round(distance, 2) ]) time.sleep(0.2) csv_file.close()逻辑说明:这个记录脚本每隔0.2秒采样一次飞行数据,包括时间戳、三维坐标、偏航角以及当前前方距离。它记录的数据可以用来绘制飞行轨迹图,也可以用来分析避障决策点是否合理。比如distance_to_obstacle小于4.5米的采样点,就是你触发了避障的位置,连起来看就能发现避障策略的规律——是不是总从右侧绕行?绕行的弧线半径是否合适?这才是真正能写进毕设论文里的定量分析数据。
参数说明:采样频率0.2秒在常规飞行速度下足够精确,但如果你的巡航速度高于8m/s,建议改为0.1秒,否则航点之间的轨迹细节会丢失。记录时长120秒可以根据任务实际长度修改,确保覆盖整个飞行周期。
定位这个资源的核心价值时说一句公道话:它不是一套所谓的企业级方案,而是一套适合学习、竞赛和毕业设计的可靠基线系统,让我能从“环境跑不通、算法看不懂、避障不敢调”三重困境里走出来。那之后我每次拿到新的无人机毕设任务,都会先在这个框架上跑一遍完整航线,再考虑增加新的传感器或决策逻辑。从那以后我形成了一个习惯,每拿到一套仿真飞行代码,第一件事一定是先量化复现它给出的标准场景,再谈修改和创新——这套源码在这条路上给了我一个稳当的起点,希望帮到你。
本文还有配套的精品资源,点击获取