☰
Matlab三维无人机集群避障仿真:人工势场法实现与代码解析
2026/10/20 5:26:13 网站建设 项目流程

无人机群在飞行任务中面临的第一个难题,往往不是"怎么飞",而是"这么多人怎么不撞上"。如果把一架无人机看成三维空间里的一个质点,单机避障只需要考虑"我"和"障碍物之间的那条线,而集群避障还要额外处理"我和邻居之间的那条线"——双重约束叠加之后,问题复杂度会直线上升。这篇内容对应的项目正是围绕这个痛点展开的:在Matlab环境下搭建一个三维无人机集群仿真场景,验证机群在飞行过程中同时完成机间碰撞规避和静态障碍物规避的算法可行性,并附上完整可跑的代码参考。适合正在做机器人路径规划、多智能体协同方向的在校学生,以及刚接触无人机仿真、想知道"别人怎么把避障算法跑起来"的工程技术人员。

1. 项目全貌:如何理解三维集群避障这件事

1.1 集群飞行到底难在哪

单架无人机做静态避障,本质上是一个"寻路"问题:起点到目标点之间,绕过若干个已知坐标的障碍物,找一条安全路径。但换成无人机群之后,性质就变了——每架无人机不仅要躲开环境中固定的障碍物,还要躲开其他同样在运动中的无人机。

这两类避障的数学性质完全不同。静态障碍物的位置是已知的、固定的,无人机可以提前规划或者实时感知后做避让;但机间避碰是典型的动态约束,邻居在动,你也在动,双方都在对未来轨迹做预测或响应。更麻烦的是,当机群规模变大时,机间避碰的计算量呈组合式增长——两两之间都要判断距离和相对运动趋势,8架无人机就有C(8,2)=28对关系需要同时维护,20架就是190对。

所以很多初做集群仿真的同学最大的困惑就在这里:单机避障的代码明明能跑,"加上邻居之后"整个系统为什么就乱成一锅粥?这其实是没把两类约束分开建模导致的。

1.2 仿真环境里到底在验证什么

做仿真不是为了让画面好看,而是为了高效验证控制逻辑和算法参数的合理性。这个项目里验证的核心内容有三块:

一是避障决策的正确性。给定若干静态障碍物(通常用球体或圆柱体近似),机群能否从起始点散布开来,绕开所有障碍物后准确到达各自目标点,且全程不与任何障碍物表面接触。

二是机间距离约束的有效性。任意两架无人机在任意时刻的距离都必须大于设置的安全阈值——这是硬约束,不满足就是碰撞。仿真能直观看到这个最小距离的曲线变化,一旦逼近阈值就知道算法压力点在哪。

三是参数敏感度分析。避障算法的表现与一系列增益系数、距离阈值强相关,仿真环境允许你以极低成本反复修改参数,观察同一场景下机群轨迹的差异。这在真实飞行实验中几乎不可能做到,也是仿真最大的价值所在。

2. 算法选型:为什么推荐人工势场法做基础方案

2.1 主流方法横向对比

集群避障可选算法很多,从随机采样类到最优控制类都有,这里先做个快速对比:

方法实时性实现难度机间协同程度典型弱点
RRT/RRT*中中低,多机需并行规划路径不一定光滑,机间约束难直接纳入
速度障碍法(VO)高中高高,天然考虑相对速度多机密集时可行速度集合可能为空
模型预测控制(MPC)低高中,取决于目标函数计算量大,实时性吃紧
人工势场法(APF)高低中,叠加斥力即可局部极小值问题明显

对于教学、验证和快速原型来说,人工势场法是性价比最高的起步方案。它的核心思想是:目标点对无人机产生引力,障碍物和其他无人机对它产生斥力,合力方向就是无人机下一步的运动方向。每一个力都能独立计算、独立叠加,逻辑清晰,调试直观,而且天然适合分布式实现——每架无人机只需要知道自己、目标、障碍物和邻居的相对位置,不依赖全局规划器协调。

2.2 引力与斥力公式:三维扩展的物理直觉

