资讯动态

分散式多智能体避障:算法选型、MATLAB实现与调参实战

发布时间:2026/10/20 17:09:21 来源:尧图企业网站定制
做多智能体的避障项目最绕不开的灵魂拷问就是你用中心式还是分散式我前几年做仿真图省事上了一套中心式调度——所有机器人把信息汇到主机主机算完轨迹再发回去。跑起来挺顺一往实际机器人上迁移就露馅通信延迟一上来整个系统跟着抖中间节点一掉线机器人全愣在原地。所以后来这个“基于分散性的多智能体障碍规避”项目我干脆把所有决策压到了每个智能体身上。今天这篇就把这套方案的算法选型、MATLAB实现思路和调参踩坑过程全摊开给做多机器人协同、无人机编队、AGV调度的朋友一个可复用的参考。1. 项目到底在做什么分散式避障的核心问题拆解1.1 中心式到分散式绕不开的现实问题中心式控制的本质是“有个上帝视角的大脑”。所有环境感知数据、所有智能体的位置速度都要汇到一个中心节点由中心节点做全局规划再把控制指令分发给每个智能体。这套模式如果环境信息完全已知、通信稳定效果确实好尤其在做全局最优路径规划时非常方便。但放到实战场景里问题很明显第一通信带宽是瓶颈智能体数量一多数据交互量就爆炸第二中心节点是单点故障一旦宕机整个集群直接瘫痪第三每个智能体没有自主决策能力遇到突发动态障碍物还得等中心节点反应时间上根本来不及。分散式控制走的是另一条路线每个智能体即是一个完整的决策单元它只依赖自身的传感器感知结果以及通过局部通信拿到的邻居状态信息在本地生成控制指令。没有谁拥有全局地图没有谁在“全局调度”。这相当于每个人只照顾自己眼前的事但最终整体却能涌现出有序的队形和避障行为。类比一下中心式就像“指挥官拿对讲机指挥一队人抬桌子”每个搬桌子的人不用看旁边的人只要听命令就行分散式更像“一群鱼在水中游动”每条鱼只感知附近几条鱼的位置和速度就能同步转向、分散、聚拢。对多智能体系统来说分散式天然具备可扩展性也更接近物理世界中智能机器人的真实约束。1.2 分散式避障的三个子问题第一个子问题是静态环境障碍规避。环境中的墙壁、柱子、石块、货架等位置可以事先知道也可能事先不知道但短时间内不会移动。对每个智能体而言关键是能用自己的激光雷达、视觉或预设地图信息把“我不应该进入的区域”表达成一个约束然后在目标导航的同时保持这个约束。第二个子问题是智能体之间的相互避碰。这比静态障碍物更难因为每个智能体本身是一个移动的障碍物且它的行为也在变化。邻居可能突然提速、急转甚至通信短暂丢失。这种“感知预测响应”的组合要求避碰算法不能只看当前距离还要结合相对速度来判断威胁。第三个子问题是任务目标保持。避障不等于乱跑每个智能体最终要到达自己的目标点。如果有编队要求比如保持V形队形或维持固定相对位置那还要考虑“避障-队形”之间的平衡。避障动作太大队形就散了队形纠偏太强又可能避不开障碍物。三个问题耦合在一起正是多智能体避障的难点所在。1.3 项目目标与场景假设项目直接在二维平面上建模仿真场景定义如下8个智能体从左侧起点出发目标点分散在右侧区域地图中央布置了4个圆形静态障碍物每个智能体只感知半径4米范围内的障碍物和其他智能体智能体之间默认可以交换位置和速度信息模拟一个低带宽广播通信但不依赖中心节点。控制频率20Hz运动学模型用最简单的单积分器。选这个场景是因为它足以暴露上面三个子问题静态障碍物需要躲多智能体在一个狭窄通道里会遭遇互碰同时还要保证每个智能体按时到达目标点。相比一上来就编队或三维飞行这个场景更适合用来验证“分散式避障”这个核心问题。项目目标很明确在给定的仿真时间里让所有智能体无碰撞地到达各自的目标点不依赖任何中央规划器。衡量指标包括碰撞次数、到达率、平均行程时间、避障过程中的速度平滑度。2. 避障算法选型几种主流方案的取舍2.1 人工势场法APF直观但不够人工势场法大概是最容易入门的一种避障思路。它把目标点想象成一个“引力源”把障碍物想象成一个“斥力源”智能体在势场中沿合力方向移动。公式很简单引力正比于目标点与自身的位置差斥力反比于障碍物距离的某种倒数关系。在实际代码中通常这样写斥力F_rep k_rep * (1/d - 1/d_0) / d^2 * unit(delta)其中d是智能体到障碍物的最近距离d_0是斥力影响范围。距离越小斥力越大到边界处趋近无穷这样就形成一个“软约束的墙”。APF的优点是计算量极小一个控制器里几行代码就实现实时性非常好。缺点同样明显一是局部极小智能体可能在障碍物前方某个平衡点来回震荡永远绕不过去二是动态避碰能力弱因为APF本质上只看“位置差”没有利用“相对速度”信息三是当目标点离障碍物很近时引力斥力在目标点附近打太极导致终端抖动。所以直接拿APF做分散式多智能体避障小场景能跑通复杂场景基本靠运气。2.2 速度障碍法VO动态场景下的利器速度障碍法是从“速度空间”角度解决避碰的经典方法。它的思想是把另一个智能体或动态障碍物当作一个会保持当前速度运动的圆然后考察自己有哪些速度会导致未来一段时间内发生碰撞。这些“会碰撞的速度”构成一个碰撞锥我只要让我的速度偏离这个锥就能规避动态威胁。在多智能体场景中每个智能体都可以基于邻居速度计算碰撞锥再选择一个不会碰撞的可行速度。VO的强项在于对动态威胁的判断非常直接。与APF相比它天然包含了相对速度信息而且能给出一个“安全速度集合”的感觉更像是在做“速度层的决策”。但VO也有它的坑第一它假设其他智能体保持当前速度不变但实际上对方也会避障这个假设会让系统出现“抖动”甚至振荡这就要靠互相协商或引入时间窗来解决第二选择哪个速度从可行速度集合里怎么取值本身是个优化问题需要设定目标函数例如兼顾目标方向、速度大小和平滑性第三它要求对方速度“可观测”在真实机器人上需要速度估计噪声大了效果会变差。2.3 我的组合方案势场为骨速度项为翼我在这个项目里没有用纯APF也没有用纯VO而是选了一个更工程化的折中方案主体还是势场法但把“相对速度”作为一个修正项引进去。静态障碍物依旧用经典斥力对邻居智能体不仅考虑相对位置还根据相对速度判断威胁趋势——如果正在靠近就额外叠加一个“躲避修正力”如果已经远离就不再作用。这样既保留了APF的实时性和平滑性又吸收了速度障碍法的预测思路。在MATLAB里这个方案非常容易实现后续也方便升级成基于控制障碍函数CBF的安全约束版。为什么不用纯VO因为纯VO需要维护“碰撞锥”和“速度选择优化”在密集多智能体场景里速度空间里可选择的速度集合会被碎片化难以保证算法的平滑性。而基于势场的修正项天然连续输出速度不会突变工程调参更友好。如果之后追求严格的安全证明我会再引入CBF把APF加修正项作为标称控制器用二次规划做安全过滤。3. MATLAB代码实现核心架构与关键代码拆解3.1 代码工程如何组织先给一个可以直接照抄的文件组织方式。虽然这个仿真很小但我不建议把所有逻辑塞进一个main文件里后面调试会非常痛苦。我把工程分成了以下几个文件sim/ ├── main.m ├── init_params.m ├── decentralized_controller.m ├── detect_obstacles.m ├── detect_neighbors.m └── draw_frame.mmain.m负责主循环和数据记录init_params.m集中管理所有参数decentralized_controller.m是每个智能体本地的决策逻辑也是核心detect_obstacles.m和detect_neighbors.m模拟局部感知draw_frame.m负责绘图和动画。这样分文件的好处是想改感知范围、想换控制器、想加新障碍物都不需要动主流程代码。初始化参数我用一个函数返回结构体而不是脚本式散落一堆变量方便传参function p init_params() p.N 8; p.dt 0.05; p.v_max 1.0; p.k_att 0.8; p.k_rep 2.5; p.k_avo 1.8; p.d_rep 1.5; p.d_safe 1.2; p.d_safe_vel 1.8; p.sensing_range 4.0; p.obstacles struct(pos, {[3,3], [5,6], [7,2], [8,7]}, r, {0.5, 0.6, 0.5, 0.4}); p.starts [0,0; 0,1; 1,0; 1,1; 0,2; 2,0; 2,1; 0,3]; p.targets [10,10; 10,9; 9,10; 9,9; 10,8; 8,10; 8,9; 10,7]; end解释一下为什么要把参数放在结构体里在分散式控制器里每个智能体都需要访问这些参数如果到处定义全局变量代码一长就很容易出现同名覆盖问题。把参数打包成结构体后控制器函数只需要多传一个参数逻辑立刻清晰。3.2 局部感知与邻居信息分散式系统的核心是“局部信息”我分别写了两个辅助函数来模拟“感知”和“通信”。检测障碍物时返回感知范围内的障碍物列表检测邻居时返回邻居的位置、速度代码如下function obs_list detect_obstacles(pos_i, obstacles, sensing_range) obs_list []; for j 1:length(obstacles) d norm(pos_i - obstacles(j).pos) - obstacles(j).r; if d sensing_range obs_list(end1, :) [obstacles(j).pos, obstacles(j).r]; end end endfunction nb_list detect_neighbors(pos, vel, idx, sensing_range) nb_list []; N size(pos, 1); for j 1:N if j idx continue; end d norm(pos(idx, :) - pos(j, :)); if d sensing_range nb_list(end1, :) [pos(j, :), vel(j, :)]; end end end这里有一个实际工程中容易被忽视的点detect_neighbors返回的是“全部邻居信息”但并没有区分“这个邻居在不在我的前方”“它是不是潜在威胁”。这一步做了简化但如果你要追求更高安全性能建议在控制器里再加一个威胁优先级判断。感知范围越大智能体越“安全”但计算量呈平方增长。对8个智能体的仿真没问题数量上百后就要考虑用空间分区或者KD树来加速了。3.3 分散式控制器核心实现核心控制器实现了我前面讲的“势场为骨速度项为翼”的组合思路。函数输入是当前位置、当前速度、目标点、感知到的静态障碍物、邻居信息和参数结构体输出是期望速度。function vel decentralized_controller(pos_i, vel_i, target_i, obs_i, nb_i, p) F_att p.k_att * (target_i - pos_i); % 静态障碍物斥力 F_rep zeros(1, 2); for j 1:size(obs_i, 1) obs_pos obs_i(j, 1:2); obs_r obs_i(j, 3); delta pos_i - obs_pos; d norm(delta) - obs_r; if d p.d_rep d 0 mag p.k_rep * (1/d - 1/p.d_rep) / d^2; F_rep F_rep mag * delta / norm(delta); end end % 邻居智能体避碰相对位置 相对速度 F_avo zeros(1, 2); for j 1:size(nb_i, 1) nb_pos nb_i(j, 1:2); nb_vel nb_i(j, 3:4); rel_pos pos_i - nb_pos; d norm(rel_pos); if d p.d_safe_vel rel_vel vel_i - nb_vel; approach -dot(rel_pos, rel_vel) / max(d, 1e-3); if d 0 F_avo F_avo p.k_rep * (1/d - 1/p.d_safe) * rel_pos / d; end if approach 0 % 正在靠近叠加速度修正项 F_avo F_avo p.k_avo * approach * rel_vel / max(d, 0.1); end end end vel F_att F_rep F_avo; end说明一下这个控制器的设计意图引力项让智能体朝目标走静态障碍物斥力避免撞墙邻居项是两部分的叠加位置斥力保证即使两个智能体静止靠在很近的距离也不会贴在一起速度修正项则处理“正在靠近”的动态威胁。approach越大说明相对速度在接近对方修正力越大相当于提前减速或偏移。这些系数在代码里都不是随便给的它们的量级关系影响很大。比如k_rep太大会导致不必要的绕路k_att太大会导致避障失效。我习惯的做法是先固定感知范围再把k_att设为一个基础参考值然后逐步增大k_rep直到不发生碰撞最后再调k_avo来抑制多智能体互撞。3.4 主循环与动画可视化主循环要解决一个顺序耦合问题如果在一个for i 1:N循环里一边更新pos一边更新vel那么第i个智能体更新pos时会影响到后面智能体的邻居检测结果。所以我先计算所有智能体的新速度再统一更新位置避免同一帧内“有的智能体提前移动、有的还留在原地”的不一致现象。这也是分散式仿真里最容易踩的坑之一。for k 1:steps new_vel zeros(N, 2); for i 1:N obs_i detect_obstacles(pos(i, :), p.obstacles, p.sensing_range); nb_i detect_neighbors(pos, vel, i, p.sensing_range); v decentralized_controller(pos(i,:), vel(i,:), p.targets(i,:), obs_i, nb_i, p); if norm(v) p.v_max v v / norm(v) * p.v_max; end new_vel(i, :) v; end vel new_vel; pos pos vel * p.dt; traj(:, :, k) pos; if mod(k, 10) 0 draw_frame(pos, p.obstacles, p.targets, traj, k); end end绘制函数我就用MATLAB基础绘图命令避免引入工具箱依赖。动态画圆用viscircles智能体用plot的小圆点表示轨迹用线连接目标点用绿色圆圈。对于学习用途这个可视化已经完全够用。function draw_frame(pos, obstacles, targets, traj, k) clf; hold on; axis equal; xlim([-1 12]); ylim([-1 12]); for i 1:length(obstacles) viscircles(obstacles(i).pos, obstacles(i).r, Color, k); end plot(targets(:,1), targets(:,2), go, MarkerSize, 8); plot(pos(:,1), pos(:,2), bo, MarkerSize, 6); for i 1:size(pos,1) plot(squeeze(traj(i,1,1:k)), squeeze(traj(i,2,1:k)), -, LineWidth, 1); end drawnow; end在MATLAB里结构体数组和普通数组混用容易出错我这里统一用了形状规整的二维数组来存障碍物和邻居信息就是为了减少索引出错的可能性。4. 实操记录从仿真到调参的全过程4.1 首次跑通的仿真场景第一次跑通时我用的参数是k_att1.0、k_rep2.0、k_avo1.5、感知范围4m、dt0.05s。8个智能体分别从左侧出发。观察结果会发现一个大问题前面几个智能体很快绕过第一个障碍物但后面几个被前面的智能体挡住产生“扎堆”现象。它们不会真的撞上因为斥力和避碰项在起作用但前进速度非常慢像堵车一样挤在障碍物边缘。这个“扎堆”现象本质上是分散式系统没有全局协调的表现每个智能体都选择当前自己认为最优的方向结果多个局部最优解撞到一起整体陷入低效。解决思路不是加中心调度器而是调整避碰项的选择逻辑让智能体在绕障碍时保持更大的横向间隔从而避免大家挤同一条绕行路线。4.2 关键参数怎么调参数调整经验整理成一张表方便直接对照使用属性现象调整方向k_att过大智能体直奔目标忽略避障撞上障碍物适当减小k_att或增加k_repk_rep过大避障过于敏感绕大弯甚至卡在多个斥力交汇处减小k_rep或增加切向扰动k_avo过大邻居一靠近就剧烈闪避导致整体振荡减小k_avo增大d_safe_velsensing_range过小发现障碍物太晚刹不住车增大感知范围同时降低v_maxdt过大位置更新不稳定出现穿模或振荡减小dt或用双积分运动学模型并限制加速度调参的顺序一般这样走先关掉邻居避碰项只调静态障碍物斥力让单个智能体能够在静态障碍场里顺利到达目标再打开邻居避碰项调k_avo让两个智能体迎面相遇时能平滑错开最后再放到8个智能体的完整场景中看整体效果。这样分步调参比直接上全场景反复试运气要高效得多。4.3 一个局部极小的处理实例实际调试中遇到过一个最典型的局部极小智能体从(1,1)出发目标在(10,10)中间障碍物在(5,5)。引力指向目标斥力沿障碍物径向向外两个力在某条直线上完全抵消智能体停在障碍物边缘来回震荡。此时距离障碍物约0.6米距离目标5米开外。一开始以为是参数不对把k_rep调小试了几次无济于事。后来加了一个很朴素的“逃逸策略”当检测到智能体连续若干步的前进速度小于阈值且目标方向与当前速度方向夹角大于90度时就沿着障碍物切线方向加一个固定的切向力持续一两秒。就是这一下小扰动让智能体脱离平衡点绕过去之后重新被引力主导。这个方法在代码上改动很少在8智能体场景中能解决大多数局部极小问题。它的本质是给势场法增加了一个临时性的记忆打破对称平衡。5. 常见问题与排查技巧5.1 问题速查表下面这些问题都是实际运行过程中逐一遇到过的整理成速查表方便排查现象可能原因解决办法智能体撞上静态障碍物感知范围太小、引力和斥力比例失衡增大sensing_range或降低k_att/k_rep智能体互相挤压推搡邻居避碰项强度不足、d_safe值太小增加k_avo增大d_safe_vel仿真发散、速度爆炸dt过大、速度限幅没生效限制v_max减小dt到0.01~0.02智能体在目标点附近抖动目标点引力与障碍物斥力在近距离内对抗在距离目标小于某个阈值时切换到纯引力控制轨迹来回振荡呈S形避碰项修正过强、没有阻尼减小k_avo或对速度做低通滤波两个智能体互相让路让成僵局双方同时朝同一个方向避让加入固定偏移偏好例如让编号小的智能体优先向左让5.2 代码层面的几个坑第一个坑是在主循环里直接修改vel再更新pos时存在本帧顺序依赖。前面提到过要用new_vel暂存这个改动很小但如果没有意识到运行结果会出现奇怪的“后一个智能体躲前一个智能体”的倾向。第二个坑是结构体数组的访问方式。MATLAB里如果obstacles是struct数组循环取obstacles(i).pos非常直观但如果你把障碍物信息打包成N×3矩阵索引逻辑又要重新适应。我自己吃过几次亏后来统一规范障碍物和邻居信息都用矩阵全部是[pos_x, pos_y, r]或[pos_x, pos_y, vx, vy]这种规整格式这样数组操作方便出错率低。第三个坑是速度限幅的位置。我在控制器之后、位置更新之前限幅但注意限幅后的速度是“期望速度”而实际使用单积分模型时位置更新用的是这个限幅后的速度这是对的。如果你把限幅放在控制器内部而控制器里又需要原始相对速度来计算approach那么限幅会破坏approach的计算导致避碰判断失真。所以最好保留一个未限幅的速度用于逻辑判断输出端再限幅。第四个坑是图形化仿真环境和纯函数混用。有一些朋友习惯先在图形化环境里搭模型再想把避障逻辑放到内置代码模块里。这样做不是不行但代码模块里的变量作用域和调试体验和独立脚本差别很大遇到问题时会很别扭。我建议先用独立脚本或函数把这个仿真跑通确认算法没有问题之后再考虑做更复杂的传感器模型或控制结构。6. 写在最后几点体会6.1 我对分散式避障的重新理解这个项目做下来我最深的感受是分散式避障的难点其实不在某一个算法而在于把“局部信息下的决策”和“整体系统的涌现行为”对起来。你单独看某个智能体的轨迹可能会觉得它绕得不够最优路径不够漂亮但放到整个系统里你看到的是每个智能体都在用自己的局部感知做反应整个队群却能顺利穿过障碍场这种稳定性的来源不是“有人设计了全局路径”而是“每个局部决策都足够安全”全局秩序是涌现出来的。6.2 给刚开始做这个方向的朋友三点建议第一先从最简单的二维单积分模型开始把算法跑通再逐步换成更贴近机器人的运动学模型否则一开始就陷入底层坐标变换、电机控制里很难判断问题到底出在避障算法还是运动控制。第二参数一定要分步调不要想一次性调到完美。第三在仿真里一定要留足可视化时间反复播放动画很多问题你看曲线看得头晕看动画一眼就明白了。如果后续还想深入可以在这个基础上做三个方向的扩展一是把模型升级成带加速度约束的双积分模型这样更接近真实无人机二是给每个智能体的速度估计加噪声考核算法的鲁棒性三是引入控制障碍函数CBF把当前这套启发式避碰项替换成带安全约束的优化控制往带安全性证明的方向靠。无论怎么走这套分散式的代码框架都可以作为底座让你反复复用。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