简介:这份资源面向无人机编队、机器人集群及自动化控制方向的学习者与研究者,聚焦二维空间中多智能体协同避障这一核心问题。压缩包共10个文件,全部为m脚本文件,整体约4KB,涵盖主程序入口、智能体位置绘制、邻接矩阵构建、障碍物邻接关系计算、范数函数、群集可视化以及碰撞函数等模块,便于在MATLAB环境下直接运行与二次修改。内容围绕一致性理论展开,涉及障碍物检测、避障策略设计、路径规划与动态调整等环节,可帮助读者理解智能体如何通过信息交换共享障碍物信息,在保持队形的同时安全绕行。目前已有211人学习下载,适合希望借助轻量级代码快速验证多智能体避障算法、搭建仿真实验或完成课程设计的读者参考。
1. 多智能体避障仿真包:从二维环境到群体协同的落地路径
如果你正在做多机器人编队、集群路径规划或者 ROS2 动态避障的课题,大概率会遇到一个尴尬局面:算法论文看了一堆,公式推导也能跟上,但真要在二维栅格地图里让五个智能体同时绕开静态障碍、彼此之间还不撞上,代码跑起来不是原地抖动就是集体卡死。这份「二维_避障.zip」就是冲着这个痛点来的——它把多智能体避障的完整仿真流程打包好了,包含环境建模、个体避障策略、群体间防碰撞逻辑以及可视化输出。适合两类人:一类是刚接触多智能体系统、需要一份能跑通的最小闭环来建立直觉的新手;另一类是做动态避障小车路径规划、想快速验证自己改进算法是否有效的熟手。它不解决硬件部署问题,但能把算法层面的坑先帮你趟一遍。
2. 拆开压缩包:二维栅格地图与智能体运动学模型怎么搭
拿到一个仿真包,我习惯先看它怎么描述世界和个体。二维避障仿真里,世界就是一张栅格地图,个体就是带运动学约束的质点或刚体。这两件事定义清楚了,后面所有避障逻辑才有讨论的基础。
2.1 栅格地图的生成与障碍物膨胀
常见做法是用二维数组表示地图,0 表示自由空间,1 表示障碍物。但直接拿原始障碍物去做避障规划,智能体贴着障碍物边缘走的时候很容易因为离散步进导致碰撞误判。所以工程上一般会对障碍物做膨胀处理,把障碍物向外扩张一个安全半径。
import numpy as np def create_grid_map(width, height, obstacle_list, inflate_radius=1): """ 生成二维栅格地图并对障碍物做膨胀 width, height: 地图尺寸 obstacle_list: [(x, y), ...] 障碍物中心坐标 inflate_radius: 膨胀半径,单位是栅格数 """ grid = np.zeros((height, width), dtype=np.int8) for ox, oy in obstacle_list: # 先标记原始障碍物 if 0 <= ox < width and 0 <= oy < height: grid[oy, ox] = 1 # 膨胀操作:对每个障碍物格子,把周围半径内的格子也标记为障碍 inflated = grid.copy() for oy in range(height): for ox in range(width): if grid[oy, ox] == 1: for dy in range(-inflate_radius, inflate_radius + 1): for dx in range(-inflate_radius, inflate_radius + 1): nx, ny = ox + dx, oy + dy if 0 <= nx < width and 0 <= ny < height: inflated[ny, nx] = 1 return inflated这段代码的逻辑很直白:先按障碍物坐标在栅格上打点,然后以每个障碍物格子为中心,把膨胀半径覆盖到的邻域全部置为障碍。参数inflate_radius是关键,设太小等于没膨胀,设太大会把窄通道堵死导致无解。我一般会把它设成智能体半径加一个栅格余量,比如智能体半径对应 0.5 个栅格,那膨胀半径就取 1。注意膨胀后的地图只用于规划,可视化的时候最好把原始障碍物和膨胀层用不同颜色区分开,不然调试时你会怀疑地图为什么变胖了。
2.2 智能体运动学:差速模型还是质点模型
仿真包里智能体的运动方式决定了避障算法的输出怎么转成实际位移。如果只是验证避障逻辑本身,用质点模型最省事:智能体有位置和速度,速度矢量直接由避障算法给出。但如果你后面要对接 ROS2 动态避障或者真实小车,差速模型更贴近现实——智能体只能前进和旋转,不能横移。
class DifferentialDriveAgent: def __init__(self, x, y, theta, radius=0.3, max_v=1.0, max_w=1.5): self.x = x self.y = y self.theta = theta # 朝向角,弧度 self.radius = radius self.max_v = max_v # 最大线速度 self.max_w = max_w # 最大角速度 def step(self, v, w, dt): """根据线速度v和角速度w更新位姿""" # 限幅,防止仿真步长过大导致跳变 v = np.clip(v, -self.max_v, self.max_v) w = np.clip(w, -self.max_w, self.max_w) self.x += v * np.cos(self.theta) * dt self.y += v * np.sin(self.theta) * dt self.theta += w * dt # 角度归一化到 [-pi, pi] self.theta = (self.theta + np.pi) % (2 * np.pi) - np.pi差速模型下,避障算法输出的期望速度矢量不能直接赋值,得先转成线速度和角速度。常见做法是算期望速度方向与当前朝向的夹角,用比例控制器生成角速度,线速度则根据夹角大小做衰减——夹角越大走得越慢,避免急转弯时冲过头。参数max_v和max_w要根据仿真步长dt来调,如果dt=0.1而max_v=2.0,一步就能挪 0.2 米,在小地图里很容易直接穿过障碍物。我一般会把单步位移控制在智能体半径的三分之一以内。
2.3 多智能体初始化与通信拓扑
多智能体避障和单智能体的本质区别在于个体之间要互相避让。仿真包里通常会给每个智能体分配一个目标点,然后让它们从不同起点出发。通信拓扑决定了谁能看到谁的位置:全连接最简单,每个智能体都知道其他所有智能体的位置;邻居拓扑更接近真实场景,只和一定距离内的个体交换信息。
def init_agents(num_agents, map_width, map_height, safe_dist=0.8): """随机初始化智能体位置,保证初始间距大于安全距离""" agents = [] attempts = 0 while len(agents) < num_agents and attempts < 1000: x = np.random.uniform(1, map_width - 1) y = np.random.uniform(1, map_height - 1) ok = True for a in agents: if np.hypot(x - a.x, y - a.y) < safe_dist: ok = False break if ok: theta = np.random.uniform(-np.pi, np.pi) agents.append(DifferentialDriveAgent(x, y, theta)) attempts += 1 return agents初始化看着简单,但坑不少。如果随机撒点不检查间距,两个智能体可能重叠着出生,避障算法一启动就陷入互相排斥的死循环。safe_dist建议设成两倍智能体半径再加一点余量。另外目标点也要检查,别把目标点设在障碍物里面,否则智能体会在障碍物边缘反复试探直到超时。
3. 避障策略落地:人工势场法与速度障碍法怎么选怎么调
环境搭好之后,核心就是每个智能体怎么决定下一步往哪走。仿真包里一般会集成不止一种避障策略,方便对比。人工势场法和速度障碍法是二维多智能体避障里最常被拿来做 baseline 的两种,各有各的脾气。
3.1 人工势场法:引力斥力合成与局部极小值处理
人工势场法的思路很符合直觉:目标点对智能体产生引力,障碍物和其他智能体产生斥力,合力方向就是运动方向。实现起来代码量少,实时性好,但在多智能体场景下局部极小值问题会被放大。
def artificial_potential_field(agent, goal, obstacles, other_agents, k_att=1.0, k_rep=2.0, rep_range=1.5): """ 计算人工势场合力 k_att: 引力增益 k_rep: 斥力增益 rep_range: 斥力作用范围 """ # 引力:指向目标点 att_force = k_att * (np.array(goal) - np.array([agent.x, agent.y])) # 斥力:来自障碍物和其他智能体 rep_force = np.zeros(2) all_obstacles = list(obstacles) + [(a.x, a.y) for a in other_agents if a is not agent] for ox, oy in all_obstacles: dx = agent.x - ox dy = agent.y - oy dist = np.hypot(dx, dy) if 0 < dist < rep_range: # 斥力大小与距离成反比,方向远离障碍 magnitude = k_rep * (1.0 / dist - 1.0 / rep_range) / (dist ** 2) rep_force += magnitude * np.array([dx, dy]) / dist total_force = att_force + rep_force return total_force引力增益k_att和斥力增益k_rep的比值直接决定行为风格。k_att太大,智能体会勇往直前然后被斥力猛地弹开,轨迹像在抽搐;k_rep太大,智能体会离障碍物老远就开始绕,窄通道根本过不去。我一般先把k_att设为 1.0,然后从 1.5 开始试k_rep,观察轨迹是否平滑。rep_range要大于膨胀半径,否则智能体还没进入斥力范围就已经撞上膨胀层了。
局部极小值是人工势场法的经典翻车点:当引力和斥力刚好抵消,智能体会停在原地。多智能体场景下更麻烦,两个智能体互相排斥又都想去同一个窄出口,就容易在出口前形成对峙。常见做法是加一个随机扰动或者切换成沿墙走策略,仿真包里如果有状态机切换逻辑,记得把触发条件调得敏感一些。
3.2 速度障碍法:相对速度锥与避让时机
速度障碍法换了个角度:不看力,看速度。如果两个智能体保持当前速度,未来某个时刻会碰撞,那它们当前的速度组合就落在速度障碍锥里。每个智能体通过调整自己的速度,让自己避开所有障碍物和其他智能体产生的速度障碍锥。
def compute_velocity_obstacle(agent, other, dt=0.5, safety_margin=0.2): """ 计算agent相对于other的速度障碍锥参数 返回一个角度范围,落在这个范围内的相对速度会导致碰撞 """ rel_pos = np.array([other.x - agent.x, other.y - agent.y]) dist = np.linalg.norm(rel_pos) combined_radius = agent.radius + other.radius + safety_margin if dist < combined_radius: # 已经太近,返回全方向避让 return None # 相对位置的角度 angle_to_other = np.arctan2(rel_pos[1], rel_pos[0]) # 速度障碍锥的半角 half_angle = np.arcsin(combined_radius / dist) return (angle_to_other - half_angle, angle_to_other + half_angle)速度障碍法的优势在于它显式考虑了时间维度,避让动作更提前、更平滑。但参数dt和safety_margin需要仔细调。dt是预测时间窗口,设太小的话智能体反应滞后,设太大又会导致过度避让、路径绕远。我一般取 0.5 到 1.0 秒之间的值,具体看智能体最大速度和地图尺度。safety_margin是额外安全余量,用来补偿仿真步长带来的离散误差,通常取智能体半径的 0.2 到 0.5 倍。
多智能体场景下,速度障碍法需要每个智能体对所有邻居都算一遍速度障碍锥,然后找一个不在任何锥内的可行速度。如果可行速度集合为空,说明当前状态无解,需要降速或者紧急停止。仿真包里如果实现了速度障碍法,建议加一个降速重试逻辑:第一次找不到可行速度就把期望速度减半再试,还不行就原地旋转。
3.3 两种策略的对比与混合使用
| 对比维度 | 人工势场法 | 速度障碍法 |
|---|---|---|
| 计算量 | 低,每步只算合力 | 中,需遍历邻居并求可行速度 |
| 轨迹平滑度 | 一般,参数不当时抖动明显 | 较好,速度变化连续 |
| 局部极小值 | 容易陷入 | 较少,但可能出现无解 |
| 多智能体扩展 | 斥力叠加简单但易震荡 | 需处理可行速度集合为空 |
| 调参难度 | 增益和范围敏感 | 预测窗口和安全余量敏感 |
实际项目中,我见过不少方案是把两者混着用:远距离用人工势场法快速接近目标,进入密集区域后切换到速度障碍法做精细避让。仿真包里如果两种都提供了,可以写个简单的切换逻辑,用最近障碍物距离作为切换条件。注意切换时速度要平滑过渡,不然轨迹上会出现折角。
4. 避坑与排查:多智能体避障仿真里最容易翻车的五个地方
仿真跑不起来或者结果不对,八成是下面这几个问题。我按现象、原因、解决的结构列出来,方便你对照排查。
4.1 智能体原地抖动或画圈
现象:智能体在某个位置附近来回震荡,不往目标点走。原因:人工势场法引力和斥力在某个位置达到平衡,或者速度障碍法可行速度集合频繁切换导致左右摇摆。解决:先检查目标点是否在障碍物膨胀层内部,如果是就重新选目标点;然后在合力方向上加一个小的历史速度惯性项,让智能体有保持当前运动方向的趋势;如果用的是速度障碍法,把预测时间窗口dt调大一点,减少速度切换频率。
4.2 智能体之间发生穿透
现象:两个智能体在仿真里重叠了,但避障算法没有报错。原因:仿真步长太大,单步位移超过了安全距离,或者斥力范围小于两倍智能体半径。解决:把仿真步长dt减小到单步位移不超过智能体半径的三分之一;检查斥力作用范围rep_range是否大于两倍智能体半径加安全余量;在位置更新后加一个硬性碰撞检测,如果间距小于两倍半径就强制推开。
4.3 窄通道集体堵死
现象:多个智能体都要通过一个窄出口,结果在出口前挤成一团,谁也过不去。原因:斥力叠加导致出口处的合力指向远离出口的方向,或者速度障碍法下所有智能体的可行速度都指向出口外侧。解决:在窄通道区域临时降低斥力增益,或者引入优先级机制——距离出口最近的智能体优先通过,其他智能体在通道外等待;也可以给每个智能体加一个随机扰动,打破对称对峙。
4.4 目标点不可达但算法不报错
现象:智能体在目标点附近绕圈但始终到不了,仿真一直跑不结束。原因:目标点被障碍物膨胀层覆盖,或者目标点距离障碍物太近导致斥力始终大于引力。解决:在初始化阶段检查目标点是否在自由空间内,并且与最近障碍物的距离大于膨胀半径加智能体半径;如果目标点确实在障碍物附近,把到达判定阈值放宽,比如距离目标点小于一个智能体半径就算到达。
4.5 仿真速度越来越慢
现象:刚开始跑很流畅,智能体多了或者跑久了之后帧率明显下降。原因:每步都在做全量邻居遍历,或者可视化部分每帧都在重绘整个地图。解决:把邻居查询改成空间哈希或者网格索引,只检查附近格子里的智能体;可视化部分把静态地图缓存成背景图,每帧只重绘智能体位置;如果不需要实时看,把可视化关掉纯跑数据,速度能快好几倍。
5. 从仿真到验证:怎么确认你的多智能体避障真的有效
仿真跑通只是第一步,怎么判断避障策略是真的有效而不是碰巧没撞上,需要一套验证方法。我一般会从三个维度来评估:安全性、效率和鲁棒性。
安全性最直接的指标是碰撞次数和最小间距。跑一百次随机初始化,统计有多少次出现了智能体间距小于两倍半径的情况。如果碰撞率超过百分之五,说明安全余量不够或者避障逻辑有漏洞。最小间距的分布也能看出问题:如果很多次都贴着安全边界走,说明策略太激进,稍微加点噪声就会撞。
效率看的是路径长度和到达时间。把所有智能体的实际路径长度加起来,除以起点到目标点的直线距离总和,得到一个路径效率比。这个比值在 1.2 到 1.5 之间算正常,超过 2.0 说明绕路太严重,可能是斥力范围设太大了。到达时间的方差也值得看,方差大说明有些智能体被堵了很久,群体协同有问题。
鲁棒性测试是往仿真里加噪声。给每个智能体的位置加高斯噪声,模拟定位误差;给速度执行加随机延迟,模拟通信和响应延迟。如果加了噪声之后碰撞率飙升,说明避障策略对状态估计太敏感,需要加大安全余量或者引入滤波。
def evaluate_swarm(agents, goals, collision_threshold, dt, max_steps=2000): """跑一次仿真并返回安全性、效率指标""" collision_count = 0 min_dist_record = float('inf') total_path_length = 0.0 arrival_times = [] for step in range(max_steps): # 这里调用你的避障算法更新每个智能体的速度 # update_agents(agents, goals, ...) for i, a in enumerate(agents): a.step(a.v, a.w, dt) total_path_length += abs(a.v) * dt # 检查碰撞 for i in range(len(agents)): for j in range(i + 1, len(agents)): d = np.hypot(agents[i].x - agents[j].x, agents[i].y - agents[j].y) min_dist_record = min(min_dist_record, d) if d < collision_threshold: collision_count += 1 # 检查到达 for i, a in enumerate(agents): if i not in [t[0] for t in arrival_times]: if np.hypot(a.x - goals[i][0], a.y - goals[i][1]) < 0.3: arrival_times.append((i, step * dt)) if len(arrival_times) == len(agents): break return { 'collision_count': collision_count, 'min_distance': min_dist_record, 'total_path_length': total_path_length, 'arrival_times': arrival_times }这个评估函数把关键指标都收回来了。collision_threshold一般设成两倍智能体半径,max_steps根据地图大小和智能体速度来定,别设太小导致还没到目标就超时。跑完一百次之后,把collision_count和min_distance的分布画出来,比只看单次结果靠谱得多。
还有一个容易被忽略的验证点:把智能体数量从 3 个逐步加到 10 个,看碰撞率和路径效率比怎么变化。如果加到 5 个以上就频繁碰撞,说明避障策略的扩展性不行,可能需要引入分组或者分层规划。我自己的习惯是,每次改完避障参数,都强制跑一遍 3、5、8 个智能体的三组测试,确认没有退化才继续调。从那以后我每次调参都先跑小规模再跑大规模,省得在大场景里浪费时间排查低级问题。希望帮到你。
本文还有配套的精品资源,点击获取