CBS算法详解:多AGV路径规划的冲突消解利器
2026/9/16 21:33:31 网站建设 项目流程

简介:一份基于CBS算法实现的多AGV路径规划仿真系统,面向多AGV物流分拣场景的路径规划与避障问题,包含完整源代码、项目开发说明和演示程序,适合计算机、人工智能等专业的毕设参考或课程设计。算法核心以JavaScript实现,另有Python辅助脚本与可视化页面,资源共34个文件,涵盖js、png、py、css、docx、html等类型,整体约10.77MB,结构清晰便于按模块学习。该项目为作者本科毕业设计,代码经测试运行成功,答辩平均分96分,目前已有237人学习。使用者可在已有地图配置、起点终点及障碍设定基础上,直接查看算法求解过程,也可结合开发文档进行二次功能扩展,是理解多智能体路径规划与CBS冲突消解机制的较好样例。

1. 从“谁先走”到“怎么走”:CBS把多AGV决策拆成了两层

多AGV路径规划里有一个很容易被忽视的事实:单机A*跑得再快,只要两辆车在交叉口相遇,就要有一个“谁让谁”的裁决。传统做法是加优先级、加时间窗,或者让AGV停下来等待,但这些方法都在同一层解决所有问题,冲突一多,状态空间迅速膨胀。CBS(Conflict-Based Search)换了一个思路:先假设所有AGV各自按最短路径走,不管别的车;一旦发现冲突,不为整条路径重新规划,而是只对冲突涉及的这辆车加一条约束,再让它单独重算。路径规划是一层,冲突消解是另一层,两层交替迭代,直到所有路径都相容。

这篇内容面向的是要做AGV调度、多机器人路径规划的开发者,也适合把CBS作为毕业设计核心算法来写代码的同学。标题里那套“系统源代码+项目开发说明+演示程序”是典型的教学工程包,但源码能跑只是起点,真正值钱的是理解CBS的约束树怎么建、冲突怎么检测、迭代什么时候收敛。下面按我平时搭这套系统的顺序来讲,从算法骨架到参数调节,最后给一套能直接用在自己代码里的验证方法。

2. CBS在两辆车相向而行时真正改了什么

2.1 CBS的核心机制:冲突就是搜索的状态

先看一个最小冲突场景:AGV_1要从A到B,AGV_2要从B到A,路径都是直线。两辆车在某个时间点必然占用同一个格子,这就是顶点冲突(vertex conflict),记作(agv1, agv2, v, t),意思是两辆车在t时刻都想进格子v。还有一种是边冲突(edge conflict),即(agv1, agv2, u, v, t),表示agv1在t时刻从u走到v,agv2在t+1时刻从v走到u,两车在一条边上对向占道。

CBS把这两种冲突当作搜索的“状态”,而不是把整条路径当作状态。高层的约束树(Constraint Tree,CT)里每个节点包含三样东西:一组约束、一组路径、一个总代价。初始节点没有任何约束,各AGV独立跑A*得到最短路径。对初始节点的路径做一次冲突检测,如果一条冲突都没有,这个节点就是最终解;如果有冲突,就分裂出两个子节点,分别给其中一辆车加约束。

约束只有三种形式:

  • 顶点约束:agv在t时刻不能进格子v
  • 边约束:agv在t时刻不能从u走到v
  • 起终点约束:agv的起点或终点被其他车占用时,直接让另一辆车绕行

分裂出来的两个子节点都保留父节点的全部约束,再各自加一条新约束。子节点重新计算受影响AGV的路径,然后继续检测冲突。这个过程反复进行,直到某个叶子节点的所有路径无冲突。搜索策略一般用代价优先队列,总代价低(所有AGV路径长度之和最小)的节点先扩展。

2.2 为什么不是直接在A*里加“避让条件”

很多人在实现多AGV时第一反应是改A的代价函数:对面有车就加惩罚项,或者把占用格子在代价里标成不可通过。这在小地图、低密度场景里确实能跑通,但有一个深层次问题:A的每个状态是“某一辆车在某一时刻的位置”,它根本不知道其他车未来的位置。

举个例子:两辆AGV在一个仓库的十字通道里,A车从南向北,B车从西向东,交汇点是格子(5,5)。如果A先到,B用A*避开(5,5),代价是绕行两格,总路径变长。但CBS的做法是:A和B都先按最短路径算,发现两车都想在某一时刻进(5,5),从而加一条约束“B在t时刻不能进(5,5)”,B重新规划后可能整体只多走一格,而且还能保证后续不再产生新冲突。