人工势场法的引力场设计为无人机到目标点的函数,期望效果是"离得越远吸引力越强,靠近后逐渐减小"。常用形式是:

[ U_{att} = \frac{1}{2} k_{att} \cdot ||p - p_{goal}||^2 ]

对位置求负梯度,得到引力:

[ F_{att} = - k_{att} \cdot (p - p_{goal}) ]

注意这里的 p 是三维坐标向量,( (p - p_{goal}) ) 的方向就是"从目标点指向当前位置"的反方向,也就是朝目标飞。k_att是引力增益,控制吸引力强弱。

斥力场则设计为距离障碍物越近斥力越强,并引入有效作用范围。常用的改进形式是:

[ U_{rep} = \begin{cases} \frac{1}{2} k_{rep} \left( \frac{1}{\rho} - \frac{1}{\rho_0} \right)^2, & \rho \leq \rho_0 \ 0, & \rho > \rho_0 \end{cases} ]

其中 (\rho) 是无人机与障碍物表面的最近距离,(\rho_0) 是斥力作用半径。这个式子的物理意义很直白:障碍物不是"绝对禁区",只要在作用半径之外就完全不影响你;进入半径之后,"贴近程度"越接近边界值,斥力就越大,并且在距离为0时趋于无穷大,从数学上天然保证不会穿透障碍物表面。

三维场景里,把二维的"平面力"扩展到"空间力"并没有本质变化——距离计算从二维的 ((x-x_0)^2+(y-y_0)^2) 变成三维的 ((x-x_0)^2+(y-y_0)^2+(z-z_0)^2),方向向量从平面单位向量扩展到空间单位向量。核心公式不变,维度扩展只影响距离函数和梯度计算。

2.3 算法瓶颈:局部极小值问题

人工势场法最出名的坑是局部极小值。当无人机、目标点、障碍物三者恰好位于一条直线上时,引力与斥力方向正好相反,可能在某一点达到力平衡,无人机就悬停在那里打转,表现为"明明没有挡住路,却飞不动了"。

这个问题的根源在于势场法是"贪婪的局部决策"——它每一步都只根据当前局部受力来决定运动方向,从不做全局规划,因此天然会落入势场的局部盆地。

应对措施有几种工程实践:一是添加随机扰动,检测到无人机速度长时间低于阈值时,在垂直方向上叠加一个临时外力,帮助它爬出势阱;二是引入"切向逃逸力",当检测到合力方向几乎跟飞行方向相反时,在垂直于障碍物连线的方向上添加主导力;三是分层策略,先用栅格法做一次粗粒度全局规划得到中间路点,再用势场法在路点之间做细粒度跟踪。这个项目里采用的思路是第一种加第二种的组合,实际仿真效果比单纯加扰动稳定得多。

3. Matlab仿真的系统架构与关键模块设计

3.1 从单机到集群:仿真器怎么搭

Matlab做这类仿真的优势在于矩阵运算和可视化一体成型,不需要额外装仿真引擎。搭建思路分三层:

物理层:用简化的质点模型描述每架无人机的运动——位置向量、速度向量,不考虑姿态动力学,通过加速度指令直接控制速度变化。对避障算法验证来说这种模型精度足够,如果加上完整六自由度模型,注意力反而会被姿态控制细节分散。

算法层:每架无人机独立计算自己的合力向量。输入是自身位置、目标位置、障碍物位置列表、邻居位置列表以及各自的感知距离,输出是下一时刻的加速度指令。

状态层:保存每个时间步的机群状态,用于回放轨迹、统计最小距离、计算航程等后处理指标。

3.2 机间避碰模块设计

机间避碰与静态避障共用同一个势场框架,但斥力源的触发逻辑不同。对静态障碍物来说,斥力是否触发完全取决于距离;对邻居无人机来说,除了距离约束,还应该考虑相对速度——一架无人机即使距离你很近,如果你们同向同速,相对距离并不会进一步缩小,此时不应该产生强烈的避让反应。

