简介:这是一篇面向智能清洁机器人、路径规划及智能算法研究者的PDF论文,针对复杂障碍物环境下全局路径规划效率低的问题提出一种融合算法方案。文中利用栅格法对工作环境建模,将K-Means聚类与支持向量机(SVM)相结合:先以不同约束条件对障碍物栅格聚类,再借助SVM进行精细分区,有效压缩分区数量,缩小蚁群算法的搜索空间,从而减少收敛时间并避免陷入局部最优。资源压缩包体积仅316KB,含1个PDF文档,内容覆盖栅格地图建模、K-Means聚类步骤与评价函数、SVM分类面推导、蚁群算法仿真及与传统方法的对比分析。已有277人学习下载。读者可借此掌握从环境建模、聚类分区到启发式搜索的完整实现链路,参考其MATLAB仿真设计,适用于相关课题研究、课程设计或算法改进。
1. 栅格地图大到一定程度,全局规划先撑不住,分区加分类才是解药
处理高分辨率栅格地图的路径规划任务时,直接拿全局搜索算法硬跑,内存和时间都会迅速失控;换成“基于K-Means与SVM结合的栅格分区路径规划方法”,先用K-Means把大地图切成若干分区,再用SVM对每个栅格的通行代价做估计,路径规划就从一张大图的压力变成几个小分区的高效接力。这个思路对动辄上千乘上千栅格的仓储地图、矿区地图尤其好用,解决的是“地图太大、直接算不动”的痛点,适合做移动机器人导航和无人车路径规划的从业者。我一开始也有点怀疑,多了一层分区和分类,岂不是更慢?实际跑完对比后才发现,只要分区粒度合适,速度反而快一倍,路径质量还更稳。
2. K-Means在栅格地图上做空间分区:聚类特征、K值选择与代码实现
2.1 均匀切分的两个死穴:障碍分布不均与分区边界浪费
很多人拿到大栅格地图,第一反应是均匀切成等大小的网格,每个网格内部跑一次局部规划。这个方案在障碍物稀疏的地图上勉强能跑,但两张典型地图会直接让你翻车。第一类是障碍物分布极不均匀的仓库地图,一个区域密密麻麻全是货架,另一个区域空到能跑直线。均匀网格切下去,要么货架区被切成几十个碎片,要么空荡区被合并成一个大块,局部规划的负载完全失衡。第二类是分区边界浪费问题,均匀切分的边界和障碍物分布没有关系,A*在跨区时常常沿着边界走一大段折线,路径长度凭空增加不少。
K-Means在这里的优势是它按栅格坐标的实际分布来聚簇,而不是按固定网格线切图。同一个簇里的栅格在空间上接近,障碍物的分布对簇形状有直接影响。再加上一点密度特征,就能把“空旷的大区域”和“拥挤的窄通道”区分成不同分区,每个分区内部的地形复杂度大致相当。常见做法是只对可通行栅格做聚类,不把障碍物栅格纳入样本,这样每个簇的质心落在可通行区域内部,后续从质心出发规划路径才不会落进墙里。
2.2 构造聚类输入:可通行栅格坐标加局部密度特征
K-Means的输入不能直接丢整张图片,常见做法是把二维栅格地图里所有值为“可通行”的栅格坐标提取出来,转成N行特征的数组。特征至少要包含归一化后的x和y坐标,否则如果地图是非正方形的,比如120宽180高,x和y的数值范围差异会直接让聚类结果偏向数值更大的那个方向。我一般还会加一个局部障碍物密度特征,用来区分空旷区域和拥挤区域,避免空旷地带上万栅格被划成一个巨大分区。
import numpy as np from sklearn.cluster import KMeans def build_cluster_features(grid, density_radius=3): """把栅格图转成K-Means可吃的特征矩阵。 grid: 2D numpy数组, 0=可通行, 1=障碍物 返回: feats (N,3), coords (N,2) """ ys, xs = np.where(grid == 0) h, w = grid.shape feats = [] for x, y in zip(xs, ys): # 取以当前栅格为中心的方形窗口,统计障碍物占比 x0, x1 = max(0, x - density_radius), min(w, x + density_radius + 1) y0, y1 = max(0, y - density_radius), min(h, y + density_radius + 1) window = grid[y0:y1, x0:x1] obs_ratio = np.mean(window == 1) # 坐标归一化到0~1,密度特征天然也是0~1 feats.append([x / w, y / h, obs_ratio]) return np.array(feats), np.column_stack([xs, ys]) grid = np.zeros((120, 180)) grid[30:50, 20:25] = 1 # 一块竖长障碍 grid[80:105, 100:160] = 1 # 一块大型货架区 grid[10:20, 60:110] = 1 # 一条带状障碍 feats, coords = build_cluster_features(grid) k = 6 km = KMeans(n_clusters=k, random_state=42, n_init=10) labels = km.fit_predict(feats)这段代码里最值得说明的是density_radius。它决定了局部窗口的大小,半径太小,密度特征对障碍物变化过于敏感,同一片区域会出现很多噪声簇;半径太大,密度特征被平滑掉,加了等于没加。我一般取地图短边长度的1%到3%作为密度半径,在一个120×180的地图上就是2到5个栅格。n_init=10是K-Means的多次初始化次数,避免单次随机初始点落入局部最优,这个参数在很多实现里默认是10,但显式写出来会提醒自己它对聚类稳定性的影响。
聚类跑完后,记得把标签重新映射回栅格坐标。常见做法是定义一个和grid形状相同的整型数组,初始化为-1,再把可通行坐标处的值替换成对应的簇标签。后续画分区图、计算每分区质心,都从这个二维标签数组出发,比反复查坐标列表快得多。
2.3 K值怎么定:轮廓系数只是参考,端到端验证才是标准
K值选择是这个方案里最玄学的一环。轮廓系数是常见的自动选K手段,它衡量簇内紧密度和簇间分离度,理论上越高越好。但路径规划场景下,轮廓系数高的分区方案,端到端路径很可能反而更差,原因后面避坑章会专门讲。这里先给出一段自动试探K的代码,作为初始参考值。
from sklearn.metrics import silhouette_score best_k, best_score = 4, -1 for k in range(3, 12): km = KMeans(n_clusters=k, random_state=42, n_init=10) lab = km.fit_predict(feats) sc = silhouette_score(feats, lab) if sc > best_score: best_k, best_score = k, sc print("轮廓系数最优K:", best_k, "得分:", best_score)轮廓系数在这个流程里只适合快速筛选掉明显离谱的K值,比如K=2或者K=20。真正决定K的是两个约束:一是分区数量不能太多,否则后续顶层分区访问顺序的求解规模呈平方增长,一个200×200地图分成5个区和分成20个区,后者的组合优化耗时完全不是一个量级;二是单个分区不能太大,否则分区内A*的地图尺寸优势就消失了。我一般先按轮廓系数拿到一个候选区间,再用端到端路径长度和规划耗时做最终决策。200×200的仓库地图,K在6到12之间比较稳妥,超过15就要警惕多分区拼接带来的边界损耗。
3. SVM给每个栅格打代价标签:特征构造、伪标签生成与概率输出
3.1 SVM在路径规划里的定位:不是找路,是给每个栅格打代价分
很多人一看到“K-Means与SVM结合”就会默认SVM用来对路径点做分类,比如把路径点分成“安全”和“危险”两类。这个思路不是不行,但实际落地时收益很有限,因为路径点本身是规划的输出,拿输出做分类再回头修正,逻辑上绕了一圈。我更常用的做法是把SVM放在代价地图层面:K-Means负责宏观分区,SVM负责微观通行代价估计。每个栅格提取几个局部特征,SVM判断这个栅格是“高通行代价”还是“低通行代价”,再把分类概率转换成A*的栅格代价。这样SVM的输出影响的是路径的走向,而不是路径的最终判定,和路径规划算法耦合得更自然。
为什么选SVM而不是随机森林或者小型神经网络?这个问题的答案在于样本量和特征维度。栅格数量虽然多,但特征只有3到5维,而且可通行栅格之间存在很强的空间相关性,有效信息量并没有那么大。SVM在这个场景下的优势是:小样本下泛化稳定,RBF核能够表达非线性边界,训练速度快到可以忽略不计。相比之下,随机森林在特征维度低时表现也还行,但给出的概率输出不如SVM的可解释性好;神经网络在这个数据量下容易过拟合,还要调一堆结构参数,性价比不高。
3.2 特征与标签从哪来:无真实数据时用启发式规则造一份伪标签
SVM是监督学习,必须要有标签。但路径规划场景下,谁会给你标注几万个栅格是“好走”还是“难走”呢?常见做法是先用启发式规则生成一批伪标签,比如距离障碍物太近的栅格标成高代价,距离足够远的标成低代价,中间过渡地带不参与训练。这里的关键细节是:不能把全部栅格都打上标签再喂给SVM,那样SVM学到的只是规则的复读,换一张地图就失效。正确做法是只对特征明显的栅格打标签,让SVM自己学习两类样本之间的边界,这样它得到的决策边界会比硬阈值平滑得多。
from scipy.ndimage import distance_transform_edt from sklearn.svm import SVC from sklearn.model_selection import train_test_split def build_svm_dataset(grid): """用距离变换构造SVM训练集。 dist[y,x] = 栅格到最近障碍物的栅格数 """ dist = distance_transform_edt(grid == 0) ys, xs = np.where(grid == 0) h, w = grid.shape feats, labels = [], [] for x, y in zip(xs, ys): d = dist[y, x] # 3x3邻域自由栅格占比 x0, x1 = max(0, x-1), min(w, x+2) y0, y1 = max(0, y-1), min(h, y+2) free_ratio = np.mean(grid[y0:y1, x0:x1] == 0) feats.append([d, free_ratio]) if d < 2: labels.append(1) # 高代价:太靠近障碍物 elif d > 4: labels.append(0) # 低代价:足够开阔 else: labels.append(-1) # 模糊地带,不参与训练 mask = np.array(labels) != -1 X = np.array(feats)[mask] y = np.array(labels)[mask] return X, y, dist X, y, dist = build_svm_dataset(grid) X_train, X_val, y_train, y_val = train_test_split( X, y, test_size=0.2, random_state=7 ) svm = SVC(kernel='rbf', C=1.5, gamma='scale', probability=True) svm.fit(X_train, y_train)标签规则里<2和>4这两个阈值不是拍脑袋定的。距离障碍物1个栅格意味着路径几乎贴着墙走,A*在这个位置产生的折角很大;距离4个栅格以上基本可以认为有足够的转向空间。中间2到4这段,SVM学到的边界会把代价逐渐抬高,而不是像阈值规则那样一刀切。gamma='scale'是RBF核的一个安全默认值,它会根据输入特征的方差自动调整核宽度,避免特征尺度差异导致训练失效。C值控制对误分类样本的惩罚,C越大决策边界越复杂,1.5是一个初始值,后面避坑章会讲怎么微调。
test_size=0.2和random_state=7是训练测试划分的常规参数。这里我特别说一下probability=True,它让SVM在训练时额外做一次交叉验证来校准概率输出,训练时间会稍微变长,但换来的是predict_proba可以直接给出“这个栅格是高代价”的可信概率,后续转代价地图就靠它。
3.3 从分类概率到栅格代价值:一个保守的缩放函数
SVM输出的概率范围是0到1,但不能直接把这个值当作A栅格代价。A在计算路径总代价时会把每一步的栅格代价累加,如果两个相邻栅格的代价差异过大,路径宁可绕行十多个普通栅格也不愿意跨过一个高代价栅格。实际落地时,一根细长的障碍物旁边如果被赋了一个极大的代价系数,A*会被迫绕一个很夸张的弧度。常见做法是把概率map到一个更保守的代价区间,比如从1.0到3.0,同时把所有障碍物栅格设为极大值1e6,确保搜索算法永远不会踩进去。
def svm_cost_map(grid, svm, dist): """把SVM概率输出转成A*用的栅格代价地图。""" h, w = grid.shape cost = np.ones_like(grid, dtype=float) ys, xs = np.where(grid == 0) X_online = [] for x, y in zip(xs, ys): d = dist[y, x] x0, x1 = max(0, x-1), min(w, x+2) y0, y1 = max(0, y-1), min(h, y+2) fr = np.mean(grid[y0:y1, x0:x1] == 0) X_online.append([d, fr]) prob = svm.predict_proba(np.array(X_online))[:, 1] # 保守缩放:代价限制在1~3之间 cost[ys, xs] = 1.0 + 2.0 * np.clip(prob, 0, 1) cost[grid == 1] = 1e6 return cost cost_map = svm_cost_map(grid, svm, dist)np.clip(prob, 0, 1)其实是白写了一次,因为predict_proba输出本身就落在0到1之间,但保留这行代码是为了强调截断是必须的,如果哪次你改了模型输出的定义,clip能兜住异常值。代价下限设为1.0意味着普通栅格的移动代价等于欧氏距离,这是A*的基线成本;上限设为3.0意味着一个“高代价栅格”移动一步相当于走三步普通栅格,绕行三个格子来避开它才划算。这个1:3的比值是我试过几个地图后觉得最平衡的,如果你调整后路径变得过度绕行,优先检查这个缩放系数而不是SVM本身。
4. 组合链路怎么走:分区顺序、分区内A*与边界拼接的实现
4.1 两级规划流程:分区顺序与分区内寻路是谁先谁后
K-Means和SVM各司其职之后,真正把这条路串起来的是一个两级规划流程。顶层解决“从起点所在分区到终点所在分区,中间要经过哪些分区,按什么顺序走”,这个问题本质上是一个小规模的旅行商问题,但分区数量通常在6到12个之间,完整求解不划算,贪心最近邻足够当主力。底层解决“在某个分区内部,从入口点到出口点的具体路径”,这里用的是以SVM代价地图为场地的A*。两个层次的先后顺序是一定的:先做分区顺序,再做分区内路径。如果先跑局部路径再决定分区顺序,入口点和出口点都还没有定义,局部A*根本无从开始。
顶层贪心策略的具体做法是:以每个分区的质心作为节点,计算质心之间的欧氏距离矩阵,从起点分区出发,每次选择离当前位置最近且没有访问过的分区作为下一站。这样一个200×200地图,K=10的情况下,十几行代码就能算出足够好的访问序列。它的缺陷是可能漏掉“绕一点路但整体更优”的组合,但对路径规划来说这已经很实用了。真正影响路径质量的反而不在顶层,而在底层的边界拼接。
4.2 一个能跑通的组合链路代码:K-Means分区、SVM代价、A*按序拼接
这里给出一个可以直接跑通的最小实现,把从栅格地图到最终路径的整个链路串起来。A*的实现用的是四邻域,四邻域比八邻域更保守,不会出现斜穿墙角的现象。对于轮式机器人和AGV,四邻域的路径更容易被下游轨迹跟踪模块执行。
import heapq import numpy as np from sklearn.cluster import KMeans def astar_with_cost(cost_map, start, goal): """基于代价地图的A*搜索,四邻域。""" h, w = cost_map.shape dist = {start: 0.0} prev = {} pq = [(0.0, start)] while pq: d, cur = heapq.heappop(pq) if cur == goal: break cx, cy = cur for dx, dy in ((1,0), (-1,0), (0,1), (0,-1)): nx, ny = cx + dx, cy + dy if 0 <= nx < w and 0 <= ny < h and cost_map[ny, nx] < 1e5: nd = d + cost_map[ny, nx] if nd < dist.get((nx, ny), 1e18): dist[(nx, ny)] = nd prev[(nx, ny)] = cur heapq.heappush(pq, (nd, (nx, ny))) # 回溯路径 path, cur = [], goal while cur != start: path.append(cur) cur = prev.get(cur) if cur is None: return [] # 无路可达 path.append(start) return path[::-1] # 1. K-Means分区 feats, coords = build_cluster_features(grid) km = KMeans(n_clusters=6, random_state=42, n_init=10) cluster_label = km.fit_predict(feats) # 2. SVM代价地图 X, y, dist = build_svm_dataset(grid) svm = SVC(kernel='rbf', C=1.5, gamma='scale', probability=True) svm.fit(X, y) cost_map = svm_cost_map(grid, svm, dist) # 3. 计算每个分区的质心作为访问节点 centroids = km.cluster_centers_ # (k, 3)的最后一列是密度特征,取前两列 centers = centroids[:, :2] # 反过来映射回原地图坐标需要乘回宽高 # 4. 贪心求分区访问顺序 k = centers.shape[0] visited = {0} # 假设起点在0号分区 order = [0] while len(order) < k: cur = order[-1] candidates = [(np.hypot(centers[j,0]-centers[cur,0], centers[j,1]-centers[cur,1]), j) for j in range(k) if j not in visited] _, nxt = min(candidates, key=lambda t: t[0]) visited.add(nxt) order.append(nxt) # 5. 按顺序拼接分区内的A*路径 full_path = [] start = (5, 5) # 手动指定起点 goal = (110, 170) # 手动指定终点 cur_point = start for cid in order: # 取该分区的质心作为该分区内的“出口目标” cx = int(centers[cid, 0] * grid.shape[1]) cy = int(centers[cid, 1] * grid.shape[0]) seg = astar_with_cost(cost_map, cur_point, (cx, cy)) if not seg: print("分区内A*无路可达:", cid) break full_path.extend(seg[:-1]) # 去掉重复的连接点 cur_point = (cx, cy)这段代码有几个容易看漏的点。第一,astar_with_cost返回的路径直接用代价地图来寻路,而cost_map已经包含了障碍物栅格的极大值,所以搜索过程天然不会进入障碍物。第二,贪心的visited集合假设起点在0号分区,实际使用时应该根据起点坐标判断它落在哪个分区,再从这个分区开始访问。第三,分段路径拼接时seg[:-1]是为了去掉重复的连接点,避免同一个栅格出现在两段路径的末尾和下一段的开头。
路径拼接完还有一个细节:full_path是离散栅格坐标序列,直接给机器人执行会有很多折角。常见做法是做一个简单的路径平滑,取相邻三点的中间点作为折中,或者用分段线性插值,把每一步的方向变化控制在30度以内。这一步和分区本身关系不大,但要是不做,路径会显得很毛糙。
4.3 分区边界为何要留重叠带:路径连续性的保障
分区后各簇之间是紧邻的,但A*在分区内部搜索时看不到下一分区的地形。当它到达分区边界时,它选择的出口点可能是当前分区内的代价最低点,但到了下一分区,这个出口点不一定是最佳入口,相邻两个分区独立搜索的结果就会在边界处形成一个尖锐的V型折线。这个问题在障碍物分布复杂的区域特别明显,我见过路径在一个边界处突然直角转弯,长度损失超过了10%。
解决这个问题的方式不是强行约束出口点,而是给每个分区做一层膨胀。binary_dilation可以把分区掩码向外扩展若干栅格,被扩展出来的区域叫重叠带。分区内A搜索时,重叠带里的栅格也参与计算,但代价仍然是基于SVM代价地图的真实值。这样A在边界附近就能看到一部分下一分区的代价信息,自动选择更顺滑的出口方向。
from scipy.ndimage import binary_dilation def build_local_cost(mask, cost_map, expand=4): """把分区掩码膨胀后,限制A*只在膨胀区域内搜索。""" mask_exp = binary_dilation(mask, iterations=expand) local_cost = np.where(mask_exp, cost_map, 1e6) return local_costexpand的取值直接决定路径平滑和计算量的权衡。取2以下,重叠带太窄,SVM代价信息还没起作用就到下一分区了;取6以上,重叠带可能把相邻分区的障碍物误差引入进来,A*为了避开远处障碍物在边界处提前转向,反而绕路。4个栅格是我常用的起点值,如果发现边界折线仍然存在,优先加大到5或6,同时留意路径长度是否同步增加。
5. 避坑:K-Means与SVM组合落地时的5个常见问题
5.1 聚类结果把同一片可通行区域切成不连通碎片
现象:某个分区内部A*明明可以到达边界,但搜索返回空路径,日志显示“无路可达”,手动检查发现是同一块连通区域被K-Means的决策边界从中间切开了。K-Means只看坐标距离,完全不看障碍物是否阻挡了两片区域的连通性,所以聚类边界完全可能画在一条狭窄通道的正中间,把通道一分为二。
原因:没有把连通性约束放进聚类特征。栅格的x和y坐标反映了空间位置,但一条通道和通道两侧的宽阔区域,坐标距离很近却并不直接连通。
解决:聚类之前先跑一次连通域标记。如果地图被障碍物天然分割成多个连通域,先按连通域分组,在每个连通域内部单独做K-Means,最后再按空间邻近关系把零散连通域合并到相邻的大簇里。合并时以“边界接触长度”为标准,不要只看质心距离。
5.2 SVM只是把阈值规则复读了一遍,换个地图就失效
现象:SVM在训练集上验证F1高达0.95,把整条管线挪到另一张障碍物布局完全不同的地图上,高代价区域预测结果肉眼可见地偏离实际。墙边的栅格没被标记成高代价,通道中间反而出现了高代价点。
原因:伪标签来自单一启发式规则,SVM学会了复读这个规则,本质上没有学到任何规则之外的结构。特征空间里只有距离和邻域占比,这两个特征本身就是规则的计算依据,SVM的决策边界和规则几乎重合,天然无法泛化到布局变化后的地图。
解决:在伪标签里混入少量人工种子标签。不用多,二三十个典型栅格就够,比如明确的墙角、通道中央、货架边缘各标几个,这些种子标签提供规则之外的几何结构信息。另外可以把规则从一种扩到三种,比如障碍物距离、局部路径宽度、周边转折次数,三种规则投票生成标签,能明显减少单规则的噪声。
5.3 分区边界处出现锯齿折线,路径连续性崩掉
现象:整条路径在每个分区边界处都有一个小角度的折角,K越大折角越多,路径总长度比全局最优长了将近15%。
原因:分区内A只能看到自己分区和重叠带内的代价信息,无法感知下一个分区的入口方向。每个分区的A都在用自己的局部视角选择出口点,这些出口点在全局视角下并不连续。
解决:先把重叠带加上,让分区A能看到边界外的代价。同时调整出口点选取逻辑,不再用分区质心当出口目标,而是把出口点放到重叠带内、同时距离下一分区质心最近的栅格上。另一种做法是先做一次全局粗路径,用粗路径穿过分区边界的位置作为每个分区的入口出口约束,再把限制传给各分区A。
5.4 K值调成轮廓系数最优,规划总时间反而更差
现象:把K从6调到8,轮廓系数明显上升,聚类结果看起来也更“漂亮”,但端到端规划总时间反而从0.8秒涨到2.1秒,路径长度还变长了。
原因:轮廓系数衡量的是空间聚集度,和路径规划的执行成本无关。K增大让每个分区变小,分区内A的搜索范围确实减小了,但顶层贪心访问顺序需要访问更多分区,每次跨边界都要付出拼接代价。分区数量增加带来的边界损耗超过了A搜索范围减小带来的收益。
解决:选K时直接以端到端路径长度和总耗时为标准,轮廓系数只用来排除极端方案。给K设置一个硬上限,比如不超过地图短边栅格数的三分之一,一个200×200的地图,K的上限大概在20到25之间,但实际K在10左右就能获得大部分收益。
5.5 代价范围没截断,A*为了躲开一个高代价栅格绕远路
现象:路径明显偏离直觉,在空旷区域里为了绕开一小片SVM标记的高代价栅格,绕了一个U型弧,总长度增加不少。
原因:svm_cost_map里代价缩放系数调的太大,或者概率输出没有经过clip,导致某个栅格的代价异常高。A*的代价累加特性会放大这种离群值,让它宁可绕十个普通栅格也不肯踏过这一个高代价栅格。
解决:检查代价地图的最大值和分布,正常范围应该是1到3之间,如果出现超过5的值,说明概率输出或者缩放系数出了问题。遇到真正的静态障碍物,应该直接把栅格标记为不可通行,而不是给一个很大的代价让它“别走”。代价地图的作用是引导路径避开拥挤区域,不是模拟障碍物。
6. 验证这套组合方案值不值得用:三个端到端指标和参数联动调整
6.1 三个端到端指标:规划时间比、路径长度损失率、代价预测准确度
分区路径规划和全局最优路径相比,一定会有路径长度损失,这个损失换来的收益必须用三个指标说清楚。第一个是规划时间比,拿完整分区规划的总时间除以直接在原地图上跑全局A*的时间,比值小于0.5说明分区策略有价值,比值大于1就说明K选小了或者顶层贪心逻辑太复杂。第二个是路径长度损失率,用(分区规划路径长度减全局最优路径长度)除以全局最优路径长度,这个值超过10%就需要调整K或者重叠带宽度。第三个是代价预测准确度,在验证集上计算SVM分类的F1分数,同时看最终路径上高代价栅格占比,如果路径完全避开了所有高代价栅格,说明代价地图太“软”,SVM输出没有真正影响路径选择。
我在本地随机生成的不同障碍密度地图上测过几轮,K从6涨到10,规划时间比从0.6降到0.4,路径长度损失率从6%涨到9%,这类权衡趋势很稳定。三个指标最好每次都一起算,只看单一指标容易误判。
6.2 参数联动调整表
| 参数 | 影响范围 | 调整方向 |
|---|---|---|
| K值 | 分区数量,影响顶层调度规模和边界拼接损耗 | 轮廓系数给候选区间,端到端路径损失率超过10%时减小K |
| SVM的C和gamma | 决策边界的复杂度和代价地图平滑度 | 路径上高代价栅格占比过低时调大C,出现孤立高代价点噪点时调小gamma |
| 重叠带宽度expand | 路径跨区连续性和计算量 | 边界存在折角时加大到5或6,路径过度弯曲绕路时退回3 |
| 代价缩放系数 | A*对高代价区域的绕行倾向 | 路径绕行明显时把上限从3降到2,路径穿过拥挤区域时从3提到4 |
这套参数联动的规律是从一个点出发去影响整条链路。K决定分区的形状,expand决定分区之间的视觉衔接,SVM的C和gamma决定代价地图的细节纹理,代价缩放决定A*对这些纹理的敏感程度。调参顺序我一般固定为:先定K,再调expand,最后动SVM和代价缩放,一次只动一个变量,跑完三个指标再看下一步。
我最早踩的坑是只顾着把K-Means分得漂亮,轮廓系数拉到很高,结果端到端路径一测,边界消耗的收益比想象中的大得多。把评估指标从聚类纯度换成端到端路径损失率之后,很多直觉就要反过来用了。每次调整完参数,我都会把全局A*的最优路径结果当作参照物重新算一遍长度损失率,这个习惯帮我挡掉了很多拍脑袋的参数选择。希望帮到你。
本文还有配套的精品资源,点击获取