更关键的是,改A代价函数只能处理“当前时刻”的冲突,处理不了跨时间的边冲突:A车在t时刻占用了通道,B车在t+2时刻也要走这条通道,这时候按A的静态代价根本算不出来。CBS靠约束传递时间信息,本质上是把“相互避让”变成了“按时间片分配空间资源”,这才是它适合多AGV的根本原因。

2.2.1 CBS的完整迭代流程

整个CBS可以浓缩成下面的伪代码,这也是我实现时的骨架:

function CBS(agents, map): root.constraints = empty root.paths = [] for each agent in agents: root.paths[agent] = AStar(map, agent.start, agent.goal, root.constraints) root.cost = sum(path.length for path in root.paths) open_set = priority_queue([root], key=cost) while open_set not empty: node = pop_lowest_cost(open_set) conflict = first_conflict(node.paths) if conflict is None: return node.paths for agent in [conflict.a1, conflict.a2]: new_node = copy(node) new_node.constraints.add(constraint(agent, conflict)) new_node.paths[agent] = AStar(map, agent.start, agent.goal, new_node.constraints) new_node.cost = sum(path.length for path in new_node.paths) if new_node.paths[agent] is not None: open_set.push(new_node) return failure

这段代码做的事是:优先扩展总代价最小的节点;每次扩展前检测优先级最高的冲突;一旦发现冲突就为涉及的两个AGV分别生成子节点。有个细节容易被忽略:子节点只重算冲突涉及的AGV路径,其他车保持不动。如果两辆车都没有可行路径(AStar返回None),说明这个节点不可行,直接丢弃。

参数上,优先级队列的键是用总路径长度,这个值在分支时不会比父节点更小,所以CBS是代价最优的(前提是底层A*也最优)。我一般在实现时还会给first_conflict加一个技巧:冲突按时间排序,取t最小的那个,这样能让搜索树往“早期冲突”方向收敛,减少无效扩展。

2.3 CBS和优先级规划、A*组合方案的边界

业界做多AGV还有两条常见路线:优先级规划(Prioritized Planning)把AGV按顺序一个个规划,后规划的AGV把先规划的路径当成障碍;还有一条路线是把所有AGV拼成一个大状态做联合A*。优先级规划的缺点是顺序敏感,A车先走可能把B车逼进死胡同,最后不得不回溯顺序;联合A*确实最优,但状态空间是所有AGV位置的笛卡尔积,4辆车在30×30地图上就可能有接近百万级别的状态,根本跑不动。

CBS正好卡在中间。它用一个高层搜索来协调冲突,底层还是单车A*,状态空间不会爆炸。但要注意,CBS在拥挤场景里CT节点会指数增长,如果两辆车的路径频繁互相干扰,每层冲突都会分裂出两个节点,搜索树很快就大了。所以业界常给CBS加“旁路”优化,比如在子节点重规划失败时,不马上丢弃,而是把该AGV路径置为“等待后再出发”,用等待时间换空间,这在真实AGV调度系统里是常规操作。

3. 多AGV仿真系统的骨架:地图、任务与主循环

3.1 地图和AGV的建模方式

仿真系统里我一般用栅格地图,AGV只做上下左右四方向运动,不走斜线。原因是四方向移动的A*实现简单,冲突检测规则也容易定义;如果要用八方向,CBS的约束就得多考虑对角穿越的边冲突,复杂度上升不少。地图用一个二维数组表示,0是空地,1是货架或墙壁。

AGV的物理属性至少要包含这几个字段:

  • 位置:当前所在格子坐标
  • 目标:当前任务的终点格子坐标
  • 速度:单位时间走几格,默认1
  • 状态:空闲、运行、等待、充电

一个常见误区是给AGV加“加速度”和“转弯半径”。物理上这更真实,但对CBS算法本身是干扰因素,因为底层A假设AGV每步都能立即到达相邻格,加速度会让路径长度变成时间相关,A不再是最优的。我的建议是:算法层先做离散运动,仿真展示层再加速度曲线,两者解耦。

3.2 多AGV仿真系统的地图与起点终点设定

