简介这份资源面向无人车路径规划方向的学习者与开发者聚焦基于人工势场法APF的避障路径规划实现适合已具备一定MATLAB基础、希望理解算法落地细节的中高级读者。压缩包共4个文件均为m脚本文件整体约3KB涵盖主程序入口与引力、斥力、角度计算等核心模块结构精简便于直接运行与逐行研读。资源围绕目标势能与障碍势能的构建、合力方向求解、位置迭代更新及碰撞检测等关键环节展开可帮助读者厘清吸引力场与排斥力场如何共同驱动无人车绕开障碍并逼近目标同时理解局部最小值等典型问题的成因与改进思路。目前已有220人学习下载适合作为课程设计、算法验证或工程原型的参考脚本便于在此基础上调整参数、替换场景并扩展优化策略。1. 从一份“最新版本程序.rar”说起人工势场法凭什么是无人车避障的入门首选如果你在搜索框里敲下“人工势场 避障 无人车 路径规划”大概率是两种情况要么正在做毕设或课程项目需要让一台仿真小车绕开障碍物走到目标点要么已经在实车上试过发现车要么在障碍物前面来回抖要么直接一头撞上去。这两种情况我都经历过所以这篇笔记不打算从“势场是什么”这种教科书定义讲起而是直接按“能跑起来、能调参数、能排错”的顺序把人工势场法Artificial Potential FieldAPF在无人车避障里的落地路径拆开讲。人工势场法的核心思想其实很朴素把目标点当成一个“吸引源”把障碍物当成“排斥源”无人车在两者叠加出来的虚拟力场里沿着合力方向走。它的吸引力在于实现简单、计算量小、实时性好特别适合算力有限的嵌入式平台或者需要快速验证避障逻辑的仿真环境。但它的坑也同样出名——局部极小值、目标不可达、窄通道震荡这三个问题几乎每个用过 APF 的人都踩过。这篇笔记会围绕一份典型的“最新版本程序.rar”里可能包含的仿真工程结构把建模、参数整定、代码实现和避坑经验串起来适合刚接触路径规划的新手照着复现也适合已经跑通过但效果不理想的熟手对照排查。2. 人工势场法建模引力、斥力与合力到底怎么算2.1 引力场与斥力场的数学形式选择人工势场法最常见的引力场形式是二次型当无人车距离目标点较远时引力大小与距离成正比距离很近时为了避免引力过大导致超调也有用锥形势场的做法。我一般会先用二次型引力场把基本逻辑跑通因为它的梯度连续求导后得到的引力方向稳定不会在目标点附近产生突变。斥力场则要复杂一些。经典形式是当无人车进入障碍物影响半径之后斥力随距离减小而急剧增大距离越近斥力越大直到无穷大——这在数学上保证了不会撞上障碍物但在离散仿真里会直接导致数值爆炸。所以实际写代码时几乎所有人都会给斥力设一个上限或者把障碍物影响半径设得比安全距离大一圈让车在真正贴近之前就已经被推开。下面这段 Python 代码展示了一个最简二维引力场和斥力场的计算方式参数都是可调的后面会逐个解释。import numpy as np def attractive_force(pos, goal, k_att1.0): 二次型引力场F_att k_att * (goal - pos) pos: 无人车当前位置 [x, y] goal: 目标点位置 [x, y] k_att: 引力增益系数 return k_att * (goal - pos) def repulsive_force(pos, obstacles, k_rep100.0, rho_02.0): 经典斥力场F_rep k_rep * (1/rho - 1/rho_0) * (1/rho^2) * grad_rho obstacles: 障碍物位置列表 [[x1,y1], [x2,y2], ...] k_rep: 斥力增益系数 rho_0: 障碍物影响半径 f_rep np.zeros(2) for obs in obstacles: diff pos - obs rho np.linalg.norm(diff) if rho rho_0 and rho 1e-6: # 斥力方向为障碍物指向无人车 direction diff / rho magnitude k_rep * (1.0/rho - 1.0/rho_0) * (1.0/rho**2) f_rep magnitude * direction return f_rep def total_force(pos, goal, obstacles, k_att1.0, k_rep100.0, rho_02.0): 合力 引力 斥力 return attractive_force(pos, goal, k_att) repulsive_force(pos, obstacles, k_rep, rho_0)这段代码里k_att控制车往目标点走的“急切程度”k_rep控制避障的“强硬程度”rho_0决定障碍物在多远开始影响车辆。三个参数没有绝对最优值必须结合地图尺度和车辆速度来调。我一般会先把k_att设为 1.0rho_0设为障碍物最大外接圆半径的 1.5 到 2 倍然后从小到大调k_rep直到车能稳定绕开障碍物又不至于在远处就被“弹开”。2.2 合力方向与运动学约束的衔接算出合力之后下一步是把力方向转成车辆能执行的控制量。仿真里常见两种做法一种是直接把合力方向当作速度方向速度大小固定或按距离目标远近调整另一种是把合力当作加速度再积分出速度和位置。前者简单适合差速小车后者更接近真实动力学但需要处理速度限幅和转向角限制。我一般会在仿真里用第一种方式快速验证势场参数因为它的因果关系最直接——力指向哪车就往哪走。等参数稳定了再换成加速度模型加上最大速度和最大角速度约束看避障效果是否还能保持。这一步很关键很多在纯运动学模型下跑得很好的参数一加动力学约束就翻车原因通常是斥力变化太快车辆转向跟不上导致在障碍物边缘来回震荡。提示如果你的无人车模型有最小转弯半径斥力方向突变时车可能来不及响应建议对合力方向做一阶低通滤波或者限制相邻两帧之间的方向变化率。3. 用 Python 在本地跑通人工势场避障的最小闭环3.1 搭建仿真环境与障碍物地图要复现一份“最新版本程序.rar”里的避障效果第一步不是急着看主程序而是先把地图和障碍物配置搞清楚。常见的地图有两种栅格地图和连续坐标地图。人工势场法在连续坐标下计算更自然所以我一般用连续坐标定义障碍物再用 matplotlib 画出来做可视化。下面这段代码定义了一个 20x20 的仿真区域目标点在 (18, 18)障碍物用圆形表示位置和半径都可以改。import matplotlib.pyplot as plt # 仿真区域 map_size 20.0 goal np.array([18.0, 18.0]) start np.array([1.0, 1.0]) # 障碍物列表[x, y, radius] obstacles [ [5.0, 5.0, 1.5], [8.0, 12.0, 2.0], [12.0, 6.0, 1.8], [14.0, 14.0, 1.2], [10.0, 10.0, 1.0], ] def plot_scene(ax, pathNone): ax.set_xlim(0, map_size) ax.set_ylim(0, map_size) ax.set_aspect(equal) ax.plot(goal[0], goal[1], g*, markersize15, labelGoal) ax.plot(start[0], start[1], bo, markersize10, labelStart) for obs in obstacles: circle plt.Circle((obs[0], obs[1]), obs[2], colorr, alpha0.5) ax.add_patch(circle) if path is not None: path np.array(path) ax.plot(path[:, 0], path[:, 1], b-, linewidth1.5, labelPath) ax.legend() ax.grid(True)这段代码只做可视化不参与势场计算。把障碍物画出来之后你就能直观看到哪些位置容易形成局部极小值——比如两个障碍物中间夹着的区域或者障碍物正好挡在目标点正前方的场景。3.2 主循环从起点到目标点的迭代逻辑主循环要做的事情很固定每一步计算当前位置的合力沿合力方向移动一个步长判断是否到达目标点或是否超时。步长和最大迭代次数是两个关键参数步长太大会在障碍物附近震荡步长太小则收敛慢。def run_apf(start, goal, obstacles, step_size0.2, max_iter2000, k_att1.0, k_rep80.0, rho_02.5, goal_threshold0.3): pos np.array(start, dtypefloat) path [pos.copy()] for i in range(max_iter): if np.linalg.norm(pos - goal) goal_threshold: print(fReached goal in {i} steps) break f_total total_force(pos, goal, obstacles, k_att, k_rep, rho_0) if np.linalg.norm(f_total) 1e-6: print(fStuck at {pos} due to zero net force) break direction f_total / np.linalg.norm(f_total) pos pos step_size * direction # 边界处理撞到地图边界就停住 pos np.clip(pos, 0, map_size) path.append(pos.copy()) else: print(Max iterations reached without reaching goal) return path这里step_size0.2是我在 20x20 地图上常用的值对应大约 100 步走完全程。goal_threshold0.3表示距离目标点 0.3 米以内就算到达避免在目标点附近无限震荡。max_iter2000是保险丝防止陷入局部极小值后死循环。跑完主循环后用前面的plot_scene把路径画出来你就能看到车是怎么走的。如果路径在某个位置开始打转或者停住那就是局部极小值出现了下一章会专门讲怎么处理。3.3 参数整定k_att、k_rep、rho_0 的联动关系这三个参数不是独立的。k_att越大车越“想”往目标点冲但遇到障碍物时如果k_rep不够大就会撞上去k_rep越大避障越强硬但太大又会让车在障碍物影响范围边缘就被弹开走出一条绕远路的轨迹。rho_0决定障碍物影响范围设得太小车发现障碍物太晚设得太大则多个障碍物的斥力场会叠加把车推离目标方向。我一般用下面这个顺序调参固定k_att1.0rho_0设为最大障碍物半径的 2 倍。从k_rep20开始每次翻倍观察路径。如果车撞障碍物继续加大如果车绕得太远减小。找到能稳定避障的k_rep后再微调rho_0让车在障碍物附近的行为更平滑。最后调step_size步长太大会震荡太小会慢一般取rho_0的 1/10 左右。注意如果你的障碍物半径差异很大建议对每个障碍物单独设置rho_0否则小障碍物可能被大障碍物的影响范围淹没。4. 局部极小值与目标不可达人工势场法最出名的两个坑怎么绕4.1 局部极小值的三种典型场景与检测方法局部极小值是人工势场法最常被诟病的问题。简单说就是车走到某个位置时引力和斥力刚好抵消合力为零车就停在那里不动了。最常见的三种场景是障碍物正对目标点、两个障碍物形成窄通道、U 型障碍物包围目标点。检测局部极小值的方法很直接如果连续多步位置变化小于某个阈值或者合力模长持续小于某个阈值就判定为陷入局部极小值。下面这段代码在主循环里加了检测逻辑。def run_apf_with_detection(start, goal, obstacles, step_size0.2, max_iter2000, k_att1.0, k_rep80.0, rho_02.5, goal_threshold0.3, stuck_threshold0.01, stuck_patience20): pos np.array(start, dtypefloat) path [pos.copy()] stuck_count 0 for i in range(max_iter): if np.linalg.norm(pos - goal) goal_threshold: print(fReached goal in {i} steps) break f_total total_force(pos, goal, obstacles, k_att, k_rep, rho_0) if np.linalg.norm(f_total) 1e-6: print(fZero force at step {i}, pos{pos}) break direction f_total / np.linalg.norm(f_total) new_pos pos step_size * direction new_pos np.clip(new_pos, 0, map_size) if np.linalg.norm(new_pos - pos) stuck_threshold: stuck_count 1 else: stuck_count 0 if stuck_count stuck_patience: print(fStuck detected at step {i}, pos{pos}) break pos new_pos path.append(pos.copy()) return pathstuck_threshold0.01表示一步移动距离小于 1 厘米就认为没动stuck_patience20表示连续 20 步没动就判定卡住。这两个值可以根据步长调整一般stuck_threshold取步长的 1/10 到 1/20。4.2 用虚拟目标点法跳出局部极小值检测到局部极小值之后常见的处理方式有几种随机扰动、模拟退火、虚拟目标点法。我比较常用的是虚拟目标点法因为它实现简单效果也稳定。思路是当检测到卡住时在车当前位置附近找一个临时目标点让车先朝那个方向走一段脱离极小值区域后再切回真实目标点。下面是一个简化版的虚拟目标点生成逻辑沿垂直于“车到目标点”方向随机选一侧距离取rho_0的 2 倍。def generate_virtual_goal(pos, goal, rho_0, sideNone): 在垂直于 pos-goal 的方向上生成虚拟目标点 side: 1 或 -1None 表示随机选 to_goal goal - pos dist np.linalg.norm(to_goal) if dist 1e-6: return goal to_goal_unit to_goal / dist perp np.array([-to_goal_unit[1], to_goal_unit[0]]) if side is None: side np.random.choice([-1, 1]) virtual_goal pos side * perp * rho_0 * 2.0 virtual_goal np.clip(virtual_goal, 0, map_size) return virtual_goal在主循环里一旦stuck_count超过阈值就把goal临时替换成virtual_goal同时把goal_threshold调大一些让车更容易“到达”虚拟目标。到达虚拟目标后再切回真实目标。这个逻辑需要加一个状态标志避免在虚拟目标和真实目标之间反复切换。4.3 目标不可达问题的成因与改进步长策略目标不可达通常发生在目标点附近有障碍物的情况。因为斥力在障碍物附近趋于无穷大如果目标点离障碍物太近车在接近目标点时会被斥力推走永远到不了。解决思路有两种一是修改斥力函数让它在目标点附近衰减二是当车进入目标点一定范围后直接忽略斥力只受引力控制。我一般用第二种简单粗暴但有效。在total_force里加一个判断如果np.linalg.norm(pos - goal) goal_threshold * 3就只返回引力。这样车在最后接近阶段不会被障碍物斥力干扰能稳定到达目标点。提示如果你的应用场景要求车必须精确到达目标点建议把目标点附近的障碍物影响半径单独调小或者把目标点选在障碍物影响范围之外。5. 避坑与排查人工势场避障最常见的五个翻车现场5.1 车在障碍物前面高频抖动现象车在障碍物影响范围边缘来回小幅震荡路径画出来像锯齿。原因斥力随距离变化太剧烈步长又相对较大导致车一步进入斥力区、下一步又被推出来反复横跳。解决减小步长或者对合力方向做低通滤波。我一般会加一个方向平滑direction 0.7 * direction_prev 0.3 * direction_new再归一化。这样车不会因为单步斥力突变而猛打方向。5.2 车绕过第一个障碍物后直接冲向第二个现象避障逻辑只对最近的一个障碍物生效车绕开一个之后没有及时响应下一个。原因斥力计算时只取了最近的障碍物或者rho_0设得太小车发现第二个障碍物时已经太晚。解决斥力要对所有在影响范围内的障碍物求和不能只取最近的一个。另外rho_0要至少覆盖车辆一个步长能移动的距离否则车会在“发现障碍物”和“撞上障碍物”之间没有足够的反应时间。5.3 路径在目标点附近画圈现象车接近目标点后不停止绕着目标点转圈。原因引力在目标点附近变小但斥力仍然存在合力方向偏离目标点车就绕着走。解决在目标点附近切换控制策略比如距离小于某个阈值时直接朝目标点直线走忽略斥力。或者把goal_threshold调大让车在进入目标点一定范围后就判定到达。5.4 参数换一张地图就失效现象在 A 地图上调好的k_rep和rho_0换到 B 地图后车要么撞障碍物要么绕远路。原因人工势场法对地图尺度敏感障碍物密度和半径变化后原来的参数不再适用。解决把参数和地图特征绑定。我一般会按最大障碍物半径来设rho_0按障碍物平均间距来调k_rep。如果地图变化频繁建议加一个简单的自适应逻辑根据当前障碍物密度动态调整k_rep。5.5 仿真能跑但实车不动现象仿真里路径很漂亮烧到实车上之后车要么不动要么乱转。原因仿真里用的是位置控制实车需要速度或加速度控制合力方向没有转成车辆能执行的指令。另外实车有通信延迟和传感器噪声势场计算用的位置可能滞后。解决在仿真阶段就加上速度限幅和转向角限制把合力转成线速度和角速度。实车调试时先把速度降到很低确认避障逻辑正确后再逐步提速。传感器噪声大的话对障碍物位置做滑动平均滤波。6. 从仿真到实车人工势场法还能怎么用得更稳如果你已经把前面的仿真跑通并且用虚拟目标点法解决了局部极小值下一步可以考虑几个进阶方向。第一个是动态障碍物处理把障碍物速度纳入斥力计算让斥力方向不仅考虑当前位置还考虑相对运动趋势。这需要在斥力函数里加一个速度项实现起来不复杂但能明显改善对移动障碍物的避让效果。第二个是势场参数的自适应调整。固定参数很难同时适应空旷区域和密集障碍物区域我一般会用一个简单的规则统计车辆前方一定范围内的障碍物数量数量多时增大k_rep、减小rho_0数量少时反过来。这样车在空旷区域走得快在密集区域走得稳。第三个是把人工势场法和其他规划器结合。纯 APF 适合做局部避障全局路径可以用 A* 或 RRT 先规划一条粗略路径再用 APF 做局部修正和动态避障。这种混合方案在实车上比纯 APF 稳定得多也是目前比较常见的做法。验证方法上我习惯用一组固定场景做回归测试单障碍物、双障碍物窄通道、U 型障碍物、动态障碍物横穿。每次改完参数或逻辑都跑一遍这组场景看路径长度、是否到达目标、是否撞障碍物。这三个指标能覆盖大部分问题。最后说一个我自己的教训早期做 APF 的时候我总想把参数调到“完美”结果在一个场景下调好了换个场景就翻车。后来才明白人工势场法本身就是一个局部方法它的边界就在这里——不要指望它解决所有避障问题把它放在合适的场景里配合全局规划和动态调整才能发挥最大价值。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?