ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

K-Means+SVM+蚁群算法:清洁机器人路径规划的地图分区优化

K-Means+SVM+蚁群算法:清洁机器人路径规划的地图分区优化

简介:这份PDF面向智能清洁机器人路径规划方向的学习者与研究者,聚焦复杂障碍物环境下全局路径规划效率偏低的问题,给出一种将K-Means聚类与支持向量机(SVM)结合的栅格分区方案。资源以栅格地图建模为基础,先用K-Means对障碍物栅格聚类以减少分区数量,再由SVM对聚类结果精细分割形成优化分区,最后在预处理后的地图上运行蚁群算法完成路径搜索,并附MATLAB仿真对比。压缩包共1个PDF文件,约316KB,内容涵盖地图建模、聚类步骤与仿真、SVM分区原理及蚁群算法实验,结构完整、篇幅精炼,便于快速把握算法流程与实验思路。目前已有277人学习,适合希望了解栅格地图分区策略、改进蚁群算法收敛性能的读者参考借鉴。

1. 从一张“障碍物扎堆”的栅格地图说起

做清洁机器人全局路径规划,最容易被低估的一步不是路径搜索本身,而是地图预处理。我见过不少课程设计,栅格地图一画出来,障碍物密密麻麻挤在中间,蚁群算法跑五十轮还在原地打转,收敛曲线像心电图。问题往往不在蚁群参数没调好,而在于把整张地图当成一个搜索空间硬啃。这套方案要解决的正是这件事:用栅格法建模,先用 K-Means 把障碍物栅格聚成簇,再用 SVM 找出分区边界,把一张复杂地图纵向切成几块,最后让蚁群算法在缩小后的搜索空间里寻优。它适合正在做路径规划毕设、课设,或者要给清洁机器人做全局覆盖方案的人,读完能照着搭出一套可仿真、可验收的完整流程。

2. 需求与总体方案:为什么是“聚类 + 分类 + 蚁群”三段式

2.1 需求拆解:全遍历、低重复、能收敛

智能清洁机器人的核心指标就三个:清扫覆盖率、重复率、规划耗时。随机遍历策略覆盖率差、耗时长,这是行业里公认的痛点。要改成规划式遍历,就得先回答一个问题——机器人在已知环境里怎么走完每一块可通行区域,还不来回绕。

栅格法把二维环境切成边长 1 的方格,每个格子记录“可通行/障碍”信息,以左下角为原点、X 轴向右、Y 轴向上建直角坐标系。这个建模方式直观、结构化,后续所有算法都在这个矩阵上做文章。但栅格地图有个天然缺陷:障碍物越多,可搜索的栅格节点越碎,蚁群算法这类基于概率转移的搜索方法就越容易陷入局部最优,收敛速度直线下降。

所以需求可以翻译成一句工程话:在不丢失覆盖完整性的前提下,把大搜索空间切成若干小搜索空间,让蚁群算法每次只面对一块“干净”的区域。这就是三段式方案的出发点。

2.2 三段式流水线的职责划分

整套方法拆成三个串行阶段,每个阶段只干一件事:

阶段算法输入输出解决的问题
障碍物聚类K-Means障碍物栅格坐标K 个障碍物簇及簇心把散乱障碍物归并,降低后续分类样本量
地图分区SVM聚类后的栅格样本最优分类面与支持向量找到纵向切分边界,生成多边形分区
区域寻优蚁群算法分区后的栅格地图各区域遍历路径在小空间内快速收敛到较优路径

这个划分的关键在于约束条件的切换。K-Means 第一次跑用欧几里得距离,让障碍物按空间邻近性聚团;第二次只取横坐标距离,强制障碍物在 X 方向聚成 3 类,为纵向分区铺路。SVM 则把“哪些栅格属于左区、中区、右区”变成一个二分类/多分类的间隔最大化问题,支持向量就是分区边界上的关键点。

提示:分区数量不是拍脑袋定的。原文仿真里地图含 1 个凹形障碍物和 5 个矩形障碍物,最终切成 3 块。分区太少等于没切,太多则每块区域过窄,蚁群在边界处反复穿越,反而增加重复率。

2.3 技术选型的理由与边界

