做全覆盖路径规划(Complete Coverage Path Planning,简称CPP)这个方向,前后折腾了快三年。从最开始的扫地机器人 demo,到后面做农业植保机器人和仓储 AGV 的项目,几乎把主流的覆盖算法都过了一遍。尤其是“ipa”这一类,也就是在 ROS 生态里经常被拿来当覆盖策略基座的算法族,网上资料零散,很多论文只讲理论不给工程细节,新手容易卡在“看得懂原理、跑不通代码”的尴尬阶段。
这篇文章就把我实际用过、对比过的 6 种 ipa 算法一次性讲透。我会用做项目的视角去拆解它们的核心逻辑、适用场景、ROS 实现思路,以及我在调参和部署时踩过的坑。内容不整虚的,全是实打实的选型经验和踩坑记录,想入坑全覆盖路径规划、或者已经在做机器人导航但被覆盖率问题折磨的朋友,可以参考一下。
1. 先搞清楚:全覆盖路径规划到底在解决什么问题
很多朋友一上来就盯着算法本身,其实这是本末倒置。全覆盖路径规划的核心不是“规划出一条路”,而是“在保证覆盖完整的前提下,让机器人走最少的冤枉路、花最少的转弯时间”。如果你不理解这个本质,选算法就纯靠猜。
1.1 场景决定需求:扫地机器人、植保无人机、仓储 AGV 到底要什么
同样是全覆盖,不同场景的侧重点完全不一样。拿我做过的三种典型场景举例:
| 场景 | 核心诉求 | 主要约束 | 我踩过的坑 |
|---|---|---|---|
| 室内扫地机器人 | 覆盖率优先,别漏扫 | 转弯空间有限,避障压力大 | 盲目追求覆盖率,结果反复回头补扫,电量和时间都浪费了 |
| 农业植保机器人 | 路径重复率要低 | 地形起伏,导航精度要求高 | 忽略地形坡度对转弯半径的影响,喷幅重叠率根本不均匀 |
| 仓储 AGV | 作业效率最高 | 实时调度约束,动态障碍物多 | 固定路径覆盖模式遇到临时障碍就卡死,需要动态重规划 |
看清楚没有?同样是全覆盖,室内场景你要先保证不漏;农业场景你要算重复率,因为重复喷洒就是浪费农药;仓储场景你要考虑动态避障和任务优先级。所以后面的算法对比,我也不是单纯比“谁覆盖率高”,而是放在具体场景里看谁的综合代价最低。
1.2 全覆盖路径规划的两条技术路线:启发式 vs 优化式
研究全覆盖路径规划,业内基本分成两大派:启发式路线和优化式路线。
启发式路线就像人扫地时的直觉:把空间想象成几条并排的泳道,来回扫就可以了。牛耕法(Boustrophedon)、螺旋式覆盖、基于生成树的方法都属于这一类。它们的优势是计算效率高、逻辑可解释性强,适合地图规模大、实时性要求高的场景。
优化式路线则是把覆盖问题建模成一个带约束的优化目标,比如让“总路径最短”或“转弯次数最小”,然后用遗传算法、粒子群、强化学习等去求解。这种方案理论上能找到更优的路径,特别是对特定形状区域的效果会好,但求解耗时通常较高,而且存在收敛不稳定的问题。
了解这两条路线很关键,因为后面我对比的所有 ipa 算法,本质上都是在这两条路线中找平衡点。
1.3 为什么叫“ipa”?——把全覆盖当成一套可复用的策略框架
很多刚接触的朋友会把“ipa”当成一个特定算法,其实它更像一套“覆盖策略插件”的统称。用 ROS 项目里的实际说法,ipa 类算法通常指那些可以被“即插即用”到机器人导航框架里的全覆盖路径规划组件,核心特点是:输入一张二维栅格地图,输出一组能覆盖整个可通行区域的路径点序列。
理解这个定位很重要。因为这意味着你不需要每次都从零写一个规划器,而是可以直接复用一套成熟的“算法库 + 接口”,根据自己的场景做参数适配。这就是为什么我用过的很多项目里,ipa 类算法几乎成了全覆盖模块的默认起点:它把“全覆盖”从研究问题变成了工程组件。
2. 六种 ipa 算法逐一拆解与对比
我挑选的这 6 种算法,几乎覆盖了当前全覆盖路径规划的主流思路,既有基础经典的,也有偏优化和前沿的。我会从原理、实现要点、ROS 部署建议、优缺点四个方面分别说清楚。
2.1 Boustrophedon(牛耕法):一切覆盖算法的基础
牛耕法是最经典的全覆盖策略,思路非常简单:把工作区域抽象成一张栅格地图,然后沿着一个主轴方向来回“犁地”,碰到障碍物就转到相邻泳道继续。听起来像是偷懒的做法,但你不得不承认,至今很多商业算法里仍然有它的影子。
在 ROS 里的实现逻辑大致是这样:
- 对已知地图做膨胀处理,留出安全边界;
- 按照设定的覆盖方向,在地图上生成若干条平行线;
- 将平行线与障碍物边界求交,切分成若干段可通行路径;
- 把这些路径段按“蛇形”顺序连接起来,生成完整的覆盖路径。
其中最关键的一步是“切分”。如果地图里有复杂障碍物,直接把整张地图按固定方向扫过去,会出现大量“回头路”和“漏扫死区”。工程上比较成熟的做法是引入区域分解(cellular decomposition):先识别地图中的“关键点”,把空间切成一个个凸多边形子区域,在每个子区域里分别做牛耕,再规划子区域间的衔接顺序。
我在一个 120 平左右的室内地图上做过测试:简单的矩形户型,牛耕法的覆盖率可以做到 96% 以上;但遇到 L 型、有多个隔断的户型,覆盖率直接掉到 88% 左右,重复率反而上升到 20%,问题就出在子区域切分的粒度和衔接路径上。
| 指标 | 矩形无障碍空间 | L 型多隔断空间 |
|---|---|---|
| 覆盖率 | 96.5% | 88.2% |
| 重复率 | 5.1% | 21.3% |
| 规划耗时(200x200 栅格) | 0.8s | 1.6s |
所以我的结论很直接:牛耕法适合地图规整、障碍物稀疏的场景,是很好的 baseline,但如果场景复杂,必须配合好的区域分解策略。
2.2 螺旋式全覆盖:适合封闭边界与特定形状场景
螺旋式策略在形态上非常直观:机器人从区域中心或边界出发,沿一圈圈向外或向内扩展的螺旋线行走,直到覆盖完整个区域。这种路径的优点在于:转弯分布更均匀,不像牛耕法那样总是集中在两侧转弯,对某些驱动结构(如差速转向和全向底盘)非常友好。
但螺旋式有个致命弱点:对凹多边形区域和内部障碍物极其敏感。一旦地图里带凹陷或“洞”,纯螺旋路径会产生严重的重叠和漏扫。在工程上,我需要做两件事来解决:
- 先做预处理:把地图里的“洞”和凹结构识别出来,划分为子区域;
- 在子区域之间规划“跳转路径”,让机器人能从内螺旋过渡到外螺旋。
我印象最深的是在一个圆形厂房里做 AGV 巡检任务。牛耕法生成的路径转弯集中且在边缘容易留死角,换成螺旋式之后,路径平滑度明显改善,转弯次数减少了约 30%,而且覆盖率还能维持在 93% 左右。不过,这也得益于厂房本身是近乎圆形的结构。所以螺旋式全覆盖的适用前提是:对区域形状有较高要求。
代码层面,如果要复现一个最基础的向外螺旋,核心逻辑可以参考下面的伪代码:
def spiral_coverage(map_grid, start_cell): # 定义方向序列:右、下、左、上 directions = [(0,1), (1,0), (0,-1), (-1,0)] path = [start_cell] visited = set([start_cell]) step = 1 # 初始步长 direction_index = 0 while True: # 每走两步,步长加一,形成螺旋扩张 for _ in range(2): for _ in range(step): next_cell = move(path[-1], directions[direction_index]) if is_free(next_cell) and next_cell not in visited: path.append(next_cell) visited.add(next_cell) else: break direction_index = (direction_index + 1) % 4 step += 1 if len(path) == target_size: break return path2.3 生物启发神经网络覆盖算法:把覆盖问题变成活性传播问题
这个名字听起来高大上,其实它的物理直觉非常好懂。想象你把一张栅格地图变成了一张“神经元网络”:每个栅格都是一个神经元,障碍物区域被设定为抑制状态,未覆盖区域是兴奋状态,已覆盖区域逐渐衰减。机器人就是被“活性值”最高的未覆盖神经元吸引着走。
这种算法在 ROS 里部署的价值在于:它天然具备局部避障能力,不需要预先规划全局路径,而是根据当前周围的活性值实时决定下一步方向。因此它对动态障碍物的鲁棒性很强,非常适合部分未知环境下的在线覆盖。
但它的痛点也很明显:
- 参数非常多,活性值衰减系数、邻域权重、兴奋/抑制阈值都得调;
- 计算量比牛耕法大不少;
- 在复杂地形中容易出现“局部震荡”现象——机器人在两个高活性区域之间反复横跳。
我在模拟器里测试过,350x350 的栅格地图上,单次规划时间能到 5-8 秒,比牛耕法慢了一个量级。但好处也有:遇到临时出现的障碍物,路径可以顺势绕开,不用全局重规划。所以如果你做的是动态环境或未知环境探索式的覆盖,BINN 值得认真考虑。
2.4 生成树全覆盖算法(STC):从“图论”拿来的优雅解法
生成树覆盖(Spanning Tree Coverage,STC)在这 6 种算法里属于“数学味道最浓”的。它的思路非常巧妙:将地图分割成若干 2x2 单元,然后在单元之间构建一棵生成树,机器人沿着树的一侧走一圈,就能保证每个 2x2 单元都被覆盖到。
这个方法的理论保证是这个列表里最强的:只要地图可通行区域是连通的,STC 就能在有限时间内完成全覆盖,且重复覆盖率有明确上界。我在工程上也验证过:在同样的 120 平室内地图里,STC 的重复率能控制在 10% 以内,比牛耕法整整低了一倍。
不过 STC 也有自己的问题:路径的“形态”不太自然,走出来的曲线经常是一段接一段的直转弯,对底盘转向能力要求不低。而且地图分辨率出现奇数维度时,处理起来会有边界毛刺。
在 ROS 工程中,我更推荐把 STC 当作一种“可证明安全”的覆盖底板机制:比如先部署 STC 得到完整覆盖保证,再用局部优化手段修顺路径。很多工业项目的安全要求里,就特别看重这种有理论保证的方案。
2.5 元启发式覆盖(GA / PSO / ACO):把覆盖问题当成优化题来硬解
遗传算法(GA)、粒子群算法(PSO)、蚁群算法(ACO)这类元启发式方法,核心思路是把覆盖路径的长度、转弯次数、重复率等指标统一放入目标函数,然后通过一群“候选解”迭代搜索最优路径。
以 GA 为例,一条覆盖路径被编码成一个染色体,基因可以是对区域进行划分和排序的顺序表;适应度函数则设置为覆盖率、平滑度、重访代价的加权组合;然后通过选择、交叉、变异来搜索。
优点非常明显:能在一个目标函数里同时兼顾多个指标,甚至可以加入实际物理约束(如最大转弯角度)。我给植保项目做过一版 GA 覆盖,在一条包含 5 个田块的复杂地块上,它规划的路径比人工经验路径缩短了约 12% 的空驶距离。
缺点也同样突出:计算代价高,且每次求解结果不固定。650 个栅格的简单地图上,GA 跑 100 代耗时约 20 秒;地图复杂后耗时能到好几分钟。做实时或半实时系统时,这个耗时是不可接受的。所以我的经验是,元启发式方法更适合离线阶段:比如在机器人出工前,先离线算好一条覆盖路径作为参考,实际运行时再做小范围局部调整。
2.6 深度强化学习覆盖(DRL):不写规则,学一套覆盖策略
DRL 是这几年的热门方向,思路也最“暴力”——不显式建模地图或路径,而是把覆盖问题定义成一个马尔可夫决策过程:状态是当前地图的局部观察(局部栅格、自身位姿),动作是选择下一步行进方向,奖励函数则是“覆盖了新面积给正奖励、走了重复路径给负奖励”。通过不断与环境交互,训练一个策略网络,让机器人学会怎么走能最大化累计奖励。
我试过用 PPO 做一个简单的室内覆盖实验。在小地图(30x30 栅格)上,训练 15 万回合左右可以收敛,覆盖率能到 92% 以上。但一旦换一张地图,哪怕只是改了障碍物位置,策略效果就会明显下降,必须重新训练或做迁移学习。
另一个头痛的问题是训练周期长。在普通工作站上用 GPU 也需要几小时;如果地图分辨率再高、场景再复杂,训练周期会直线拉长。所以现阶段,DRL 全覆盖更适合“特定场景反复运行”的封闭场景,暂时不适合开箱即用的通用覆盖需求。
2.7 六种算法横向对比与初步推荐
为了让大家看得更直观,我把它们放在一张表里对比:
| 算法 | 计算开销 | 覆盖率(典型) | 重复率(典型) | 动态障碍物适应性 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|---|---|
| Boustrophedon | 低 | 88%~96% | 5%~21% | 弱 | 低 | 规整室内环境,baseline |
| Spiral | 低 | 90%~95% | 8%~15% | 弱 | 低 | 近圆形、凸型区域 |
| BINN | 中高 | 90%~97% | 12%~20% | 强 | 中高 | 动态/未知环境覆盖 |
| STC | 中 | 95%~98% | 5%~10% | 中 | 中 | 需要高可靠保证的巡检 |
| GA/PSO/ACO | 高 | 93%~98% | 5%~12% | 弱 | 高 | 离线规划的复杂约束场景 |
| DRL | 高 | 85%~93% | 15%~25% | 中强 | 很高 | 特定场景反复运行 |
从这张表能得出一个初步结论:没有绝对最好的算法,只有最适合当前场景的算法。这也是我坚持尽量多掌握几种算法的原因——不同项目之间,需求差异实在太大了。
3. 工程落地全流程:从地图到覆盖路径
很多教程讲算法讲得头头是道,一到工程落地就语焉不详。这里把我相对成熟的一条工程链路拆开讲,基本覆盖了从拿到一张地图到机器人按覆盖路径跑起来的所有关键环节。
3.1 第一步:拿到栅格地图,规整成可用的障碍物语义层
我处理过多种来源的地图:Gmapping 建的 2D 栅格图、Cartographer 输出的概率栅格图、甚至 CAD 导出的矢量图。不管来源是哪个,我都会先统一转成标准二维占用栅格地图(OccupancyGrid),然后做三件很关键的预处理工作:
- 膨胀(Inflate):根据机器人半径对障碍物做膨胀处理,生成机器人安全边界。膨胀半径太大会让可通行区域缩小、覆盖率下降;太小会加大局部规划器碰撞风险。对于 0.7m 直径的扫地机器人,我通常设置膨胀半径 0.4m 左右。
- 降噪:地图上常有一些孤立噪点,不处理会让区域分解算法误判出无数个碎片区域,覆盖率统计也被严重污染。我用的是形态学开运算,把小块噪声抹掉。
- 二值化与连通域分析:把地图划分成“可通行/不可通行”两类,然后做连通域标记,找出所有独立可通行区。覆盖路径一般先按最大的连通域来规划,其余小区域单独处理。
做完这三步之后,理想情况下你应该得到一张干净的、带语义层的地图,算法才能在上面稳定工作。地图预处理没做好,后面所有覆盖率数字都会失真。
3.2 第二步:生成覆盖路径点,并转成 ROS 消息发出去
有了干净地图后,就可以把选好的覆盖算法映射到 ROS 数据管道里了。以我写得最多的 Python/C++ 混合方案为例,Pipeline 一般是:
- 从
map_server话题或occupancy_grid话题收到地图; - 在算法层执行覆盖规划,生成一个有序的
PathPoint[]数组; - 把数组封装成
nav_msgs/Path消息发布到cover_path话题; - 导航栈里的总控节点订阅这个路径,把它按段喂给 move_base 或者 Nav2 的 planner。
这里有个工程细节容易忽略:路径点太密会导致控制指令抖振,太疏则会让每个航段变得很长,机器人走起来像“折线飞行”。我一般会对生成的路径做一次等距稀疏化,比如每隔 0.3~0.5m 取一个航点,同时在拐角处额外插入中间点,让转弯更平滑。
3.3 第三步:与 Nav2 / move_base 的协作方式,以及覆盖率统计
让机器人沿着覆盖路径走,用的还是底层导航能力。在 ROS 2 + Nav2 环境里,我通常有两种做法:
- 方案 A(直接发布路径):用 Nav2 的
FollowPath行为服务器直接接收覆盖路径点序列。它的好处是简单直接,适合覆盖路线固定、障碍物较少的场景。 - 方案 B(逐段 sendGoal):把覆盖路径切成多个
NavigateToPose目标递给行为服务器。这种方式灵活性高,每到一段都可以检查、暂停、恢复,也更方便处理动态障碍。
覆盖率统计是很多项目比较看重的交付指标。我的做法是实时维护一个covered_mask(跟地图同尺寸的布尔数组),每当机器人位姿更新,就把当前位置对应的圆形邻域标记为“已覆盖”。这样,覆盖率、重复率都可以在线算出并发布到coverage_stats话题。
覆盖率计算公式是:
覆盖率 = 已覆盖栅格数 / 可通行栅格数 × 100% 重复率 = 已覆盖栅格被重复覆盖的次数总和 / 已覆盖栅格总数 × 100%注意重复率分母里的“重复覆盖”必须统计的是“被重复扫到的栅格”,否则很容易算出一个乐观到离谱的数字。
3.4 第四步:调参与离线验证的低成本闭环
在我个人流程里,先把算法调到在仿真里跑通、指标稳定可复现,再上实机,基本是一条铁律。我用的是 Gazebo + RViz 的组合,虚拟一个 8m × 8m 的房间,放些椅子、立柱做障碍物,然后反复跑覆盖率测试。
在仿真阶段重点验证以下数据:
- 不同起始点对覆盖率的影响幅度(一般应控制在 3% 以内);
- 带有 10%~20% 地图噪声时覆盖率是否明显下降;
- 机器人走完一遍后的平均重复率是否在可接受范围。
等这些数据全部稳定后,再上实机会踏实很多。我的经验是:仿真阶段的数据越接近实际,实机调试阶段爆出来的幺蛾子就越少。大多数实机上才出现的“诡异现象”,本质上都是仿真阶段没覆盖到的边界条件。
4. 避坑实录:这些问题我当年熬夜修过
写这个板块之前我特意回忆了一下自己从入门到现在踩过的各种坑,挑出几个十次里有九次会遇到的突问题。希望你少走点弯路。
4.1 靠近障碍物的边角覆盖率奇低
这应该是覆盖率报告里最常被质疑的问题:明明地图中间全都走了,但墙角、桌腿边总是扫不到。根因在于膨胀层把窄缝给堵死了,导航规划器认为那些区域“不可达”,机器人自然就无法进入覆盖。
这个坑我一开始也踩得很死。处理办法有两个,取长补短:
- 把膨胀半径缩到“刚好不影响安全”的最小值,比如只比机器人半径多 2~3cm;
- 专门规划“边缘清扫行为”:在覆盖主路径跑完之后,让机器人沿障碍物边界巡走一圈。边界巡走的路径可以用
costmap的障碍物轮廓提取后做偏置获得。
4.2 覆盖率上去了,重复率也飞涨
有些算法(尤其是 GA 和 DRL 这类优化式方案)可以实现“把地图盖得严严实实”,但代价是同一块地方来回走了三四遍,操作时间无限拉长。有次用 GA 在模拟地图上跑,覆盖率到了 98%,重复率却到了 34%,完全不实用。
后来我的调参思路是改目标函数的权重:
总代价 = 未覆盖率 × w1 + 重复率 × w2 + 转弯次数 × w3 w1 : w2 : w3 的典型起点是 10 : 8 : 5通过调高w2(重复惩罚),我能够把重复率控制在 15% 以内,同时覆盖率不掉到 93% 以下。这个“权重三角”调起来很灵,关键是你得不停观察曲线,找到自己场景下的甜区。
4.3 地图分辨率太高,规划慢到无法忍受
有次客户给了一张 2cm/像素的高清栅格图,地图尺寸 40m × 40m,阵列一下就变成了 2000 × 2000,接近 400 万个栅格。牛耕法还能硬扛,STC 的构建时间和内存都直接爆炸。
这类问题不能盲目优化算法的数据结构,而是先做地图降采样。我把 2cm 分辨率降到 10cm,地图规模直接缩小 25 倍,规划时间从几十秒降到 2 秒内,覆盖路径的实际表现几乎没有差别。关键是:对全覆盖规划来说,10cm 栅格已经足够刻画大多数室内结构,更高的分辨率只会增加计算负担,不会带来实际收益。
4.4 覆盖路径与实时导航“打架”
这是我在做 ROS 2 + Nav2 项目时遇到最多的问题类型:覆盖路径已经规划好了,但机器人走的过程中经常出现“跑到一半突然往回绕”或者“在某处反复震荡”。
排查下来的最常见原因有两个:
- 覆盖路径的航点过密,导致局部规划器在计算代价时陷入局部最小;
- 跟实时 costmap 的膨胀参数不一致,原本规划时安全的路径,在实际运行时被判定为太靠近障碍物。
解决办法是在覆盖路径生成之后,先做一次贴图校验:用实时 costmap 验一遍路径,把“不可达”的航点剔除或修正;如果修正不了,就在该航点附近重新规划一段局部连接路径。这比在真机上出现问题再修要高效得多。
4.5 关于调参和复现流程的心得
最后给一点非技术的经验:一定要给自己的实验留好记录。我吃过亏:某次调试 GA 覆盖时调出了一组看似完美的参数,覆盖率首次上了 97%,但因为当时没有保存地图版本和随机种子,第二天想复现,怎么都调不回去。后来我养成了三个习惯:
- 每次实验固定地图版本(给地图文件加 md5 记录);
- 固定随机种子;
- 保存曲线和指标到统一归档目录。
这三个习惯在项目里帮了我大忙,尤其在甲方问“你这个覆盖率结果怎么来的”时,我能直接甩出一套完整的复现记录,专业度直接提升一个档次。
5. 选型指南:6 种算法怎么选,一句话总结
说了那么多,最后帮大家把思路拉回到实际选型上。这个板块可以当作一份速查手册,以后做项目的时候直接翻出来对照。
5.1 按场景特征的速查表
我把选型要点压缩成一张快速参考表:
| 场景特征 | 首选算法 | 备选算法 | 一句话原因 |
|---|---|---|---|
| 室内规整户型,预算有限 | Boustrophedon | Spiral | 逻辑简单,调参成本低 |
| 圆形/近圆形厂房巡检 | Spiral | STC | 转弯平滑,匹配结构形态 |
| 动态障碍物多的环境 | BINN | DRL | 实时局部决策能力强 |
| 高可靠巡检,需要理论保证 | STC | Boustrophedon | 覆盖完成性有上界保证 |
| 复杂离线任务、多约束优化 | GA/PSO | GA | 可统一考虑长度、转弯、能耗 |
| 固定场景高频复跑 | DRL | GA | 离线学一次,在线执行快 |
如果你的场景是典型的室内中小型机器人,我的初期推荐组合是:先用 Boustrophedon 快速验证完整链路,再根据实际覆盖瓶颈点换成 STC 或 Spiral。大多数项目走到这一步就已经能交付了。遇到动态障碍偏多或者有复杂作业约束的场景,才需要考虑 BINN 和元启发式方法。
5.2 我常用的“主算法 + 辅助策略”组合拳
最后分享一个我近两年比较偏爱的工程组合:主算法选 STC 或 Boustrophedon,搭配局部优化和边界巡逻。
具体操作是这样的:
- 先用 STC 生成一条覆盖路径,保证基础覆盖指标;
- 然后针对路径里的冗余转弯做一次局部平滑,可以使用conjugate gradient 之类的优化器,也可以用简单的“Collinear Point Removal”算法;
- 最后在整条路径跑完后,追加一段沿障碍物边界的巡逻路径,用于清理膨胀层覆盖不到的贴边区域。
这套组合在三个不同场景里都表现出相当稳定的指标:覆盖率能到 96% 以上,重复率控制在 10% 出头。更重要的是,它的计算量可控,非常容易迁移到 ROS 1 和 ROS 2 的不同项目里。
如果你正在做类似方向,我的建议很明确:别执着于在第一次就把所有算法全跑通。先选一两种最适合当前场景的算法往深处做,把一个链路完全打通、指标吃透,再横向扩展其他算法。覆盖路径规划这个方向,工程能力往往比“懂更多算法”更能解决问题。走走停停,边做边总结,你也会慢慢找到自己的那套“最佳实践”。