下面是一个可以直接跑的最小地图定义和任务分配代码,我用Python写,方便演示:

# 地图:0可通行,1为货架/墙 MAP = [ [0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 1, 0, 1, 0], [0, 1, 0, 0, 0, 1, 0], [0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 1, 0, 1, 0], [0, 0, 0, 0, 0, 0, 0], ] # AGV任务:起点、终点 TASKS = [ {"start": (0, 0), "goal": (5, 6)}, {"start": (5, 0), "goal": (0, 6)}, {"start": (0, 6), "goal": (5, 0)}, ] CONFLICT_PENALTY = 10 # 冲突等待阈值,单位:时间步

这段代码做的事是:定义了一张7×6的地图和3个AGV任务。CONFLICT_PENALTY是后面调参用的关键参数,表示AGV遇到冲突时能接受的等待时间上限,超过这个时间就触发重规划。我在地图里故意放置了多个1(货架),让通道变窄,这样更容易制造冲突,方便观察CBS的行为。

参数设定上有几个讲究。地图尺寸和AGV数量的比例非常关键,6×7地图放3辆车已经比较挤了,如果放到6辆车,基本每一步都有冲突,CBS的CT树会疯狂分裂。我在实际测试时一般固定地图大小,逐步增加AGV数量,记录“从无冲突到有冲突”的临界数量,这才是评估CBS适用性的正确方式。

3.3 仿真主循环:让CBS算整体路径,再逐帧播放

仿真系统的职责是“算一段,走一段”,不能每走一步都重新规划,那样效率太低。常见做法是:每次有新任务时跑一次CBS,得到所有AGV的完整路径,然后仿真时钟按步推进,AGV按路径移动。如果某辆AGV因为机械故障或障碍物停下,才会触发局部重规划。

# 主循环逻辑伪代码 for t in range(max_steps): for agv in agvs: if agv.has_task and agv.position == agv.next_move: agv.move_one_step() # 沿CBS给出的路径走一步 elif agv.is_idle and task_queue: agv.assign_task(task_queue.pop()) paths = cbs_solve(all_agvs, MAP) agv.set_path(paths[agv.id]) # 状态刷新、渲染、日志记录

这里有一个很微妙的点:CBS解出来的路径是“哪个时刻在哪个格子”,所以仿真器里要维护一个全局时钟,AGV每一步都必须严格对齐这个时钟。如果一辆AGV因为某些原因晚了一步,后续所有时间约束都会乱掉,所以我在仿真器里加了“等待补偿”机制,允许AGV在路径上停留一个时间步,但一旦停留超过阈值,就触发整轮CBS重规划。

4. 把慢因素拆开:A*、约束树与重规划参数

4.1 底层A*的代价函数和约束传入方式

CBS的底层A和普通A有一点关键区别:它要接收“约束”作为输入,在扩展节点时跳过违反约束的移动。下面这段代码展示了约束如何影响A*搜索:

def astar_with_constraints(start, goal, map_grid, constraints): open_set = [(0, start)] came_from = {} g_score = {start: 0} while open_set: _, current = heapq.heappop(open_set) if current == goal: return reconstruct_path(came_from, current) for next_pos in neighbors(current, map_grid): # 约束检测:当前时间步不能进入next_pos time_step = g_score[current] + 1 if (next_pos, time_step) in constraints: continue tentative_g = g_score[current] + 1 if tentative_g < g_score.get(next_pos, float('inf')): came_from[next_pos] = current g_score[next_pos] = tentative_g heapq.heappush(open_set, (tentative_g + heuristic(next_pos, goal), next_pos)) return None

这段代码做的事是:在普通A*的基础上增加了一个约束集合判断,(next_pos, time_step)在约束里就跳过。这里有个很容易写错的地方:time_step是根据起点到当前节点的实际步数推算的,不是用启发式预估的值,否则约束会应用到错误的时刻。

约束的传入方式有两种实现。一种是在CBS生成子节点时,把该节点所有约束打包成列表传给astar_with_constraints;另一种是把约束放到closed_set里,但这样会污染A*的剪枝逻辑,我不推荐。真正需要留意的是约束里的时间:CBS生成约束时用的是冲突发生的时刻,但AGV在重规划后到达同一格子的时间可能变了,所以约束要绑定“时间步”,而不是绑定位

本文还有配套的精品资源,点击获取

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

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

立即咨询