简介:本资源是一套基于AirSim开源仿真平台的无人机编队飞行算法实践包,面向无人机控制、智能体协同与无人驾驶方向的高校学生、算法工程师及科研初学者,聚焦多机编队控制这一典型智能机器系统问题。压缩包共32个文件,含13个核心Python源码(如single_integrator__one_leader_3D_relativeStateDeviation.py、multi_agent_drone.py等,覆盖单/双积分器模型、领导-跟随策略、相对状态偏差跟踪等主流编队算法)、9个实验过程MP4视频(直观展示仿真运行效果)、4个XML配置文件(用于AirSim环境与无人机参数定义)、1个PPTX实验记录文档及README.md说明文件,整体大小216.04MB。已有285人学习下载,内容持续更新,提供从AirSim环境搭建、算法原理实现、多场景仿真实验到结果可视化分析的完整闭环,特别适合理解编队控制底层逻辑、调试多智能体协同代码及开展课程设计或课题验证。
1. AirSim 无人机编队仿真包:不是玩具,是能跑通 leader-follower + relative state deviation 的实战组合
你手头这个.zip文件,不是“AirSim 入门教程”那种点开 Unreal 编辑器、拖个 Quadrotor、敲两行client.takeoff()就完事的演示包。它是一套在 Windows 或 Ubuntu 上真能跑起来、带完整闭环控制逻辑、支持 2D/3D 多构型(单领航+双跟随、5 机协同跟踪、速度自适应编队)、且所有算法都基于相对状态偏差(relative state deviation)建模的实战级仿真资源。核心价值在于:它把论文里抽象的“一致性协议”和“分布式观测器”翻译成了可调试、可断点、可改参数的 Python 脚本——比如single_integrator__one_leader_3D_relativeStateDeviation.py这个文件名,已经告诉你它用一阶积分器模型、单领航者架构、在 X/Y/Z 三维空间中,以各机相对于领航者的位姿偏差为控制目标。适合两类人:一是刚学完《多智能体系统》但卡在“公式推不出来代码”的研究生;二是想快速验证自己设计的编队策略是否能在带动力学延迟、传感器噪声、通信拓扑变化的真实仿真环境中稳住队形的工程师。它不教你怎么装 AirSim,但默认你已通过官方文档完成airsim-0.4.0或0.5.0版本部署,并能独立启动Blocks或CityEnviron场景。如果你还在为“为什么我的 PID 在 Gazebo 里调得飞起,一换 AirSim 就发散”而熬夜,这个包里的speed_control.py和double_integrator(4.2)__one_leader_3D_relativeStateDeviation.py就是你该拆的第一份源码。
2. 环境准备与核心脚本链路:从 AirSim 启动到 multi_agent_drone.py 的三层调用关系
2.1 AirSim 版本与 Python SDK 的隐性绑定关系
这个包的全部脚本,强依赖 AirSim v0.4.0 或 v0.5.0 的 Python 客户端 API 行为。注意:不是最新版airsim-nightly,也不是v0.3.x。原因在于multi_agent_drone.py中对client.simGetObjectPose()的调用方式、以及double_integrator*.py里对client.moveByVelocityZAsync()的加速度约束处理,都与 v0.4.0 的DroneController类内部状态机严格对齐。若你本地装的是pip install airsim(当前为 v1.0+),必须回退:
pip uninstall airsim -y pip install https://github.com/microsoft/AirSim/releases/download/v0.5.0/airsim-0.5.0-py3-none-any.whl提示:
.whl文件需手动下载后本地安装,因为 v0.5.0 的 PyPI 包已下架。下载地址见 AirSim GitHub Releases 页面,关键词搜 “v0.5.0”。不要用pip install git+https://...直接装源码,其setup.py未适配此包的client初始化逻辑。
安装后验证:
import airsim client = airsim.MultirotorClient() client.confirmConnection() print(client.getApiVersion()) # 应输出 '0.5.0' 或 '0.4.0'若报AttributeError: 'MultirotorClient' object has no attribute 'getApiVersion',说明版本错——v0.3.x 没这方法,v1.0+ 改了返回结构。
2.2 multi_agent_drone.py:整个编队系统的入口与代理注册中心
打开multi_agent_drone.py,你会看到它并非直接写控制律,而是承担三重角色:无人机实例化、通信拓扑构建、主循环分发器。关键段落如下:
# multi_agent_drone.py 第 42–58 行(节选) drones = { "Drone0": {"role": "leader", "init_pos": [0, 0, -3]}, "Drone1": {"role": "follower", "init_pos": [2, 0, -3]}, "Drone2": {"role": "follower", "init_pos": [0, 2, -3]} } clients = {} for name in drones: clients[name] = airsim.MultirotorClient() clients[name].confirmConnection() clients[name].enableApiControl(True, name) clients[name].armDisarm(True, name) clients[name].takeoffAsync(timeout_sec=5).join() # 设置初始位置(注意:AirSim Z轴向下为正,-3 表示离地3米) clients[name].moveToPositionAsync( *drones[name]["init_pos"], velocity=5.0, drivetrain=airsim.DrivetrainType.MaxDegreeOfFreedom, yaw_mode=airsim.YawMode(is_rate=False, yaw_or_rate=0) ).join()这段代码干了四件事:
- 命名隔离:每个
MultirotorClient()实例绑定唯一无人机名(Drone0/Drone1),避免client.simGetVehiclePose()返回错乱; - 权限激活:
enableApiControl(True, name)是关键——漏掉这句,后续所有moveBy*Async()均静默失败; - 物理初始化:
armDisarm(True, name)必须在takeoffAsync()前执行,否则起飞指令被忽略; - 坐标系对齐:
moveToPositionAsync()的z=-3对应离地高度 3 米(AirSim 使用左手系,Z 向下为正),这是single_integrator__one_leader_3D_relativeStateDeviation.py中所有相对偏差计算的基准。
参数说明:
velocity=5.0是移动最大速率(单位 m/s),非加速度;yaw_mode=airsim.YawMode(is_rate=False, yaw_or_rate=0)表示以绝对朝向 0°(正北)对齐,而非角速度控制。若你的编队要求动态转向,此处需改为is_rate=True并传入角速度值。
2.3 控制律脚本的加载机制:如何让 Drone1 执行 follower 算法?
multi_agent_drone.py本身不包含控制律,它通过动态导入机制加载具体策略。核心逻辑在第 120 行附近:
# 根据 drone 名称匹配策略文件 strategy_map = { "leader": "single_integrator__one_leader_1D.py", # 简化版,仅 X 方向 "follower": "single_integrator_tracking_MTF_relativeStateDeviation_3D.py" # 主力 follower 策略 } # 加载 follower 策略时,传入当前 drone 名、领航者名、其他 follower 名列表 strategy_module = importlib.import_module( strategy_map[drones[name]["role"]].replace(".py", "") ) strategy_module.run_control_loop( client=clients[name], leader_client=clients["Drone0"], all_drones=list(clients.keys()), dt=0.05 # 控制周期 50ms,必须与 AirSim 的 physics step 一致 )这里的关键是dt=0.05:它必须等于 AirSim 配置文件settings.json中"PhysicsEngine": "PhysX"下的"TimeDilation"和"MaxFPS"计算出的实际步长。默认settings.json若未修改,MaxFPS为 120,则dt ≈ 1/120 ≈ 0.0083s,但本包所有.py脚本均按0.05s设计(即 20Hz 控制频率)。因此你必须在settings.json中强制设为:
{ "SettingsVersion": 1.2, "SimMode": "Multirotor", "Vehicles": { "Drone0": { "VehicleType": "SimpleFlight", "X": 0, "Y": 0, "Z": -3 }, "Drone1": { "VehicleType": "SimpleFlight", "X": 2, "Y": 0, "Z": -3 } }, "PhysicsEngine": { "TimeDilation": 1.0, "MaxFPS": 20 } }否则moveByVelocityZAsync()的指令会因时间步不匹配而累积误差,导致编队在 30 秒后明显发散。
3. 四类核心算法脚本解析:从 single_integrator 到 double_integrator 的建模跃迁
3.1 single_integrator__one_leader_3D_relativeStateDeviation.py:最简相对状态闭环
这是理解整个包的“锚点脚本”。它假设无人机为一阶积分器模型:
$$ \dot{p}_i = v_i $$
其中 $p_i$ 是 Drone i 的三维位置,$v_i$ 是其控制输入(期望速度)。领航者 Drone0 的轨迹由外部给定(如正弦曲线),follower 的目标是使相对位置偏差 $e_i = p_i - p_0$ 收敛至零。控制律为:
$$ v_i = v_0 - k_p e_i $$
k_p即代码中的kp = 0.8(第 37 行)。脚本主体逻辑:
# single_integrator__one_leader_3D_relativeStateDeviation.py 第 65–82 行 while True: # 获取当前 follower 位置 pos_f = client.simGetVehiclePose().position # 获取领航者位置(通过 leader_client) pos_l = leader_client.simGetVehiclePose().position # 计算相对偏差 e_x = pos_f.x_val - pos_l.x_val e_y = pos_f.y_val - pos_l.y_val e_z = pos_f.z_val - pos_l.z_val # 一阶反馈控制 v_x = v_leader_x - kp * e_x v_y = v_leader_y - kp * e_y v_z = v_leader_z - kp * e_z # 发送速度指令(注意:AirSim 的 moveByVelocityZAsync 中 Z 是垂直方向) client.moveByVelocityZAsync(v_x, v_y, v_z, duration=dt).join() time.sleep(dt)关键细节:
moveByVelocityZAsync(v_x, v_y, v_z, duration=dt)的v_z参数对应垂直方向速度,单位 m/s;duration=dt是本次指令持续时间,必须与主循环time.sleep(dt)严格相等,否则指令重叠或空档。若dt=0.05,则每 50ms 更新一次速度指令。
此脚本的局限性在于:它把无人机当质点,忽略姿态动力学。实际飞行中,当v_x突变时,无人机会因机体旋转滞后而产生横向漂移——这就是为何包里同时提供double_integrator*.py。
3.2 double_integrator(4.2)__one_leader_3D_relativeStateDeviation.py:引入加速度状态的二阶模型
二阶模型将状态扩展为位置+速度:
$$ \dot{p}_i = v_i,\quad \dot{v}_i = a_i $$
控制输入变为加速度 $a_i$,更贴合真实飞控的setTrajectory()或moveOnPathAsync()接口。该脚本的控制律为:
$$ a_i = a_0 - k_p e_i - k_v (v_i - v_0) $$
其中k_p=1.2,k_v=2.5(第 41–42 行)。它比一阶模型多一个微分项,能抑制速度突变引起的振荡。但代价是:必须精确估计领航者的加速度 $a_0$。脚本中采用数值微分:
# double_integrator*.py 第 95–102 行 # 用前两帧位置差估算领航者速度 pos_l_prev = pos_l_current pos_l_current = leader_client.simGetVehiclePose().position v_l_est = [ (pos_l_current.x_val - pos_l_prev.x_val) / dt, (pos_l_current.y_val - pos_l_prev.y_val) / dt, (pos_l_current.z_val - pos_l_prev.z_val) / dt ] # 再用速度差估算加速度 a_l_est = [ (v_l_est[0] - v_l_prev[0]) / dt, (v_l_est[1] - v_l_prev[1]) / dt, (v_l_est[2] - v_l_prev[2]) / dt ]注意:此估算在
dt=0.05下噪声极大。若你观察到 follower 高频抖动,应将dt提高至0.1并在settings.json中同步设MaxFPS=10,或改用低通滤波器平滑v_l_est。血泪经验:某次实验因未滤波,导致 Drone1 在 Z 方向持续 ±0.3m 振荡,最后发现是v_l_est[2]在dt=0.05下受 AirSim 位置更新抖动影响,标准差达 0.8m/s²。
3.3 single_integrator_tracking_MTP_relativeStateDeviation.py:MTP(Moving Target Pursuit)跟踪模式
MTP 不是固定队形,而是 follower 始终朝向 leader 当前位置运动,形成“追击线”。控制律为:
$$ v_i = k_p \cdot \frac{p_0 - p_i}{|p_0 - p_i|} \cdot v_{\text{max}} $$
即沿视线方向以最大速度逼近。脚本中v_max=3.0(第 33 行),k_p=1.0(第 34 行)。它适用于搜索场景,但存在“追逐震荡”问题:当 leader 急转弯时,follower 易因视线方向突变而过冲。解决方案在square.py中体现——它预生成正方形路径点序列,follower 跟踪离自己最近的路径点,而非实时 leader 位置,本质是“路径跟踪”而非“目标跟踪”。
3.4 multi_agent_speed.py:全网少见的分布式速度自适应编队
此脚本解决一个现实痛点:当 leader 加速时,follower 如何在不碰撞的前提下同步提速?它不依赖中心化速度指令,而是每个 follower 通过广播自己的v_i给邻居,再取平均作为参考速度。核心逻辑:
# multi_agent_speed.py 第 78–95 行 # 每个 drone 广播自身速度(简化为标量) own_speed = math.sqrt(v_x**2 + v_y**2 + v_z**2) broadcast_msg = {"drone_id": name, "speed": own_speed, "timestamp": time.time()} # 发送到共享内存或 UDP(本包用文件模拟) with open(f"speed_shared_{name}.txt", "w") as f: f.write(json.dumps(broadcast_msg)) # 读取其他 drone 的速度(最多读 3 个最近的) speeds = [] for other in all_drones: if other != name and os.path.exists(f"speed_shared_{other}.txt"): try: with open(f"speed_shared_{other}.txt") as f: data = json.load(f) if time.time() - data["timestamp"] < 1.0: # 1秒内有效 speeds.append(data["speed"]) except: pass if speeds: ref_speed = sum(speeds) / len(speeds) * 1.1 # 乘 1.1 防止保守 # 将 ref_speed 投影到当前期望速度方向上 v_norm = math.sqrt(v_x**2 + v_y**2 + v_z**2) if v_norm > 0.1: v_x = (v_x / v_norm) * ref_speed v_y = (v_y / v_norm) * ref_speed v_z = (v_z / v_norm) * ref_speed这是本包最具工程价值的创新点:用文件模拟轻量级通信,规避了 ROS2 DDS 的复杂配置,又比纯集中式控制更鲁棒。但注意
1.0秒时效性阈值——若网络延迟高,需调大此值,否则 follower 会因读不到新数据而降速。
4. 避坑指南:五个让新手前三天无法起飞的真实错误
4.1 现象:client.takeoffAsync().join()卡死,控制台无报错,无人机不动
原因:AirSim 未启用 API 控制权限,或settings.json中Drone0的VehicleType写错。常见错误是VehicleType: "PX4"(需额外固件),但本包所有脚本均基于SimpleFlight模式。
解决:检查settings.json中Vehicles下每个 drone 的VehicleType是否为"SimpleFlight";确认client.enableApiControl(True, "Drone0")在takeoffAsync()前已执行;若用 Linux,还需在~/.bashrc中添加export DISPLAY=:0(防止 Unreal 渲染线程阻塞)。
4.2 现象:follower 位置偏差e_x持续增大,最终撞墙
原因:single_integrator*.py中的kp过小(<0.5)或dt过大(>0.1),导致积分累积。更隐蔽的原因是client.simGetVehiclePose()返回的位置是世界坐标,但moveByVelocityZAsync()的速度指令是机体坐标系——当 follower 朝向与 world 坐标系夹角过大时,v_x会被旋转,实际运动方向偏移。
解决:先用square.py测试基础移动(它用moveToPositionAsync(),直接世界坐标),确认无偏移后再切回速度控制;若必须用速度控制,需在发送前将v_world转换为机体坐标,调用client.simGetVehiclePose().orientation获取四元数并做旋转。
4.3 现象:运行double_integrator*.py时,follower 在 Z 方向剧烈上下跳动
原因:数值微分放大了 AirSim 位置更新的固有抖动(尤其在MaxFPS=20时,位置噪声标准差约 0.02m)。a_l_est计算中(v_l_est[2] - v_l_prev[2]) / dt将 0.02m/s 的速度噪声放大为 0.4m/s² 的加速度噪声。
解决:在v_l_est计算后加一阶 IIR 滤波:
alpha = 0.3 v_l_filtered[2] = alpha * v_l_est[2] + (1-alpha) * v_l_filtered_prev[2]alpha=0.3可平衡响应速度与噪声抑制。
4.4 现象:multi_agent_drone.py启动后,只有 Drone0 起飞,Drone1–Drone2 报ApiControl is not enabled
原因:enableApiControl(True, name)的name参数必须与settings.json中定义的 vehicle name 完全一致,包括大小写。常见错误是settings.json写"drone1",而代码传"Drone1"。
解决:打印clients[name].listVehicles()返回的名称列表,与settings.json逐字比对;或统一用小写重命名所有 drone。
4.5 现象:speed_control.py中 follower 速度始终为 0
原因:multi_agent_speed.py依赖文件共享,但 Windows 下多个 Python 进程对同一文件的读写存在锁竞争,open(..., "w")可能失败。Linux 下则因文件系统缓存导致读取旧内容。
解决:Windows 用户改用multiprocessing.Manager().dict()替代文件;Linux 用户在open()后加os.fsync()强制刷盘。本包已提供speed_control.py的跨平台兼容版,优先使用它而非自行修改multi_agent_speed.py。
5. 仿真效果验证与进阶技巧:用 PPTX 记录进程 + 实验视频反向校准参数
5.1 用仿真进程记录.pptx建立可复现的实验档案
压缩包里的仿真进程记录.pptx不是装饰品,而是标准化实验记录模板。它强制你填写五项核心数据:
- AirSim 配置快照:截图
settings.json中PhysicsEngine和Vehicles节; - 控制参数表:列出
kp,kv,dt,v_max等所有可调参数及取值依据(如“kp=1.2来自李雅普诺夫稳定性分析”); - 轨迹对比图:用
client.simGetVehiclePose()采样 1000 帧,导出 CSV,用 Matplotlib 绘制 leader/follower 三维轨迹,标注最大偏差距离; - 速度曲线:在同一图中绘制
v_x,v_y,v_z随时间变化,识别超调与稳态误差; - 失败归因页:若编队崩溃,注明时间戳、当时 leader 动作(如“t=42.3s 急转弯”)、follower 状态(如“Drone1 Z 速度突增至 -8m/s”)。
这份 PPTX 的价值在于:当你三个月后想复现某个成功实验,不用翻几十个
.py文件找参数,直接打开 PPTX 第 2 页抄;当导师问“为什么选dt=0.05”,你能指向第 1 页的settings.json截图和第 4 页的速度曲线——证明这是经实测收敛的边界值。
5.2 用实验视频反向校准传感器模型
AirSim 的视觉传感器(如front_center)输出的图像,其畸变、曝光、帧率与真实无人机差异巨大。但实验视频文件夹里的 MP4,是开发者用相同脚本在Blocks场景中录制的真实仿真画面。你可以用 OpenCV 提取视频帧,与client.simGetImages()的实时输出对比,从而校准两个关键参数:
- 畸变系数:用
cv2.calibrateCamera()对视频中棋盘格角点拟合,得到cameraMatrix和distCoeffs; - 时间戳偏移:视频帧率标称 30fps,但实际可能因渲染负载波动。用
cv2.CAP_PROP_POS_MSEC读取每帧毫秒级时间戳,与client.simGetGroundTruthKinematics()的timestamp字段对齐,计算平均偏移量 Δt。
校准后,你在main.py中注入真实传感器模型:
# main.py 第 200 行后插入 def get_realistic_image(client): raw_img = client.simGetImages([airsim.ImageRequest("0", airsim.ImageType.Scene, False, False)])[0] img = np.frombuffer(raw_img.image_data_uint8, dtype=np.uint8) img = img.reshape(raw_img.height, raw_img.width, 3) # 应用校准参数 h, w = img.shape[:2] new_camera_matrix, roi = cv2.getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, (w,h), 1, (w,h)) img_undistorted = cv2.undistort(img, cameraMatrix, distCoeffs, None, new_camera_matrix) return img_undistorted此技巧让仿真结果具备论文级可信度。某次投稿被审稿人质疑“AirSim 图像过于理想”,我们直接提交了校准后的视频帧与真实无人机采集帧的 SSIM 对比表(SSIM>0.92),顺利过关。
5.3 从README.md挖掘未明说的隐藏技巧
README.md第 3 行写着:“支持 2D/3D 编队,推荐从single_integrator__one_leader_2D_XZ.py入手”。这句话藏着一个关键提示:2D_XZ指的是在 X-Z 平面(水平+垂直)做二维控制,Y 方向锁定。这意味着你可以用它快速测试高度协同——比如让 Drone0 在 X 方向匀速前进,Drone1 在 Z 方向保持恒定高度差。实现只需注释掉 Y 方向控制:
# single_integrator__one_leader_2D_XZ.py 第 75 行 # v_y = v_leader_y - kp * e_y # 注释此行,Y 速度恒为 0 v_y = 0.0 # 强制 Y 方向静止这种“降维测试法”是我每次验证新算法的必走流程:先在 1D(single_integrator__one_leader_1D.py)看符号是否正确,再升到 2D_XZ 看耦合效应,最后上 3D。从那以后我每次改kp,都强制走一遍这三级验证,哪怕只调 0.1——因为double_integrator(5.14)_tracking_MTP_relativeStateDeviation.py里一个kp错位,会导致 5 机编队在 120 秒后集体失锁,而日志里只显示moveByVelocityZAsync timeout,根本看不出是参数问题。希望帮到你。
本文还有配套的精品资源,点击获取