为什么不用四叉树或 Boustrophedon 单元分解?原文给了对比结论:这两种传统分区方法在障碍物密集时分区数量偏多,而 K-Means + SVM 的组合分区数更少,复杂障碍物场景下效果更稳。原因不难理解——四叉树按固定规则递归切分,不管障碍物实际分布;K-Means 是数据驱动的,障碍物在哪扎堆,簇就往哪聚,SVM 再沿着簇间空隙找最大间隔面,切出来的边界更贴合实际地形。

这套方案的适用边界也要说清楚:它面向已知静态环境的全局规划,地图先验已知,不处理动态障碍物;分区是纵向的,适合长条形或左右延展的室内平面。如果场景是多层或强动态,这套流水线需要额外加层。

3. 障碍物聚类:K-Means 两次跑,约束条件各不同

3.1 聚类步骤与收敛判据

K-Means 的基本逻辑是:从 n 个样本里取 k 个初始簇心,按相似性把样本分到最近簇,再用簇内均值更新簇心,迭代到簇内误差平方和 SSE 收敛。原文给出的流程可以整理成可执行版本:

  1. 输入障碍物栅格坐标样本集,初始化最大簇数 K、最大迭代次数 Tmax、允许误差 εmax;
  2. 随机选 K 个栅格坐标作初始簇心;
  3. 计算每个样本点到各簇心的欧几里得距离,分配到最近簇;
  4. 按簇内样本均值更新簇心;
  5. 计算 SSE,若 |SSE_T − SSE_{T−1}| < εmax 或达到 Tmax,停止;否则回到第 3 步。

欧几里得距离和簇心更新是这套算法的两个核心公式,落到代码里就是下面这段。我用 Python 写了个最小可复现版本,坐标直接喂栅格点:

import numpy as np def kmeans(points, k, max_iter=100, eps=1e-4, use_x_only=False): # points: N×2 障碍物栅格坐标; use_x_only=True 时只按横坐标聚类 data = points[:, 0:1] if use_x_only else points idx = np.random.choice(len(data), k, replace=False) centers = data[idx].copy() labels = np.zeros(len(data), dtype=int) prev_sse = np.inf for _ in range(max_iter): # 分配:计算每个点到各簇心的欧氏距离 dists = np.linalg.norm(data[:, None, :] - centers[None, :, :], axis=2) labels = np.argmin(dists, axis=1) # 更新:簇内均值作为新簇心 for j in range(k): if np.any(labels == j): centers[j] = data[labels == j].mean(axis=0) # 收敛判据:簇内误差平方和变化量 sse = sum(np.sum((data[labels == j] - centers[j]) ** 2) for j in range(k)) if abs(prev_sse - sse) < eps: break prev_sse = sse return labels, centers, sse

参数说明:k对应原文的最大簇类个数,max_iter是 Tmax,eps是 εmax。use_x_only这个开关是整套方案的关键设计——第一次聚类传False,用完整二维坐标让障碍物按空间邻近性聚团,得到图 2 那种带簇心的效果;第二次传True,只拿横坐标算距离,强制障碍物在 X 方向聚成 3 类,得到图 3 的横向聚类结果。这一步的本质是用距离度量的维度切换来施加分区约束,比改算法结构轻得多。

3.2 两次聚类的工程含义

第一次聚类解决“障碍物归并”,把零散障碍栅格压成若干簇,后续 SVM 的样本量从几百个栅格降到几十个簇心附近点,计算量明显下降。第二次聚类解决“分区方向”,只按横坐标聚类等价于告诉算法:我关心的是左右怎么切,不关心上下。这个约束是人为注入的先验,换来的是分区边界稳定、可复现。

我一般会在这里加一个检查:第二次聚类后打印每个簇的 X 范围,如果三个簇的 X 区间严重重叠,说明 K 值或初始簇心选得不好,需要固定随机种子重跑。K-Means 对初始簇心敏感是血泪经验,仿真论文里不写,但你自己跑的时候不固定种子,每次结果都不一样,答辩时被问“为什么这次分区和上次不同”就尴尬了。

注意:K-Means 的 K 值要和最终分区数对齐。原文最终切 3 块,所以第二次聚类 K 取 3。如果地图更宽、障碍物分布更散,K 可以取 4 或 5,但要同步检查 SVM 分类后每块区域的栅格数是否够蚁群展开。