工程上的简化做法是设置两个阈值:安全距离 d_safe 和预警距离 d_warn。实际距离大于 d_warn 时不做处理;位于 d_warn 和 d_safe 之间时采用软性斥力,强度随距离线性增加;小于 d_safe 时直接触发强斥力,甚至可以叠加一个速度阻尼项,让相对速度尽快下降。这里我加的机间斥力公式为:

[ F_{neighbor} = - k_{rep_neighbor} \cdot \frac{1}{||p_i - p_j||^2} \cdot \mathbf{\hat{n}}_{ij} ]

(\mathbf{\hat{n}}_{ij}) 是从邻居指向自己的单位向量。平方反比关系让近距离的排斥强度增长非常快,能有效预防近距离碰撞。

3.3 静态障碍物建模与感知模拟

这个项目里的静态障碍物是几个不同位置、不同半径的球体。球体是三维避障场景里最容易建模的立体形态——距离计算只要一个欧氏距离公式,斥力方向就是从球心指向无人机的位置向量,且斥力只作用在球面外部。圆柱体、长方体障碍物需要额外处理最近点投影,调试成本更高,但视觉和实际场景贴合度好。如果要做通用版本,可以给每个障碍物加一个 type 字段,分类型计算最近距离和斥力方向。

感知方面需要模拟机载传感器的探测范围。现实中无人机不可能知道几百米外有个障碍物,感知半径必须受限。代码里给每架无人机设置一个感知半径 attr_radius,只有在半径内的障碍物才参与斥力计算。这样处理既贴近实际,也带来一个好处——计算量被限制在局部范围,不会因为障碍物数量增加而出现全局计算爆炸。

3.4 目标点吸引与边界保护

目标点引力好理解,每架无人机一个目标点,整个机群形成一组"从起始分布飞往目标分布"的迁移过程。边界保护容易被忽略:如果无人机飞出了仿真空间范围,比如垂直方向超出500m,或者水平面超出矩形边界,轨迹就失去意义了。解决方式是在每个维度设置软边界,无人机接近边界时叠加一个指向空间内部的强斥力,防止机群整体漂移出仿真区域。

4. 代码实现:核心逻辑逐段拆解(附Matlab代码)

4.1 初始化和参数表

一套能跑起来的集群避障仿真,参数标定占了七成工作量。下面给出一套经过调试的默认参数,初学可以先用这套跑通,再慢慢调整观察变化。

%% 仿真参数初始化 num_drones = 6; % 无人机数量 dt = 0.05; % 仿真步长(秒) time_max = 30; % 最大仿真时长(秒) stop_dist = 0.8; % 到达目标点的判定距离(m) %% 环境尺寸 space_x = 50; space_y = 50; space_z = 30; %% 无人机参数 v_max = 3.0; % 最大速度(m/s) a_max = 1.5; % 最大加速度(m/s^2) sense_radius = 6; % 感知半径(m) safe_dist = 1.0; % 机间安全距离(m) warn_dist = 3.0; % 机间预警距离(m) %% 势场增益 k_att = 0.8; % 目标引力增益 k_rep_obs = 2.5; % 静态障碍物斥力增益 k_rep_drone = 3.0; % 机间斥力增益 rep_obs_radius = 5; % 障碍物斥力作用半径(m) %% 起始位置和目标位置(6架无人机均匀分布) start_pos = [ 5, 5, 10; 10, 8, 12; 7, 12, 8; 12, 10, 15; 8, 6, 18; 6, 14, 11]'; target_pos = [ 42, 42, 20; 44, 38, 22; 40, 44, 18; 45, 40, 24; 38, 36, 26; 36, 42, 17]'; %% 静态障碍物(球体: 圆心坐标 + 半径) obstacles = [ 20, 18, 12, 4; 30, 32, 10, 3; 25, 40, 18, 5; 35, 20, 10, 3.5; ];

