简介:这份资源面向无人车路径规划方向的学习者与开发者,聚焦基于人工势场法(APF)的避障算法实现,适合已具备一定MATLAB基础、希望理解势场建模与避障逻辑的中级读者。压缩包共4个文件,均为m脚本文件,整体约3KB,涵盖主程序入口与引力、斥力、角度计算等核心模块,结构紧凑,便于直接运行与逐行调试。资源围绕吸引力场与排斥力场的构建展开,涉及势能计算、位置更新、碰撞检测与终止条件判断等关键环节,并针对局部最小值这一典型问题给出优化思路。目前已有220人学习下载。通过阅读源码注释与模块划分,读者可掌握势场函数的实现方式、梯度下降更新位置的流程,以及将算法迁移到城市道路或室内场景的排错方法,为后续引入模糊逻辑、遗传算法等改进策略打下基础。
1. 人工势场避障无人车:从一份 rar 包到能跑起来的路径规划
你拿到一个叫「最新版本程序.rar」的压缩包,解压后大概率是一堆 MATLAB 脚本或者 C++ 源码,核心算法写着人工势场(Artificial Potential Field,APF)。这东西在无人车避障和路径规划里属于入门级经典算法,但真正上车跑起来,翻车概率比想象中高得多。它解决的核心问题很直接:让小车在往目标点走的过程中,自动绕开障碍物。适合谁?做 AGV 路径规划、ROS 小车避障、无人机三维路径规划的学生和工程师,尤其是那些拿到一份祖传代码但不知道怎么调参、怎么改、怎么验证的人。这篇笔记就按「这份 rar 里大概是什么 → 怎么跑通 → 参数怎么设 → 坑在哪」的顺序拆一遍,让你拿到任何一份人工势场避障代码都能快速上手。
2. 人工势场法到底在算什么:引力、斥力与合力
2.1 引力场和斥力场的基本公式
人工势场法的核心思想不复杂:目标点对小车产生引力,障碍物对小车产生斥力,两者叠加得到一个势场,小车沿着势场下降方向走。常见做法是定义引力势场 ( U_{att} ) 和斥力势场 ( U_{rep} ),然后求负梯度得到受力。
引力势场一般用二次型:
[ U_{att}(q) = \frac{1}{2} k_{att} \cdot \rho_{goal}^2(q) ]
其中 ( k_{att} ) 是引力增益系数,( \rho_{goal}(q) ) 是小车当前位置到目标点的距离。对 ( q ) 求负梯度得到引力:
[ F_{att}(q) = -k_{att} \cdot \rho_{goal}(q) \cdot \nabla \rho_{goal}(q) ]
方向指向目标点,大小与距离成正比。这意味着离目标越远,拉力越大,小车会加速冲向目标。
斥力势场常见形式是:
[ U_{rep}(q) = \begin{cases} \frac{1}{2} k_{rep} \left( \frac{1}{\rho_{obs}(q)} - \frac{1}{\rho_0} \right)^2 & \rho_{obs}(q) \leq \rho_0 \ 0 & \rho_{obs}(q) > \rho_0 \end{cases} ]
( k_{rep} ) 是斥力增益,( \rho_{obs}(q) ) 是到障碍物的最近距离,( \rho_0 ) 是障碍物影响半径。只有进入这个半径,斥力才起作用。对 ( q ) 求负梯度得到斥力,方向背离障碍物。
合力 ( F_{total} = F_{att} + F_{rep} ),小车沿着合力方向以一定步长移动。这就是最基础的 APF 避障逻辑。
2.2 从公式到代码:一份典型 MATLAB 实现拆解
拿到 rar 包后,你大概率会看到类似下面的主循环。我以最常见的二维无人车避障为例,把关键步骤拆开。
% 人工势场法二维避障主循环 % 参数初始化 k_att = 1.0; % 引力增益 k_rep = 100; % 斥力增益 rho_0 = 2.0; % 障碍物影响半径 step = 0.1; % 移动步长 max_iter = 2000; % 最大迭代次数 goal = [10, 10]; % 目标点 pos = [0, 0]; % 起点 obstacles = [3,3; 5,5; 7,4; 6,8]; % 障碍物坐标列表 path = pos; % 记录路径 for iter = 1:max_iter % 计算引力 vec_att = goal - pos; dist_att = norm(vec_att); if dist_att < 0.1 break; % 到达目标 end F_att = k_att * vec_att; % 引力向量 % 计算斥力 F_rep = [0, 0]; for i = 1:size(obstacles, 1) vec_obs = pos - obstacles(i, :); dist_obs = norm(vec_obs); if dist_obs <= rho_0 % 斥力大小 rep_mag = k_rep * (1/dist_obs - 1/rho_0) * (1/dist_obs^2); F_rep = F_rep + rep_mag * (vec_obs / dist_obs); end end % 合力与移动 F_total = F_att + F_rep; if norm(F_total) > 0 pos = pos + step * (F_total / norm(F_total)); end path = [path; pos]; end % 绘图 figure; plot(path(:,1), path(:,2), 'b.-'); hold on; plot(goal(1), goal(2), 'ro', 'MarkerSize', 10); plot(obstacles(:,1), obstacles(:,2), 'kx', 'MarkerSize', 10); axis equal; grid on;这段代码的逻辑说明:先算目标点产生的引力向量,再遍历所有障碍物算斥力向量,叠加后归一化,按固定步长移动。参数说明:k_att控制向目标的吸引力强度,太大会冲过头,太小会走得太慢;k_rep控制避障力度,太大会在障碍物附近震荡,太小会撞上去;rho_0决定多远开始避障,一般设为小车尺寸的 2~3 倍;step是每步移动距离,影响路径平滑度和计算量。
2.3 为什么你的小车会在障碍物前震荡
这是人工势场法最经典的翻车场景:小车在障碍物附近来回抖动,走不过去。原因通常是引力分量和斥力分量在某个位置大小相等方向相反,合力为零,形成局部极小点。或者斥力增益太大,小车被推得太远,下一步引力又拉回来,来回震荡。
常见做法是引入「逃逸力」或者给斥力加一个切向分量。我一般会在斥力计算里加一个垂直于斥力方向的切向力,让小车绕着障碍物走而不是正面硬刚。具体做法是在斥力向量上旋转 90 度,乘以一个较小的系数,叠加到合力里。这样即使合力为零,切向分量也能把小车推出去。
另一个办法是动态调整步长:当合力很小时,增大步长或者随机扰动一下位置。但随机扰动会导致路径不光滑,适合仿真验证,不适合实车。
3. 把 rar 包跑起来:环境配置与最小验证流程
3.1 解压后先看什么:文件结构与依赖判断
拿到「最新版本程序.rar」后,别急着运行。先看目录结构。常见情况有三种:纯 MATLAB 脚本(.m 文件)、MATLAB 加 Simulink 模型(.slx)、C++ 源码加 CMakeLists。如果是 MATLAB,先确认有没有用到 Robotics Toolbox 或 Navigation Toolbox,这些工具箱不是默认安装的。如果是 C++,看有没有 Eigen、OpenCV 或者 ROS 依赖。
我一般会先找主入口文件,通常是main.m、main.cpp或者run_*.m。然后看有没有 README 或者注释里的运行说明。如果没有,就按文件名猜:带apf、potential、avoid的大概率是核心算法,带test、demo、sim的是测试脚本。
提示:如果 rar 包里有一堆 .asv 文件(MATLAB 自动保存),说明作者调试过很多次,核心逻辑大概率在最近修改的那个文件里。
3.2 MATLAB 版本的最小运行命令与参数初始化
假设你确认是 MATLAB 脚本,先把当前文件夹切到解压目录,然后直接运行主脚本。如果报错说缺少函数,用which命令查一下函数在不在路径里。
% 切换到解压目录 cd('D:\apf_car'); % 改成你的实际路径 % 检查关键函数是否存在 which apf_main which compute_attractive_force which compute_repulsive_force % 如果没有输出路径,说明不在搜索路径里,手动添加 addpath(genpath(pwd)); % 运行主脚本 apf_main;逻辑说明:genpath(pwd)会把当前目录及所有子目录加入搜索路径,避免因为文件夹层级导致函数找不到。参数初始化一般在主脚本开头,你需要根据实际场景改goal、start、obstacles这几个变量。如果是实车,还要改step和max_iter,仿真可以跑大一点,实车要限制迭代次数防止死循环。
3.3 用一组固定障碍物验证避障是否生效
跑通之后,别急着换复杂地图。先用一组固定障碍物验证算法是否真的能绕开。我一般会设计三个测试用例:单障碍物正对路径、两个障碍物形成窄通道、障碍物在目标点附近。这三个场景能覆盖大部分局部极小点问题。
% 测试用例1:单障碍物正对路径 obstacles = [5, 5]; goal = [10, 10]; start = [0, 0]; % 测试用例2:窄通道 obstacles = [4,4; 6,6]; goal = [10, 10]; % 测试用例3:障碍物在目标点附近 obstacles = [9,9; 9.5,9.5]; goal = [10, 10];每个用例跑完后,看路径图:小车是否绕开了障碍物、是否到达目标、路径是否平滑。如果某个用例失败,先调k_rep和rho_0,再考虑改算法。窄通道场景最容易出现震荡,因为两侧斥力叠加会把小车推来推去。
4. 参数调优与动态避障改造:让小车真的能上车
4.1 引力增益、斥力增益、影响半径的整定顺序
参数调优有顺序,别一上来就乱改。我一般按这个顺序:先调rho_0,再调k_rep,最后调k_att。
rho_0决定避障的提前量。太小,小车快到障碍物跟前才开始避,容易撞;太大,小车会过早偏离目标,路径变长。一般设为小车最大尺寸的 2~3 倍,或者障碍物半径加安全距离。
k_rep决定避障力度。太小,斥力不够,小车会蹭到障碍物;太大,小车在障碍物附近剧烈震荡。经验值是k_att的 50~200 倍,具体看障碍物密度。
k_att决定向目标的吸引力。太大,小车会高速冲向目标,遇到障碍物时来不及避;太小,小车走得慢,迭代次数不够可能到不了目标。一般设为 1.0 左右,然后根据步长调整。
注意:参数之间是耦合的。改了
step之后,k_att和k_rep都要重新调。步长越大,等效的力也越大。
4.2 动态障碍物怎么处理:速度项与预测避障
原始 APF 只考虑静态障碍物。如果障碍物在动,比如其他 AGV 或者行人,你需要把障碍物速度引入斥力计算。常见做法是在斥力公式里加一个相对速度项:
[ F_{rep} = k_{rep} \left( \frac{1}{\rho_{obs}} - \frac{1}{\rho_0} \right) \cdot \frac{1}{\rho_{obs}^2} \cdot \hat{n} + k_{vel} \cdot v_{rel} ]
其中 ( v_{rel} ) 是小车与障碍物的相对速度,( k_{vel} ) 是速度增益。这样障碍物靠近时斥力更大,远离时斥力减小,小车能更自然地避让。
% 动态障碍物斥力计算(带速度项) for i = 1:size(obstacles, 1) vec_obs = pos - obstacles(i, :); dist_obs = norm(vec_obs); if dist_obs <= rho_0 rep_mag = k_rep * (1/dist_obs - 1/rho_0) * (1/dist_obs^2); F_rep = F_rep + rep_mag * (vec_obs / dist_obs); % 速度项 v_rel = (pos - pos_prev) - obs_vel(i, :); F_rep = F_rep + k_vel * v_rel; end end逻辑说明:obs_vel是障碍物速度矩阵,每行对应一个障碍物的速度向量。pos_prev是上一步位置,用来估算小车速度。速度项让斥力不仅与距离有关,还与相对运动趋势有关。参数k_vel一般设得比k_rep小一个数量级,否则速度项会主导斥力,导致小车过度反应。
4.3 从仿真到实车:步长、控制周期与安全距离
仿真里步长可以设 0.1 甚至更小,实车不行。实车的控制周期通常是 50ms 到 100ms,步长对应的是每个周期移动的距离。如果小车速度是 0.5m/s,控制周期 100ms,那步长就是 0.05m。这个值要代入算法里,否则仿真跑得好好的,实车会冲出去。
安全距离也要重新设。仿真里rho_0可以设 2.0,实车要看传感器精度和刹车距离。激光雷达测距有噪声,rho_0要留够余量。我一般会在rho_0基础上再加 0.3~0.5m 的安全裕量,防止传感器跳变导致避障失效。
另外,实车要用里程计或者定位系统实时更新pos,不能像仿真那样直接累加。如果定位漂移,势场计算会跟着漂,小车可能往错误方向走。常见做法是每几个周期用全局定位校正一次,或者用局部避障加全局路径跟踪的混合策略。
5. 避坑与排查:人工势场避障的五个血泪教训
5.1 现象:小车在障碍物前原地转圈,不往前走
原因:局部极小点。引力指向目标,斥力背离障碍物,两者在某个位置刚好抵消,合力为零。小车没有动力往前走,但算法还在迭代,看起来就是原地转圈。
解决:加切向力或者逃逸力。我一般会在斥力向量上旋转 90 度,乘以 0.3~0.5 的系数,叠加到合力里。这样即使合力为零,切向分量也能把小车推出去。另一个办法是检测到合力小于阈值时,给一个随机方向的小扰动,但实车慎用。
5.2 现象:路径在障碍物附近剧烈抖动,像锯齿
原因:斥力增益太大,或者步长太大。斥力在障碍物附近变化剧烈,步长又大,小车一步跨过势场谷底,下一步又被推回来,形成震荡。
解决:先减小k_rep,再减小step。如果还抖,检查rho_0是不是太小,导致斥力作用范围太窄,小车进入斥力区时突然受到很大力。把rho_0调大,让斥力变化更平缓。
5.3 现象:小车绕过第一个障碍物后,直接冲向第二个
原因:引力太大,或者斥力影响半径太小。小车绕开第一个障碍物后,引力占主导,速度方向直接指向目标,没有提前感知到第二个障碍物。
解决:增大rho_0,让小车更早感知到障碍物。或者引入全局路径规划做引导,APF 只做局部避障。常见做法是用 A* 或者 RRT 先规划一条全局路径,APF 沿着全局路径走,遇到动态障碍物再局部调整。
5.4 现象:MATLAB 仿真正常,C++ 实车跑出来路径完全不对
原因:坐标系或者单位不一致。MATLAB 里可能是米,C++ 里可能是毫米;MATLAB 里 y 轴向上,实车坐标系可能 y 轴向左。这些不一致会导致势场方向完全错误。
解决:先统一单位,全部用米和弧度。然后检查坐标变换,把实车定位数据转换到和仿真一致的坐标系。我一般会写一个简单的测试:让小车只受引力作用,看它是不是直线走向目标。如果不是,坐标系肯定有问题。
5.5 现象:迭代次数到了,小车还没到目标
原因:步长太小,或者参数太保守。仿真里max_iter设 2000 可能够,实车控制周期 100ms,2000 次迭代就是 200 秒,小车可能早就该到了但还在慢慢挪。
解决:先算一下理论时间:总路径长度除以步长,再乘以控制周期。如果超过预期,要么增大步长,要么增大k_att让小车走快点。但增大步长会影响避障精度,需要折中。我一般会把max_iter设成理论值的 1.5 倍,留点余量。
6. 进阶技巧:用动态窗口法弥补人工势场的先天缺陷
人工势场法最大的问题是局部极小点和震荡,这两个缺陷在复杂场景下很难靠调参完全解决。我现在的做法是:用 APF 做粗避障,用动态窗口法(DWA)做精细速度控制。APF 输出一个期望方向,DWA 在这个方向附近采样多组速度,选一组既朝向目标又不撞障碍物的。
具体实现上,APF 每周期算一次合力方向,作为 DWA 的参考方向。DWA 的采样空间限制在这个方向左右各 30 度,速度范围根据小车动力学约束设定。评价函数里加三项:朝向目标、避障距离、速度大小。这样即使 APF 陷入局部极小点,DWA 也能通过速度采样找到出路。
% APF + DWA 混合避障伪代码 function [v, w] = hybrid_avoid(pos, goal, obstacles, params) % APF 算期望方向 F_total = compute_apf_force(pos, goal, obstacles, params); desired_angle = atan2(F_total(2), F_total(1)); % DWA 在期望方向附近采样 v_range = [0, params.v_max]; w_range = [-params.w_max, params.w_max]; best_score = -inf; best_v = 0; best_w = 0; for v = v_range(1):params.v_res:v_range(2) for w = w_range(1):params.w_res:w_range(2) % 预测轨迹 traj = predict_trajectory(pos, v, w, params.predict_time); % 评价 score = evaluate_trajectory(traj, goal, obstacles, desired_angle); if score > best_score best_score = score; best_v = v; best_w = w; end end end v = best_v; w = best_w; end逻辑说明:compute_apf_force就是前面的人工势场计算,输出合力向量。desired_angle是期望方向。DWA 在速度空间采样,预测每条轨迹,用评价函数打分。评价函数里desired_angle作为参考,让 DWA 优先选择朝向 APF 期望方向的速度。这样 APF 负责全局引导,DWA 负责局部精细避障和动力学约束。
参数方面,v_max和w_max根据小车实际能力设,predict_time一般 1~2 秒,v_res和w_res是采样分辨率,太细计算量大,太粗找不到好解。我一般用 0.05m/s 和 0.05rad/s。
这套混合策略我在几个 AGV 项目里用过,比纯 APF 稳定很多。纯 APF 在窄通道和障碍物密集场景下几乎必挂,加了 DWA 之后至少能跑通。代价是计算量增加,MATLAB 仿真可能变慢,C++ 实车要注意控制周期别超。
最后说个习惯:每次改完参数,我都会把路径图存下来,文件名带上参数值。这样调参调多了,翻回去看哪组参数对应哪条路径,不用靠记忆。这个习惯帮我省了很多后悔药。希望帮到你。
本文还有配套的精品资源,点击获取