4. 地图分区:SVM 找最大间隔面,多边形切出三块区域

4.1 从分类间隔到分区边界

SVM 的核心是找一个最优分类面 H,把两类样本分开的同时让分类间隔最大。线性可分情况下,分类面写成 w·x + b = 0,归一化后满足 yi(w·xi + b) ≥ 1,分类间隔等于 2/||w||。要让间隔最大,等价于最小化 ||w||²/2,约束条件就是上面那个不等式。用拉格朗日乘子转成对偶问题后求解,只有 αi 不为零的样本才是支持向量,它们决定了分类面位置。

在栅格分区里,这一步的工程含义很直接:把第二次 K-Means 得到的横向簇标签当作 SVM 的训练标签,栅格坐标当特征,训练出一个分类面。支持向量的坐标、分类面与地图上下边界 y=0 和 y=ymax 的交点,这几个点连起来就是一个多边形,多边形的边就是分区边界。

from sklearn import svm import numpy as np # X_grid: 栅格坐标; y_label: 第二次 K-Means 给出的横向簇标签 clf = svm.SVC(kernel='linear', C=1.0) clf.fit(X_grid, y_label) # 支持向量与分类面系数 sv = clf.support_vectors_ # 支持向量坐标 w = clf.coef_[0] # 分类面法向量 b = clf.intercept_[0] # 偏置 # 求分类面与 y=0、y=ymax 的交点 x 值 def x_at_y(y_val): return -(w[1] * y_val + b) / w[0] x_bottom = x_at_y(0) x_top = x_at_y(y_max)

这段代码里kernel='linear'是必须的,因为我们要的是直线分类面,才能和地图边界围成规则多边形;换成 RBF 核虽然分类精度可能更高,但边界变成曲线,后续按 y 值比较 x 大小的分区逻辑就没法用了。C是惩罚系数,取 1.0 是常规起点,样本有噪声时可适当调大。

4.2 多边形分区与栅格归属判定

拿到支持向量和上下边界交点后,生成多边形,再对地图上每个栅格做归属判断:对每一行 y,计算多边形边在该 y 值下的 x 坐标,比较栅格中心 x 与边界 x 的大小关系,小于边界归左区,大于归右区,中间归中区。原文正是用“相同 y 值下比较 x 大小”的方式把地图划成 3 类。

这个判定逻辑看起来朴素,但它是整套方案里最容易翻车的地方。多边形边界的 x 值要按行精确计算,不能拿支持向量的原始坐标直接比,否则边界附近会出现归属跳变,蚁群跑到边界处发现左右区域不连通。我的做法是对每一行 y 单独求交点,生成一张row -> boundary_x的查找表,分区时直接查表,既快又不会错。

分区完成后,每个区域内部只保留本区域的障碍物信息和可通行栅格,蚁群算法逐区寻优。相比整图搜索,单区栅格数下降一个量级,收敛轮数自然跟着降。原文仿真里分区数比四叉树和 Boustrophedon 单元分解都少,复杂障碍物场景下优势更明显,原因就在于 SVM 的边界是贴着障碍物簇间空隙走的,不会像固定规则那样把一块完整通道硬切开。

5. 分区后蚁群寻优:参数怎么设、路径怎么连

5.1 蚁群算法的转移规则与信息素更新

蚁群算法模拟蚂蚁觅食:m 只蚂蚁随机放到 n 个节点,每只蚂蚁按信息素浓度和启发式信息选下一个点,用禁忌表记录已访问节点,一轮走完更新信息素。转移概率由信息素 τij 的 α 次方和启发因子 ηij 的 β 次方共同决定,ηij 通常取距离的倒数。信息素更新用 Ant Cycle 模型,所有蚂蚁完成一轮后统一调整,挥发系数 ρ 控制旧信息素残留,Q 是信息素强度,Lk 是第 k 只蚂蚁本轮路径长度。

原文实验参数给得很具体:ρ=0.15、ε=0.1、β=2.5、m=30、q0=0.85、NCmax=50。这组参数可以直接作为你复现的起点。ρ 偏小意味着挥发快、探索性强,适合分区后的小空间快速收敛;m=30 在单区栅格规模下够用,蚂蚁太多反而增加单轮计算量。

