ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

多智能体路径规划Python实战:从A*到CBS协同避障

多智能体路径规划Python实战:从A*到CBS协同避障 简介这份资源面向计算机、电子信息工程、数学等专业的大学生及路径规划初学者围绕多智能体路径规划这一核心问题提供可运行的Python实现方案帮助完成课程设计、期末大作业与毕业设计。压缩包共约2000个文件以1988个yaml配置文件为主配合10个py核心脚本及少量txt、md说明文档整体约7.22MB涵盖速度障碍、分布式控制、障碍物生成与多机器人可视化等模块结构清晰便于按功能检索。代码采用参数化编程参数可灵活调整注释详尽便于理解算法思路并快速修改实验条件。目前已有101人学习下载适合希望将理论知识与实践结合、掌握多智能体避碰与路径规划实现方法的读者参考使用。1. 多智能体路径规划 Python 方案从单机 A* 到多机协同避障的落地路径单台移动机器人跑 A* 或 DWA 早就不是难题真正让人头疼的是把三台、五台甚至十几台机器人塞进同一张地图里让它们各自到达目标点又不撞在一起。多智能体路径规划Multi-Agent Path FindingMAPF要解决的就是这个问题给定一张栅格或拓扑地图、一组起点和终点为每个智能体算出一条无碰撞的时空轨迹。它和单机路径规划最大的区别在于「时间维度」——两台机器人可以在不同时刻经过同一个格子但不能在同一时刻占据同一个位置也不能在相邻格之间对穿。这个方案适合做仓储 AGV 调度、多无人机编队、游戏 AI 寻路、动态避障小车集群的工程师也适合刚学完 Python 基础语法、想找一个能跑通的多智能体项目练手的人。下面从环境搭建一路讲到冲突消解和实测调参代码全部用 Python 写依赖尽量少。2. 环境准备与地图建模让 Python 跑通第一个多智能体场景2.1 Python 环境与依赖选型多智能体路径规划的计算量集中在搜索和冲突检测上纯 Python 写循环会慢所以选型上要兼顾开发效率和运行速度。我一般用 Python 3.10 以上版本配合 NumPy 做栅格运算用 heapq 做优先队列可视化用 matplotlib。不需要 ROS 也能跑通核心算法等算法验证完再往 ROS 里搬。如果你用的是 vscode 配置 Python 环境记得把解释器选对虚拟环境里装依赖避免和系统 Python 混在一起。# 创建虚拟环境并安装依赖 python -m venv mapf_env source mapf_env/bin/activate # Windows 用 mapf_env\Scripts\activate pip install numpy matplotlib这里只装两个包是有意为之。很多教程一上来就让你装一堆强化学习框架结果环境没配好就卡住了。MAPF 的经典算法CBS、 prioritized planning用标准库加 NumPy 就能实现先把算法逻辑跑通再考虑上多智能体强化学习MARL做端到端策略。2.2 栅格地图的数据结构设计地图建模决定了后面冲突检测怎么写。常见做法是用二维数组表示栅格0 表示可通行1 表示障碍物。但多智能体场景下还需要记录每个时间步各智能体的位置所以我会额外维护一个「时空占用表」。import numpy as np class GridMap: def __init__(self, width, height, obstaclesNone): # 0 可通行1 障碍 self.grid np.zeros((height, width), dtypenp.int8) if obstacles: for x, y in obstacles: self.grid[y][x] 1 self.width width self.height height def is_free(self, x, y): # 边界检查 障碍检查 if x 0 or y 0 or x self.width or y self.height: return False return self.grid[y][x] 0 def neighbors(self, x, y): # 四邻域需要八邻域可自行扩展 for dx, dy in ((1,0),(-1,0),(0,1),(0,-1)): nx, ny x dx, y dy if self.is_free(nx, ny): yield nx, ny这段代码的关键在neighbors方法它只返回可通行的邻居把边界判断和障碍判断收在一起。参数上width和height决定地图规模obstacles是障碍坐标列表。实际项目里地图可能来自 ROS 的 occupancy grid 或图片二值化转换时注意坐标系方向——图像的行对应 y列对应 x搞反了路径会整体偏移这是血泪经验。2.3 单智能体 A* 作为基线多智能体算法通常以单机 A* 为基础先让每个智能体独立算一条最短路径再处理冲突。所以先把 A* 写对。import heapq def astar(grid_map, start, goal): open_set [(0, start)] came_from {} g_score {start: 0} while open_set: _, current heapq.heappop(open_set) if current goal: # 回溯路径 path [current] while current in came_from: current came_from[current] path.append(current) return path[::-1] for nxt in grid_map.neighbors(*current): tentative g_score[current] 1 if nxt not in g_score or tentative g_score[nxt]: g_score[nxt] tentative # 曼哈顿距离启发式 h abs(nxt[0]-goal[0]) abs(nxt[1]-goal[1]) heapq.heappush(open_set, (tentative h, nxt)) came_from[nxt] current return None # 无解启发式用曼哈顿距离因为四邻域移动每步代价为 1满足可采纳性。如果改成八邻域启发式要换成对角距离否则 A* 不再最优。g_score记录起点到当前点的实际代价came_from用于回溯。这个函数返回的是从起点到终点的坐标列表后面多智能体冲突检测就基于这些路径。3. 多智能体冲突消解优先级规划与 CBS 的实现细节3.1 优先级规划最快能跑通的多机方案优先级规划Prioritized Planning的思路很直接给智能体排个序按顺序逐个规划路径后规划的智能体把先规划智能体的路径当作动态障碍。它的优点是实现简单、速度快缺点是可能因为顺序不好导致无解——明明存在可行解但某个智能体被前面的路径堵死了。def prioritized_planning(grid_map, agents): # agents: [(start, goal), ...] # 按路径长度或编号排序这里简单按编号 reserved {} # (x, y, t) - agent_id paths [] for i, (start, goal) in enumerate(agents): path astar_with_reservation(grid_map, start, goal, reserved, i) if path is None: return None # 当前顺序无解 for t, pos in enumerate(path): reserved[(pos[0], pos[1], t)] i paths.append(path) return pathsreserved字典记录每个时空点被哪个智能体占用。astar_with_reservation在扩展邻居时除了检查静态障碍还要检查(nx, ny, t1)是否已被占用以及是否存在对穿冲突——即智能体 A 从 (x1,y1) 到 (x2,y2)同时智能体 B 从 (x2,y2) 到 (x1,y1)。对穿冲突在多智能体里非常隐蔽只检查位置占用会漏掉导致两车在走廊里「擦肩而过」时相撞。3.2 冲突类型与检测逻辑多智能体路径的冲突分三类顶点冲突同一时刻同一位置、边冲突同一时刻交换位置、跟随冲突后车追尾前车通常通过保持安全距离避免。检测时要把所有路径按时间步展开。def detect_conflicts(paths): conflicts [] max_t max(len(p) for p in paths) for t in range(max_t): occupied {} for i, path in enumerate(paths): if t len(path): pos path[t] if pos in occupied: conflicts.append((vertex, t, occupied[pos], i, pos)) occupied[pos] i # 边冲突检测 for i, path in enumerate(paths): if t 1 len(path): for j, path2 in enumerate(paths): if i j or t 1 len(path2): continue if path[t] path2[t1] and path[t1] path2[t]: conflicts.append((edge, t, i, j, path[t])) return conflicts顶点冲突检测用occupied字典边冲突用双重循环比对。实际跑的时候边冲突检测是 O(n²) 每时间步智能体数量超过 20 个就要考虑优化比如只检查空间上邻近的智能体对。参数上max_t取最长路径长度短路径的智能体到达目标后可以视为「消失」不再占用格子这一点在仓储场景里很关键——AGV 到站后要释放通道。3.3 CBS用冲突树换最优解CBSConflict-Based Search是 MAPF 的经典最优算法。它分两层底层给每个智能体单独规划满足约束的最短路径顶层维护一棵约束树每次挑一个冲突生成两个分支分别给冲突双方加约束直到所有路径无冲突。def cbs(grid_map, agents): import heapq root_constraints {i: [] for i in range(len(agents))} root_paths [astar_constrained(grid_map, s, g, root_constraints[i]) for i, (s, g) in enumerate(agents)] if any(p is None for p in root_paths): return None root_cost sum(len(p) for p in root_paths) open_list [(root_cost, 0, root_constraints, root_paths)] counter 1 while open_list: cost, _, constraints, paths heapq.heappop(open_list) conflicts detect_conflicts(paths) if not conflicts: return paths ctype, t, i, j, pos conflicts[0] for agent in (i, j): new_cons {k: list(v) for k, v in constraints.items()} if ctype vertex: new_cons[agent].append((vertex, pos, t)) else: new_cons[agent].append((edge, pos, paths[agent][t1], t)) new_path astar_constrained(grid_map, agents[agent][0], agents[agent][1], new_cons[agent]) if new_path is None: continue new_paths list(paths) new_paths[agent] new_path new_cost sum(len(p) for p in new_paths) heapq.heappush(open_list, (new_cost, counter, new_cons, new_paths)) counter 1 return Noneastar_constrained在 A* 基础上增加约束检查如果当前扩展的(nx, ny, t)命中该智能体的顶点约束或边约束匹配就跳过。CBS 的优点是保证最优缺点是智能体多、冲突多时约束树爆炸式增长。我一般设一个节点扩展上限超过就退回优先级规划这是工程上的折中。4. 避坑与排查多智能体路径规划最容易翻车的五个地方4.1 现象所有智能体都规划成功但仿真里还是撞了原因通常是只做了顶点冲突检测漏了边冲突。两个智能体在相邻格对穿位置序列上没有任何时刻重合但实际运动中会相撞。解决方法是把边冲突检测加进detect_conflicts并在 A* 扩展时检查(current, nxt)是否与已占用边冲突。4.2 现象优先级规划偶尔无解换个顺序就好了这是优先级规划的固有缺陷不是代码 bug。原因是先规划的智能体路径把后规划智能体的唯一通道堵死了。解决办法有两个一是按「路径长度降序」或「冲突数降序」排序让难规划的智能体先走二是多试几种顺序取第一个成功的。如果场景固定可以离线跑一遍找出好顺序缓存起来。4.3 现象CBS 跑小地图很快地图一大就卡死CBS 的约束树规模随冲突数指数增长。地图大、智能体多时顶层节点数会爆炸。解决方法是设扩展上限或者改用 ECBSEnhanced CBS做次优但有界搜索。另一个常被忽略的点是底层 A* 重复计算太多可以加缓存相同约束集下的路径结果存起来避免重复搜索。4.4 现象智能体到达目标后堵住通道后面的车过不去这是目标点占用问题。很多实现里智能体到达目标后就从路径里消失但物理上它还停在那里。解决方法是把目标点也加入长期占用表或者给每个智能体规划一个「停车位」到达后驶离主通道。仓储场景里通常让 AGV 到站后下沉或移出通道仿真里则把到达后的智能体视为永久障碍。4.5 现象路径长度差不多但实际运行时间差很多多智能体路径规划优化的是路径长度或 makespan最后到达时间但实际执行时还有加减速、转向耗时。如果只按格子数算代价可能出现「路径短但转弯多」的方案实际跑起来更慢。解决方法是把转向代价加进 A* 的g_score直行代价 1转弯代价 1.2 或 1.5具体值根据机器人运动学标定。5. 从仿真到落地验证指标与进阶调参技巧算法跑通只是第一步要判断一个多智能体路径规划方案能不能用得看几个硬指标。我一般会统计成功率有解比例、makespan最后到达时间、总路径长度、冲突检测耗时、以及单次规划耗时。下面这张表是我在 20x20 栅格、10 个智能体场景下的经验参考值不同地图会有波动但量级可以参考。指标优先级规划CBS说明成功率85% 左右接近 100%优先级规划受排序影响makespan较长较短CBS 优化全局单次规划耗时毫秒级秒级到分钟级智能体越多差距越大实现复杂度低中高CBS 需要约束树验证时不要只看「有没有解」还要看解的稳定性。同一个场景跑十次如果优先级规划的成功率波动很大说明排序策略不够鲁棒。我习惯把排序策略做成可配置的按路径长度、冲突数、智能体编号分别试取统计上最好的那个。进阶调参上有几个技巧值得试。第一A* 的启发式权重可以稍微放大比如h * 1.1牺牲一点最优性换搜索速度在多智能体场景里往往划算。第二冲突检测可以只检查时间窗口内的冲突比如只检查未来 20 个时间步因为远处的冲突可能被后续约束消掉。第三如果场景是动态的障碍物会动可以把 MAPF 和 DWA 结合MAPF 出全局路径DWA 做局部避障这样既有全局最优性又有实时反应能力。# 带转向代价的 A* 代价计算示例 def move_cost(prev_pos, current, nxt): # 直行代价 1转弯代价 1.3 if prev_pos is None: return 1.0 dx1, dy1 current[0]-prev_pos[0], current[1]-prev_pos[1] dx2, dy2 nxt[0]-current[0], nxt[1]-current[1] if (dx1, dy1) (dx2, dy2): return 1.0 return 1.3这个代价函数要嵌进 A* 的g_score更新里came_from回溯时也要能拿到前一个位置。转向代价的系数不是拍脑袋定的最好用实际机器人的转向耗时除以直行一格耗时来标定。我踩过的坑是系数设太大导致路径绕远路也不转弯反而增加了总长度后来固定在 1.2 到 1.5 之间比较稳。最后说一个习惯每次改完算法先在小地图10x10、3 个智能体上跑通再逐步加地图和智能体数量。多智能体路径规划的问题往往在规模上来之后才暴露小场景能过不代表大场景能过。希望帮到你。本文还有配套的精品资源点击获取
返回列表