说几个关键参数的标定依据。感知半径必须大于障碍物斥力作用半径,否则斥力还没触发,飞机就已经一头扎进障碍物影响区了。机间预警距离应该大于两倍机体尺寸加上一定的机动余量,示例中的 safe_dist=1.0 是模拟1米直径的小型无人机在密集编队场景下的保守值。k_att 和 k_rep 的配比是避障成败的核心,原则是斥力足够强以阻止穿透,但引力又必须足够大以保证无人机不会在空旷区域踌躇不前。

4.2 势场函数实现

核心计算函数可以分成三个子问题:目标引力、障碍物斥力、机间斥力。下面是目标引力和障碍物斥力的实现:

function F_att = compute_attraction(pos, target, k_att) % 目标引力: 方向指向目标点, 大小随距离线性减弱 diff = target - pos; dist = norm(diff); if dist < 1e-3 F_att = [0 0 0]; return; end F_att = k_att * diff / dist * dist; % 实际等价于 k_att * diff end function F_rep = compute_obs_repulsion(pos, obstacles, rep_obs_radius, k_rep_obs) F_rep = [0 0 0]; num_obs = size(obstacles, 1); for i = 1:num_obs center = obstacles(i, 1:3); radius = obstacles(i, 4); diff = pos - center; dist_to_center = norm(diff); dist_to_surface = dist_to_center - radius; % 只关心斥力半径内的障碍物 if dist_to_surface < rep_obs_radius if dist_to_surface < 0.1 dist_to_surface = 0.1; end % 势场函数对距离求导得到斥力大小 magnitude = k_rep_obs * (1/dist_to_surface - 1/rep_obs_radius) / (dist_to_surface^2); direction = diff / dist_to_center; F_rep = F_rep + magnitude * direction; end end end

这段实现里有几个细节值得解释。dist_to_surface 取的是无人机到障碍物表面的距离,而不是到球心的距离,这样不同半径的球体才有统一的斥力基准——同样距离1米,大球表面和小球表面的"威胁感"应该是相同的,因为它们都在球面外1米处。分母里的 dist_to_surface^2 来自势场函数的梯度,这个二次项让斥力在接近表面时增长极快,有效防止穿透。

4.3 机间避碰逻辑

机间避碰和障碍物避碰之间有个优先级问题。当邻居无人机和静态障碍物同时在斥力范围内时,不能简单把两个斥力相加就完事,否则可能出现"为了躲邻居而撞上障碍物"的情况。实际处理中我的做法是采用最大安全原则——先判断静态障碍物方向的斥力,如果邻居斥力会压缩安全通道,就对机间斥力做限幅处理。

function F_drone = compute_drone_repulsion(pos, drone_list, idx, safe_dist, warn_dist, k_rep_drone) F_drone = [0 0 0]; n = size(drone_list, 2); for j = 1:n if j == idx continue; end diff = pos - drone_list(:, j); dist = norm(diff); if dist < warn_dist if dist < 0.5 dist = 0.5; % 防止距离为0时除零 end % 距离越近, 斥力越强; 超过预警距离不作用 if dist < safe_dist magnitude = k_rep_drone * (1/dist^2); else magnitude = k_rep_drone * (1/dist - 1/warn_dist) / (warn_dist - safe_dist)^2; end direction = diff / norm(diff); F_drone = F_drone + magnitude * direction; end end end

4.4 主循环推进

有了三个力之后,主循环就非常简洁了:计算合力、按加速度更新速度、按速度更新位置、检查停止条件。

%% 主仿真循环 pos_history = zeros(3, num_drones, round(time_max/dt)); idx_time = 0; for t = 0:dt:time_max idx_time = idx_time + 1; pos_history(:, :, idx_time) = pos; for i = 1:num_drones F_total = compute_attraction(pos(:,i), target_pos(:,i), k_att); F_total = F_total + compute_obs_repulsion(pos(:,i), obstacles, rep_obs_radius, k_rep_obs); F_total = F_total + compute_drone_repulsion(pos(:,i), pos, i, safe_dist, warn_dist, k_rep_drone); F_total = F_total + compute_boundary_force(pos(:,i), space_x, space_y, space_z); % 加速度到速度再到位置 acc = F_total / 1.0; % 单位质量简化 acc = limit_norm(acc, a_max); vel(:, i) = vel(:, i) + acc * dt; vel(:, i) = limit_norm(vel(:, i), v_max); pos(:, i) = pos(:, i) + vel(:, i) * dt; end % 全部到达目标点则提前结束 dist_to_target = vecnorm(target_pos - pos); if all(dist_to_target < stop_dist) break; end end