# 蚁群核心参数(对应原文实验设置) rho = 0.15 # 信息素挥发系数 beta = 2.5 # 期望启发因子 m = 30 # 蚂蚁数量 Q = 1.0 # 信息素强度 NC_max = 50 # 最大迭代轮数 # 单轮信息素更新(Ant Cycle 模型) def update_pheromone(tau, paths, lengths): tau *= (1 - rho) # 全局挥发 for path, L in zip(paths, lengths): for i in range(len(path) - 1): a, b = path[i], path[i + 1] tau[a][b] += Q / L # 按路径长度释放信息素 tau[b][a] = tau[a][b] return tau

参数背后的逻辑值得说透:β 越大,蚂蚁越倾向走短边,收敛快但容易早熟;ρ 越小,历史信息保留越少,探索性越强。分区之后每块区域节点少,β=2.5 配合 ρ=0.15 能在 50 轮内收敛,原文的收敛曲线也印证了这一点。如果你换地图后收敛变慢,先动 ρ 和 NCmax,别一上来就改 β。

5.2 分区路径的拼接与覆盖完整性

逐区寻优只解决了“区内怎么走”,全局覆盖还要解决“区间怎么连”。常规做法是:每个区域独立跑蚁群得到区内遍历路径,再按区域邻接关系用最短连接路径把各区串起来,机器人从起点进入第一区,走完转下一区。这里要检查两件事——区域之间的连接通道是否被分区边界切断,以及每个可通行栅格是否至少被一条路径覆盖一次。

我一般会在仿真里加一个覆盖率统计:把所有路径经过的栅格去重,除以可通行栅格总数,低于 95% 就回头查分区边界。分区边界如果正好压在一条窄通道上,机器人可能被“分”到两个区,两边路径都不完整,覆盖率就掉下来了。这也是为什么第 4 章强调边界要贴着障碍物簇间空隙走,而不是硬切通道。

6. 避坑与验收:仿真跑通之后怎么证明它真的有效

6.1 四个最容易翻车的点

现象一:每次仿真分区结果都不一样。原因:K-Means 初始簇心随机选,SVM 训练样本顺序也影响结果。解决:固定随机种子,聚类前打乱样本顺序并记录种子,答辩时能复现同一张分区图。

现象二:蚁群收敛曲线前期下降快、后期卡住。原因:分区边界附近栅格归属跳变,蚂蚁在边界反复横跳。解决:按行生成边界 x 查找表,分区后检查每区栅格连通性,断开的地方手动补通。

现象三:覆盖率上不去,总有角落没走到。原因:分区把凹形障碍物内部的可通行区域切到了两个区,两边都以为不归自己管。解决:分区前先标记凹形障碍物内部连通域,分区时保证同一连通域不被切开。

现象四:换一张地图参数全失效。原因:ρ、β、m 是按原图规模调的,地图变大后单区节点数变化,原参数不再适配。解决:把参数和单区栅格数挂钩,节点数翻倍时适当增大 m 和 NCmax,ρ 保持 0.1~0.2 区间微调。

6.2 验收与答辩的检查清单

验收这套方案,别只看一张漂亮的路径图。我通常按下面几条逐项过:

检查项合格标准怎么看
分区数量与 K 值一致,每区栅格数不为零打印各区栅格计数
分区边界不切断可通行窄通道叠加原图目视检查
收敛轮数分区后明显少于整图搜索对比两条收敛曲线
覆盖率可通行栅格覆盖 ≥95%路径栅格去重统计
重复率重复经过栅格占比可控统计路径栅格访问频次
可复现性固定种子后结果一致连跑三次比对

答辩时最容易被问的是“你怎么证明分区真的提升了效率”。别只放最终路径图,把整图直接跑蚁群和分区后跑蚁群的收敛曲线画在同一张图上,横轴迭代轮数、纵轴最优路径长度或覆盖率,两条线的差距就是最有力的证据。再补一张分区前后搜索节点数对比表,数字一摆,逻辑就闭环了。

从那以后我每次做这类仿真,都强制先跑一遍“不分区基线”,把基线数据存下来再动分区逻辑,不然改到最后自己也说不清提升来自哪里。希望这套流程能帮到你,把毕设或方案里的每一步都落到可复现、可验收的实处。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进