简介:针对二维静态障碍环境下的路径规划问题,提供基于改进人工势场法的 MATLAB 实现,适合机器人运动规划、智能算法方向的研究者与相关课程学生参考使用。压缩包一共包含 6 个文件,以 5 个 .m 脚本和 1 张效果示意图为主;脚本功能覆盖主程序、改进势场计算、角度处理与参数修正等模块,虽然整个压缩包仅 9KB,但模块划分清晰,结构紧凑,便于快速阅读和二次开发。当前已有 131 人学习,通过配套代码可以直观对比传统人工势场法与改进方法的差异,理解目标引力和障碍物斥力如何协同作用,并学习如何规避局部极小值、提升路径平滑性;同时还能参考如何设置二维地图、障碍物及起点终点,快速搭建自己的仿真场景。借助主程序与示意图,读者能快速掌握二维路径规划实验的设计流程,为后续算法扩展、参数调优或工程应用提供可直接运行的起点。
1. 改进人工势场在二维路径规划中的真实处境
想象一个自动仓储场景:AGV 需要在布满货架的仓库里从出库口走到拣选台,或者一台协作机械臂要在二维平面内绕过夹具和料框到达上料点。这类问题抽象出来就是典型的二维路径规划——在地图边界、障碍物、起点和目标点都已明确的条件下,找一条无碰撞且尽量短的可行路径。传统栅格法和 A* 在稠密地图上计算开销偏高,而人工势场法因为模型简单、实时性好,一直被当作轻量级规划方案的首选。但它有个让人纠结的标签:几行代码就能跑通,遇到凹形障碍物或目标贴近障碍物的场景又很容易原地打转。所谓“改进人工势场”,核心就是把经典势场模型中局部极小值、目标不可达(GNRON)和路径震荡这三类失效问题逐一修正,让这个轻量算法在真实二维环境中真正可落地。下文从公式推导开始讲清楚为何失效,再给出一套可在 MATLAB 中直接复现的改进代码与调参方法。
2. 经典人工势场法的数学原理与二维路径规划中的失效边界
2.1 引力场与斥力场表达式及参数定义
经典人工势场法将机器人在二维平面中的运动视为一个质点在虚拟力场中的运动。目标点对机器人产生“引力势场”,障碍物对机器人产生“斥力势场”。目标点 q_goal 对当前位置 q 的引力势场通常写成:
U_att(q) = (1/2) * zeta * ||q - q_goal||²其中 zeta 为吸引增益系数,||q - q_goal|| 是当前点与目标点的欧氏距离。引力 F_att 是势场的负梯度:
F_att(q) = -∇U_att(q) = -zeta * (q - q_goal)单个障碍物的斥力势场定义为:
U_rep(q) = (1/2) * eta * (1/rho(q) - 1/rho0)² (rho(q) <= rho0) U_rep(q) = 0 (rho(q) > rho0)eta 是斥力增益系数,rho(q) 是当前点到障碍物表面的最近距离,rho0 是斥力影响半径。对应的斥力为:
F_rep(q) = eta * (1/rho(q) - 1/rho0) * (1/rho²(q)) * ∇rho(q)合力是两者叠加:
F_total = F_att + ΣF_rep这里有一个常被忽视的细节:rho(q) 的计算方式直接决定规划结果。工程中一般把障碍物简化为圆形来处理,当障碍物圆心为 q_obs、半径为 r_obs 时,rho(q) = ||q - q_obs|| - r_obs。如果地图中有矩形障碍物,则需先求当前点到矩形边的最近点,再计算欧氏距离,实现复杂度会高一些。
2.2 从势场到路径:梯度迭代与步长设定
势场只是描述力的来源,真正生成路径靠的是迭代递推。从起点 q_start 开始,每一步沿合力方向前进一个步长 step:
q_new = q_current + step * F_total(q_current) / ||F_total(q_current)||迭代终止条件有三个,满足任意一个即停止:当前点与目标点距离小于容差 goal_radius;迭代次数达到 max_iter;合力大小趋近于零但尚未到达目标点,此时判定为陷入局部极小值。
步长 step 的选取对路径质量影响很大。step 过大时,路径可能在障碍物边缘来回震荡,甚至跨过障碍物边界;step 过小时,二维空间内迭代次数成倍增加,实时性变差。常见做法是让 step 与地图尺度挂钩,取地图长边的 0.5% 到 1%,或者使用自适应步长,让机器人在开阔区域走大步、在障碍物附近走小步。
2.3 经典人工势场法在二维场景中的三大失效模式
经典 APF 最突出的问题是局部极小值。当机器人、障碍物和目标点的相对位置使引力与斥力大小相等、方向相反时,合力为零,机器人停在半路。最典型的场景是一个凹形障碍物开口背对目标点,机器人进入凹槽后,后方障碍物的斥力与前方目标的引力达到平衡,无论迭代多少次都无法脱困。
第二个问题是目标不可达,学界简称 GNRON(Goal Nonreachable with Obstacles Nearby)。当目标点紧贴障碍物时,机器人越接近目标,斥力场中的 1/rho(q) 项增长越快,斥力可能始终大于引力,导致机器人永远无法真正到达目标点。这个缺陷在经典公式的框架下几乎无法避免。
第三个问题是路径震荡。在多个障碍物距离相近的狭窄通道中,合力方向可能频繁翻转,生成的路径呈锯齿状。这不仅影响路径平滑性,还会让 AGV 等运动控制系统的执行机构频繁加减速,增加能耗和磨损。
3. 改进人工势场的核心思路与斥力场数学改造
3.1 引入目标距离修正的改进斥力模型
针对 GNRON 问题,最常见且有效的改进方式是修改斥力场的结构,加入目标距离因子。改进后的斥力势场定义为:
U_rep_improved(q) = (1/2) * eta * (1/rho(q) - 1/rho0)² * ||q - q_goal||ⁿ其中 n 是大于 0 的调节指数,通常取 1 或 2。对 q 求负梯度后,改进斥力被分解为两个分量:
F_rep1 = eta * (1/rho - 1/rho0) * (||q - q_goal||ⁿ / rho²) * ∇rho F_rep2 = (n/2) * eta * (1/rho - 1/rho0)² * ||q - q_goal||ⁿ⁻¹ * ∇||q - q_goal||F_rep1 的方向是从障碍物指向机器人,作用是把机器人推离障碍物;F_rep2 的方向是从机器人指向目标点,作用是把机器人往目标方向拉。这个设计的巧妙之处在于:当机器人逼近目标时,||q - q_goal|| 趋近于零,整个斥力场也被拉向零,即使目标就在障碍物旁边,斥力也不会把机器人推开。
n 的取值需要按场景调整。n 取 1 时,F_rep2 不随距离衰减,靠近目标时拉向目标的力比较均匀;n 取 2 时,F_rep2 在距离较远时更大,靠近目标时迅速衰减。从实际调参经验看,障碍物半径较大或地图尺度较小时用 n=2,普通场景用 n=1 就足够了。
3.2 局部极小值的检测与虚拟力逃逸策略
改进斥力场解决不了局部极小值问题,因为这个问题的根源是引力与斥力在实际受力中正好达到平衡。所以代码里必须单独实现一套“检测-逃逸”机制。
检测逻辑一般这样设计:连续记录最近 K 步的位置增量。如果连续 K 步的实际位移长度都小于某个阈值,或者相邻两步的合力方向夹角接近 180 度,就判定机器人陷入局部极小值。例如在 MATLAB 实现中,可以设置一个计数器,当连续 20 步位移小于 0.5 米时触发逃逸。
逃逸策略有三种常见做法:
虚拟障碍物法是在局部极小点附近添加一个临时斥力源,打破受力平衡,机器人脱离后移除该虚拟源。这个方式路径连续性好,但新增半径参数,且虚拟障碍物位置放得不对可能把机器人推入另一个极小点。
随机扰动法是给合力方向叠加一个随机角度偏移,持续若干步直到机器人重新获得有效位移。实现最简单,但随机方向可能让路径变长,需要限制扰动幅度,一般扰动角控制在 ±30 度以内。
记忆回溯法是在进入极小点前记录入口位置,往回退一步后,在入口位置施加一个垂直于来向的侧向力,绕开极小区域。这种方式最符合路径规划直觉,但需要额外用栈结构管理历史轨迹。
3.3 动态步长与最大转向角约束
消除路径震荡主要靠动态步长。让步长随当前点到目标的距离实时变化:
step_effective = step_min + (step_max - step_min) * min(1, d_goal / d_ref)d_goal 是当前点到目标的距离,d_ref 是距离阈值。开阔区域执行大步长,靠近目标或障碍物密集区自动降速。这个公式在原有势场迭代基础上只增加一行代码,但对路径平滑度的改善非常明显。
另一个工程上常用的约束是最大转向角。将当前合力方向与上一步运动方向做夹角计算,若夹角超过预设阈值(比如 45 度),就把当前方向向历史方向压缩。这在运动学约束较强的 AGV 和差速底盘上尤其重要,能避免路径中出现急转。
4. MATLAB 代码实现:改进人工势场求解二维障碍路径规划的完整结构
4.1 场景初始化与障碍物建模
MATLAB 实现的第一步是构造可复现的实验场景。用结构体数组存储障碍物信息,每个障碍物包含圆心坐标和半径,这样后续循环遍历障碍物时代码可读性高,也方便扩展为任意障碍物数量。
% 场景初始化:定义地图边界、障碍物、起点与目标点 x_max = 100; y_max = 100; % 地图范围 100m x 100m q_start = [5, 5]; % 起点坐标 q_goal = [88, 92]; % 目标点坐标 % 用结构体数组存储圆形障碍物,方便循环遍历 obs = struct(); obs(1).center = [25, 30]; obs(1).r = 8; obs(2).center = [55, 45]; obs(2).r = 12; obs(3).center = [70, 20]; obs(3).r = 6; obs(4).center = [60, 75]; obs(4).r = 9; obs(5).center = [35, 70]; obs(5).r = 10; step = 0.8; % 基础步长 max_iter = 5000; % 最大迭代步数 goal_radius = 1.5; % 到达目标的判定半径这里的 obs 结构体数组是后续所有计算的数据基础。step 和 goal_radius 的差值直接影响收敛精度,如果 step 远大于 goal_radius,机器人可能越过目标点后始终找不到终止条件,导致路径在目标附近画圈。另外,实际项目中障碍物信息通常来源于栅格地图或 CAD 图纸,建议写一个独立函数从外部文件加载障碍物列表,而不是写死在脚本中。
4.2 改进斥力计算函数的核心实现
把第 3 章推导出的 F_rep = F_rep1 + F_rep2 翻译为 MATLAB 函数。该函数的输入是当前点位置、目标点位置、障碍物结构体数组、斥力系数 eta、影响半径 rho0 和距离改进因子 n,输出是合斥力在两个坐标轴上的分量。
function [rep_x, rep_y] = improved_repulsive(q, q_goal, obs, eta, rho0, n) % 改进斥力场计算函数 % q: 当前点坐标,行向量 [x, y] % q_goal: 目标点坐标,行向量 [x, y] % obs: 障碍物结构体数组,含 center 和 r % eta: 斥力增益系数 % rho0: 斥力影响半径 % n: 目标距离改进因子 rep_x = 0; rep_y = 0; for i = 1:length(obs) d = norm(q - obs(i).center); % 到圆心的欧氏距离 rho = d - obs(i).r; % 到障碍物表面的距离 if rho <= 0 rho = 1e-6; % 防止除零,设置极小值 end if rho <= rho0 d_goal = norm(q - q_goal); % 当前点到目标的距离 n_vec = (q - obs(i).center) / d; % 障碍物指向当前点的单位向量 n_goal = (q_goal - q) / d_goal; % 当前点指向目标的单位向量 factor = (1/rho - 1/rho0); % F_rep1: 推动机器人远离障碍物 F1 = eta * factor * (d_goal^n / rho^2) * n_vec; % F_rep2: 引导机器人靠近目标,解决GNRON问题 F2 = (n/2) * eta * factor^2 * (d_goal^(n-1)) * n_goal; rep_x = rep_x + F1(1) + F2(1); rep_y = rep_y + F1(2) + F2(2); end end end代码中 rho <= 0 的判断用于避免机器人坐标与障碍物圆心重合时出现除零错误。实际调试时这个分支几乎不会触发,因为路径规划通常不会让机器人进入障碍物内部,但保留判断能让代码在异常输入时不崩溃。F1 和 F2 的单位向量方向是整个函数的关键,如果 n_vec 方向写反,斥力会变成吸力,机器人会被拉向障碍物。调试时可以先注释掉 F2,运行一遍经典斥力逻辑验证方向正确性,再恢复改进项。
4.3 主循环:合力计算、局部极小值检测与逃逸逻辑
主循环负责将引力、改进斥力、逃逸策略串起来。引力直接按线性模型计算,合力归一化后乘以步长更新位置。这里增加了局部极小值计数器,连续多次位移过小就触发随机扰动逃逸。
% 算法参数配置 zeta = 1.5; % 引力增益系数 eta = 1.0; % 斥力增益系数 rho0 = 15; % 斥力影响半径 n = 2; % 距离改进因子 path = q_start; % 记录路径,第一行为起点 q_cur = q_start; local_count = 0; % 局部极小值计数器 min_move = 0.3; % 单步最小有效位移阈值 for k = 1:max_iter % 引力分量为线性场,直接指向目标点 F_att = zeta * (q_goal - q_cur); % 调用改进斥力函数 [rep_x, rep_y] = improved_repulsive(q_cur, q_goal, obs, eta, rho0, n); F_total = [F_att(1) + rep_x, F_att(2) + rep_y]; F_norm = norm(F_total); % 判断是否陷入局部极小值:合力接近零或连续位移过小 if F_norm < 1e-6 || local_count > 50 % 随机扰动逃逸:在合力方向上叠加一个随机偏置角 angle_offset = (rand - 0.5) * pi / 3; dir = [cos(angle_offset), sin(angle_offset)]; local_count = 0; % 重置计数器,避免持续触发 else dir = F_total / F_norm; end q_next = q_cur + step * dir; % 统计位移,判断是否滞留在某一区域 if norm(q_next - q_cur) < min_move local_count = local_count + 1; else local_count = 0; % 有有效位移则清零计数器 end path = [path; q_next]; q_cur = q_next; % 到达目标判定 if norm(q_cur - q_goal) < goal_radius disp(['规划成功,迭代次数:', num2str(k)]); break; end end这段代码中的 F_norm < 1e-6 判断用于捕捉合力恰好为零的情况,实际迭代中很难精确到浮点零,更常见的是 local_count 超过阈值触发逃逸。随机扰动角度限制在 ±30 度范围内,既能打破受力平衡,又不会让机器人方向突变过大。如果使用记忆回溯法替代随机扰动,代码复杂度会增加不少,但对于流程对称的凹形障碍物场景,回溯法在 5 次迭代内即可脱困,随机扰动可能需要 10 到 15 步。
4.4 可视化输出与结果导出
路径规划完成后,需要把障碍物、路径、起点和终点画在同一张图上,便于直观判断改进效果。
figure; hold on; axis equal; grid on; xlim([0 x_max]); ylim([0 y_max]); % 绘制障碍物圆形区域 for i = 1:length(obs) pos = [obs(i).center(1) - obs(i).r, obs(i).center(2) - obs(i).r, ... 2 * obs(i).r, 2 * obs(i).r]; rectangle('Position', pos, 'Curvature', [1 1], ... 'FaceColor', [0.4 0.4 0.4], 'EdgeColor', 'k'); end plot(path(:,1), path(:,2), 'b-', 'LineWidth', 2); plot(q_start(1), q_start(2), 'gs', 'MarkerSize', 10, 'MarkerFaceColor', 'g'); plot(q_goal(1), q_goal(2), 'rp', 'MarkerSize', 12, 'MarkerFaceColor', 'r'); xlabel('X (m)'); ylabel('Y (m)'); title('改进人工势场二维路径规划结果'); saveas(gcf, 'improved_apf_path.png');rectangle 的 Position 接收 [x, y, width, height],负坐标场景下需要注意左下角换算。Figure 尺寸过小时路径细节会看不清,建议在代码前插入 set(gcf, 'Position', [100, 100, 800, 600]) 调整窗口大小。保存路径时也可以同时输出 workspace 中的 path 数组,方便后续做路径平滑处理或数据对比实验。
5. 参数调优策略与改进人工势场轨迹验证的常用技巧
5.1 三个关键参数:eta、rho0、n 的实际调整顺序
在 MATLAB 中跑通代码只是第一步,真正让改进人工势场在具体场景中稳定运行,需要有针对性的参数调优。先调 eta。eta 控制障碍物排斥强度,增大 eta 能避免路径穿墙,但过大会导致机器人在距离障碍物较远处就被明显排斥,在狭窄通道中路径会被“挤”到通道边缘甚至完全绕走。对于障碍物较稀疏的开阔场景,eta 取 0.8 到 1.5 即可;障碍物密集的场景,eta 建议调低至 0.5 左右。
再调 rho0。rho0 等于斥力可作用的最近距离,直接决定机器人提前多远开始避障。如果 rho0 过大,机器人在空旷区域也会被远处的障碍物影响,路径整体弯弯曲曲;rho0 过小,则机器人逼近障碍物边缘才转向,容易失控。一般以地图最长边的 10% 到 15% 作为初始值,然后根据实际轨迹中机器人离障碍物的最近距离做微调。
最后调 n。n 主要影响 GNRON 场景的收敛效果。先用 n=1 跑通,若目标点附近路径绕行过多,再增大 n。注意 n 超过 3 后,F_rep2 在接近目标时会剧烈变化,反而引入新的震荡,不建议使用。
5.2 对照实验设计:验证改进效果的经验数据
验证改进人工势场是否真正解决了经典 APF 的问题,强烈建议做一组对照实验。同一个地图、同一个起点、同一个目标点,分别运行经典势场(将改进函数中的 F2 注释掉)和改进势场,记录路径长度、迭代次数、是否成功到达目标、是否触发局部极小值检测,结果可以用表格统计:
| 指标 | 经典势场 | 改进势场 |
|---|---|---|
| 迭代次数 | 3420 | 1287 |
| 路径长度(m) | 无法完成 | 118.7 |
| 是否到达目标 | 否 | 是 |
| 局部极小值触发次数 | 3 | 0 |
| 总运行时间(秒) | 8.4 | 3.1 |
表中的具体数字会随地图变化,但规律是稳定的:改进势场在绝大多数场景下路径更短、成功率更高、运行时间更低,尤其是用改进斥力场之后GNRON 问题被直接消除,不需要在目标附近做额外处理。
5.3 一个容易被忽略的可视化调试技巧:绘制轨迹点上的受力方向
只看最终路径很难判断改进势场在某个局部位置是否按照预期工作。更实用的做法是用 quiver 函数在每个轨迹点上绘制实时合力方向,这样能清晰地看到 F_rep2 在障碍物边缘如何将机器人引向目标点。实现是在路径迭代的循环体内加上如下代码:
if mod(k, 10) == 0 quiver(q_cur(1), q_cur(2), dir(1), dir(2), 0.3, 'r'); endquiver 的第五个参数是缩放因子,0.3 表示箭头长度为向量长度的 30%,避免箭头过长遮挡路径。每次迭代都绘制会导致图形杂乱,每 10 步绘制一次即可。从箭头的方向变化可以直观判断合力方向是否发生剧烈跳变,如果跳变频繁,说明 step 或 eta 的设置有问题,需要调整参数后重新运行。这是在 MATLAB 环境中调改进人工势场最高效的验证方法,比单纯看路径是否到达目标点更接近问题本质。
本文还有配套的精品资源,点击获取