这一段看似简单,但运行时会暴露一个常见问题:无人机在目标点附近来回振荡。原因是引力减弱后,如果残余速度没有及时衰减,飞机会惯性越过目标点,然后又被引力拉回来,形成振荡。解决方案有两种:一是在到达判定半径内直接设速度为零,二是加入阻尼项。我用的是后者,在更新速度时乘一个阻尼系数 0.98,实测下来收敛平滑许多。

4.5 可视化与结果分析

仿真跑完后,可视化阶段的作用是让人一眼看出问题。我习惯画三张图:三维全局轨迹图、俯视投影图、机间最小距离时间曲线。

%% 三维轨迹绘制 figure; hold on; for i = 1:num_drones traj = squeeze(pos_history(:, i, 1:idx_time)); plot3(traj(1,:), traj(2,:), traj(3,:), 'LineWidth', 1.2); plot3(start_pos(1,i), start_pos(2,i), start_pos(3,i), 'p', 'MarkerSize', 10); plot3(target_pos(1,i), target_pos(2,i), target_pos(3,i), '*', 'MarkerSize', 10); end for i = 1:size(obstacles,1) [x,y,z] = sphere(20); surfl(x*obstacles(i,4)+obstacles(i,1), y*obstacles(i,4)+obstacles(i,2), z*obstacles(i,4)+obstacles(i,3)); colormap gray; alpha(0.4); end xlabel('X (m)'); ylabel('Y (m)'); zlabel('Z (m)'); grid on; axis equal; %% 机间最小距离曲线 min_dist_history = zeros(1, idx_time); for k = 1:idx_time dists = pdist(squeeze(pos_history(:, :, k))'); min_dist_history(k) = min(dists); end figure; plot((0:idx_time-1)*dt, min_dist_history); xlabel('时间 (s)'); ylabel('最小机间距离 (m)'); yline(safe_dist, 'r--', '安全距离');

需要注意的是 pdist 函数接受的是行向量矩阵,这里 transpose 的位置不能错,否则算出来的是转置矩阵的距离,结果毫无意义。这个细节我调试时花了十几分钟才发现。

5. 参数调优实操记录:我踩过的坑

5.1 引力增益与斥力增益的配比

k_att 和 k_rep_obs 的比值是整个系统的"性格开关"。k_att 太大,无人机对障碍物"视而不见",经常以极小角度擦过球体表面;k_rep_obs 太大,机群会在空旷区域反复绕路,路径弯曲程度明显夸张,甚至出现"不敢前进"的瘫痪状态。

经验规则是:先用k_rep_obs=2.0、k_att=0.5起步,观察最小距离曲线是否有明显尖峰,若有则说明斥力不足,按0.5的步长增加k_rep_obs,直到最小距离稳定在安全阈值上方。

5.2 感知半径与斥力作用半径的配合

感知半径必须比斥力作用半径大至少20%-30%,否则会出现"刚到感知边缘就被强斥力推开"的跳变现象,轨迹上表现为明显的拐折点。同时感知半径也不是越大越好——在集群场景下,感知范围大会把远处的邻居也纳入斥力计算,导致机群过度扩散。我的配置是斥力作用半径5m、感知半径6m,这样预留了1m的决策缓冲带。

5.3 迭代步长与仿真精度的取舍

表:不同步长下的表现

dt (s)单次仿真时长最小机间距离轨迹平滑度
0.2快出现过穿模明显锯齿
0.05中稳定平滑
0.01慢稳定平滑但耗时约6倍

仿真步长并不完全是越小越好。步长小时斥力计算更精确,但计算耗时线性增长;步长过大则可能出现"上一帧还在斥力范围外、下一帧就撞进障碍物内部"的穿透问题。0.05秒对示例场景来说是个折中点。

5.4 局部极小值的现场表现

我复现得最稳定的一次局部极小值场景:一架无人机从(35, 20, 10)附近出发去往(45, 40, 24),中途必须绕过(35, 20, 10)附近的球体,配置恰好使无人机停在球体正上方偏左的位置,速度降至0.02 m/s以下,之后十几秒没有任何前进动作。加随机扰动的处理方式是检测到速度低于0.05 m/s持续3秒后,在垂直方向注入一个幅值为0.5的脉冲力,持续2秒。加入后效果立竿见影,无人机顺利从侧面脱离势阱。

6. 常见问题与排查技巧速查表

现象可能原因处理方式
无人机撞上障碍物表面斥力增益过小 / 步长过大增大k_rep_obs,减小dt;检查dist_to_surface被夹取到0.1的兜底逻辑是否过度保护
机群整体偏航漂移边界约束缺失 / 目标引力方向向量错误检查目标引力中diff方向是否正确;添加边界软约束
无人机在目标点附近振荡速度阻尼不够 / 到达判定距离过小添加阻尼系数;增大stop_dist到1.0以上
机群过度扩散感知半径过大 / 机间斥力增益过大减小sense_radius;适当降低k_rep_drone
多机同时挤向同一通道缺乏协同机制叠加优先转向力,让离目标点较近的无人机优先通过
轨迹出现锯齿状步长过大 / 障碍物斥力突变减小dt至0.05以内;检查斥力公式梯度是否连续
仿真提前发散位置更新时使用上一时刻未同步的速度确认所有无人机先统一计算合力,再统一更新状态,避免串行更新引入额外偏差

其中最后一条是批量式更新和串行式更新的问题,非常典型。很多初版代码会在循环内边更新边读取邻居位置,导致第2架飞机看到的"邻居位置"其实已经是第1架更新后的位置——这种情况下机间斥力计算就不公平了。正确做法是先用当前状态计算所有无人机的合力,存到临时数组,再统一应用位置和速度更新,保证所有感知都基于同一时刻的快照。

7. 后续扩展:从静态避障走向复杂动态环境

这套代码目前处理的是静态障碍物加机间避碰,属于集群避障的"地基"版本。方向确定之后,扩展空间其实很大。

动态障碍物是最直观的下一步。把障碍物位置从固定数组改成随时间变化的函数,或者加入恒速直线运动的多面体模型,人工势场法仍然适用,但需要额外考虑动态障碍物的速度向量,在斥力公式里加入相对速度项,否则会出现无人机对着迎面飞来的障碍物方向"硬闯"的情况。

编队保持是另一个热门扩展。当前版本中每架无人机是完全独立决策的,如果想做编队飞行,可以在合力中加上编队约束力,让机群在避障的同时保持特定几何构型。注意编队约束力不能喧宾夺主——避障是硬约束,编队是软约束,遇到障碍物时编队形状允许暂时被打破。

更进一步的落地方向包括:在Simulink中搭建完整的动力模型替换质点动力学、加入传感器噪声模型、把避障决策写成独立的类以便移植到ROS2环境、甚至接入PX4的软件在环仿真做代码复用验证。每一步扩展都能让这套仿真离真实飞行更近一步,但底层的人工势场逻辑和参数调试方法论依然通用。

根据我个人实际调试的经验,最有价值的做法不是一开始就追求算法复杂,而是把"最小可用版本"跑透——能稳定复现问题、能直观看到参数影响、能快速验证想法。这套项目恰恰提供了这样一个干净的实验场:障碍物固定、机群数量可调、势场函数透明、每一步都能可视化。把代码跑通一遍之后,你对"避障算法到底在解决什么问题""参数之间如何博弈"的理解,会比读十篇综述都扎实。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询