☰
MATLAB人工势场避障:非完整约束差速小车仿真与控制
2026/10/17 8:06:05 网站建设 项目流程

简介:面向机器人控制、自动驾驶与智能导航领域研究者的Matlab人工势场避障实现,配套IEEE机器人与自动化快报(2020)发表的导航函数控制器方案。源码重点解决非完整移动机器人在已知凸形障碍物和边界约束下的无碰撞到达目标位置问题,通过平滑连续的非线性控制器驱动机器人安全导航,并可防止机器人与障碍物和边界发生碰撞,可作为运动规划课程设计或前沿算法验证的对照实现。资源包共56个文件,仅4.62MB:32个.m文件覆盖核心避障算法与位置控制,8个.py文件提供ROS环境下的单障碍、多障碍实验脚本,另含7个fig仿真图形便于查看结果、6篇pdf论文说明便于研读原理、1个mp4演示视频及readme文档,分级目录清晰,便于从论文原理到仿真结果全链路对照复现。目前已有683人学习下载,覆盖从人工势场入门学习者到需要快速验证控制算法的研究者,具有较高的参考与扩展价值。

1. 人工势场避障的MATLAB实现:一套能跑通的非完整约束避障控制方案

第一次用人工势场法做避障仿真的感受,多半是“看着像把小车当成电荷推来推去”,可真正跑起来就会发现坑也不少:目标点旁边多一根柱子,小车就在那来回摆头;斥力参数调大了,走几米就开始震荡。这套源码做的事情很直接——在二维地图上用人工势场算合力,把合力映射到差速小车的线速度和角速度上,让小车从起点走到目标点。项目标题里的 Nonholonomic Mobile Robot 指的就是这种只能前进、后退、原地转的差速机器人,它不能横移,所以势场只给方向,把方向变成轮子转速才是控制核心。整个项目是系统开源的,MATLAB 直接跑通,适合研究移动机器人运动规划、想快速跑通一个完整避障闭环的开发者。

2. 核心算法拆解:势场计算、运动学模型与控制律

2.1 势场计算:引力与斥力的Matlab实现

先说势场函数。人工势场法把地图看作一个势场,目标点是低势场洼地,障碍物是高势场凸起,机器人沿着势场梯度下降的方向走。代码里通常不直接算势场值,而是算负梯度,也就是受力。

引力项很简单,直接把机器人位置与目标位置做差,乘一个增益系数。下面是常见的写法:

function F_att = attractionForce(pos, goal, k_att) % pos: 机器人当前位置 [x; y] % goal: 目标点位置 [x; y] % k_att: 引力增益,越大吸引力越强 F_att = -k_att * (pos - goal); end

这里负号是因为我们想沿势场下降方向走,而势场本身写为 0.5 * k_att * ||pos - goal||²,取负梯度就得到上面的表达式。注意如果机器人离目标越远,这个力的模越大,所以引力其实是随距离线性增大的,不会出现走半天还没力的情况。

斥力项就复杂一些,因为要考虑障碍物的影响半径。常见写法是基于距离的截断式斥力场:

function F_rep = repulsionForce(pos, obstacles, radius, k_rep, d0) % pos: 机器人当前坐标 [x; y] % obstacles: 圆心坐标 Nx2 的矩阵 % radius: 每个障碍物半径,N 维向量 % k_rep: 斥力增益 % d0: 斥力场影响半径,只有离障碍物小于 d0 才生效 F_rep = [0; 0]; for i = 1:size(obstacles, 1) diff = pos - obstacles(i,:).'; dist = norm(diff); if dist - radius(i) < d0 && dist > 1e-3 % 独立计算每个障碍物的斥力方向,指向机器人外侧 direction = diff / dist; factor = k_rep * (1/(dist - radius(i)) - 1/d0) / ((dist - radius(i))^2); F_rep = F_rep + factor * direction; end end end

这里有一个细节值得留意:我用的是 dist - radius(i),也就是障碍物表面到机器人中心的距离,而不是障碍物圆心到机器人的距离。如果直接用圆心距离,机器人还没贴到障碍物表面,斥力就已经开始作用,轨迹会显得非常保守,像在躲一个比实际大得多的障碍物。

代码里 direction 变量代表从障碍物指向机器人方向的单位向量,所以斥力天然把机器人往远离障碍物的方向推。factor 的计算用的是 1/(dist-radius) - 1/d0,这东西会在机器人接近障碍物表面时快速膨胀,相当于给机器人一个“别靠近”的强约束。

2.2 非完整约束模型:差速小车怎么走

所谓非完整约束,简单理解就是差速小车不能横着走。它的位姿是 [x, y, theta],theta 是车头朝向,运动学方程是经典的三条微分式:

function [new_pos, new_theta] = kinematics(pos, theta, v, omega, dt) % pos: [x; y] % v: 线速度,沿车头方向 % omega: 角速度,逆时针为正 new_pos(1) = pos(1) + v * cos(theta) * dt; new_pos(2) = pos(2) + v * sin(theta) * dt; new_theta = theta + omega * dt; end

这个模型本身不复杂,但它在整个避障链路里非常关键:你只能控制小车的车头方向和前进速度,不能像全向轮那样直接把合力方向当作运动方向。如果机器人的朝向和合力方向偏了,控制器就得先把它转过来。

初次实现这个部分很容易不假思索地写 pos = pos + F_total * dt,那等价于把小车当成一个全向质点,完全丢掉 theta 这个自由度,到了后面跑动态障碍物或者做真机移植时就会翻车。

2.3 控制律:把合力变成速度指令

有了期望合力,下一步是决定怎么把它转换成差速驱动器的转速。我的做法是先算合力方向,再把它当作期望航向角,用比例控制器把当前朝向角拉向期望角:

function [v, omega] = velocityController(F_total, theta, param) % param 需要包含 k_omega, k_v, v_max, omega_max theta_desired = atan2(F_total(2), F_total(1)); delta_theta = wrapToPi(theta_desired - theta); % 最小角度差 omega = param.k_omega * delta_theta; omega = max(min(omega, param.omega_max), -param.omega_max); v = param.k_v * norm(F_total); v = max(min(v, param.v_max), -param.v_max); end

这里用 wrapToPi 把角度差规范到 [-pi, pi],是为了避免小车为了转向 170 度而反向绕一大圈。如果不做这一步,小车很可能每走一步都在来回甩头。

还有一个常见问题是,当合力接近零的时候(也就是局部极小值),v 也会接近零,小车看起来就像卡住了。解决思路之一,是给线速度加一个最小阈值,让小车在小幅度原地转向的同时保持一个很小的前进速度,用这个办法去试探边界。不过本质上局部极小是人工势场法的固有缺陷,到后面专门说避坑的问题。

3. 主程序工作流:从初始化到轨迹输出的完整链路

3.1 环境与参数的初始化

主程序第一步是把地图、障碍物、起点终点、控制参数一次性定义清楚。这个项目里参数用一个结构体统一管理,后面改配置时不需要到每个函数里找数值。

% 初始化 param.origin = [1,1]; % 起点坐标 [x, y] param.goal = [19,19]; % 目标点坐标 [x, y] param.theta = 0; % 初始航向角 rad param.k_att = 0.6; param.k_rep = 6; param.d0 = 2.5; param.k_omega = 2.0; param.k_v = 0.8; param.v_max = 1.0; param.omega_max = 1.5; param.dt = 0.05; param.max_iter = 2000; param.threshold = 0.3; % 达到目标的判定半径 obstacles = [5,8,0.6; 12,6,0.8; 9,12,0.5; 15,15,0.7; 3,13,0.4]; % 每行: 圆心x, 圆心y, 半径 radius_arr = obstacles(:,3); pos = param.origin(:); theta = param.theta;

之所以统一用 param 结构体,是因为人工势场法的参数敏感性很高:k_att 大一点小一点,走出来的轨迹差别特别明显。把所有参数集中在一起,后续做批量参数实验会方便很多。

障碍物写成 N 行三列矩阵,前两列是圆心,第三列是半径。这样在地图上加几个柱子就很直观。注意这里的障碍物数量不多,是为了第一次运行能快速看到效果。如果你直接把这个矩阵改到十几行,机器人绕行的路径就会变得非常复杂。

3.2 主循环:一步步往前推

初始化结束后就是整个仿真最主要的循环:计算合力、映射成速度、更新位姿、检查是否收敛。代码如下:

traj = zeros(max_iter, 3); for t = 1:max_iter F_att = attractionForce(pos, param.goal, param.k_att); F_rep = repulsionForce(pos, obstacles, radius_arr, param.k_rep, param.d0); F_total = F_att + F_rep; [v, omega] = velocityController(F_total, theta, param); [pos, theta] = kinematics(pos, theta, v, omega, param.dt); traj(t,:) = [pos(1), pos(2), theta]; if norm(pos - param.goal(:)) < param.threshold traj = traj(1:t, :); break; end end

这里有几个容易出错的地方。第一个是 pos 是列向量 [x; y],而 param.goal 如果直接写成普通矩阵的横坐标形式,维度对不上会直接报错,所以上面的代码里用 param.goal(:) 强转成列向量。第二个是 threshold 不能设太小,否则仿真的终止条件会非常苛刻,可能机器人在目标附近晃了很久都判不到结束。经验值是阈值比障碍物半径小一个数量级就好。

3.3 可视化:把势场和轨迹画出来

仿真跑完,最直观的输出方式是把整个势场画成等高线,再把轨迹叠加上去。这一步能让你一眼看出机器人在哪里绕路、哪里有弯折。

% 生成网格坐标并计算总势场值 [xq, yq] = meshgrid(0:0.2:20, 0:0.2:20); U = zeros(size(xq)); for i = 1:numel(xq) p = [xq(i); yq(i)]; F_att = attractionForce(p, param.goal(:), param.k_att); F_rep = repulsionForce(p, obstacles, radius_arr, param.k_rep, param.d0); U(i) = norm(F_att) + norm(F_rep); end plot(traj(:,1), traj(:,2), 'r-', 'LineWidth', 2); hold on; for i = 1:size(obstacles,1) viscircles(obstacles(i,1:2), obstacles(i,3), 'Color','k'); end

严格说这里画的不是势场值,而是受力大小,但用来观察极小值的位置、判断轨迹拐点已经够用。代码里用双循环遍历网格点,比较直观,批量跑大图的时候可以改成矩阵运算,速度能快不少。还有一个细节:如果网格范围设得过大,等高线图会非常尖,根本看不出低谷,实际使用时把网格范围缩到任务地图的 1.2 倍以内就好。

4. 避坑指南:人工势场仿真的三个经典翻车现场

4.1 机器人卡在某个点不动:局部极小值

现象:机器人走到某一点后,无论怎么迭代,位置几乎不变。它既不在目标点,又离障碍物有一段距离。

原因:在这个点上,目标点的引力和四周障碍物的斥力刚好抵消,合力为零,控制器输出的线速度也变成零。人工势场法是局部规划方法,天然没有全局视野,对地图上这种平衡点没有任何主动权。

解决方案:最实用的一种是增加随机扰动。当检测到机器人在连续几十步内位移小于某个阈值时,给角速度叠加一个随机项,让它摆脱平衡点。另一种是把人工势场作为局部避障模块,配合RRT这样的全局规划器做一个两级规划,先有全局路径,再在局部用势场走。

我的经验是先用小扰动方案快速验证,看是否只是临时卡住。如果换了几个地图还是卡在同一个位置,再加全局规划层。

4.2 目标点附近来回摆头:震荡问题

现象:小车已经到了目标点周围,但轨迹是锯齿形或来回摆。

原因:目标点附近若还有障碍物在影响半径内,斥力和引力会在那里不断对抗,导致期望航向角不断翻转。控制律里虽然有 wrapToPi,但期望方向本身的跳变会让小车产生持续的摆头。

解决:在任务里加一个“到达判定圈”,当机器人距目标点小于某个半径时,直接把斥力场屏蔽掉,只用引力收尾。我一般在主循环里加一个状态位,判断进入目标半径后就不再调用 repulsionForce,这个小技巧能解决绝大部分收尾震荡。

这个坑很典型,因为很多人跑通一次就收工了,根本发现不了这个震荡。等换一个地图、障碍物靠近目标点,问题才会爆出来。

4.3 机器人穿障:斥力方向写反

现象:机器人径直往障碍物圆心方向走,轨迹直接穿过障碍物,看起来就像没有检测到障碍物一样。

原因:斥力方向算反了。方向向量写成了 (obstacle - pos)/norm(...),把机器人拉向障碍物,而不是推离障碍物。这个错误在实际调试中很容易出现,因为公式推导的符号一不小心就少了负号。

解决:写单元测试或者直接画向量图验证。把每一个障碍物的斥力向量从机器人位置画出来,观察箭头的方向是否指向远离障碍物。我当时是在一张图纸上画了几十根向量才发现问题,从那以后每次改动力场公式,都会先画矢量图,再跑主循环。

5. 参数调优:让机器人在不同地图上都能走通

5.1 增益配对:k_att 与 k_rep 的平衡

人工势场法参数很多,但最核心的其实是 k_att 与 k_rep 的比值。我的习惯是固定 k_att,然后一点点加大 k_rep。

如果 k_rep 偏小,机器人离障碍物太近才反应过来,急转弯会让轨迹很不自然,甚至贴着障碍物擦过。如果 k_rep 偏大,机器人会在很远的地方就开始绕弯,走出一条看起来跟障碍物毫不相关的弧线。两者相差过大的时候还会造成另一个现象——机器人在走廊中间来回折返。

调参顺序一般是这样:先把 k_att 设为 1,k_rep 从 1 开始逐渐增大,每改一次跑一遍轨迹,观察机器人到障碍物边缘的最短距离。最短距离低于安全距离就加大 k_rep,轨迹过早偏离就减小。

可以参考下面这个表格作为初始值:

参数作用初始参考值调整方向
k_att引力增益,决定冲向目标的强度0.5目标附近走不到时减小
k_rep斥力增益,决定躲障的强度5离障碍物太近时增大
d0斥力影响半径2.0路径绕太大就减小
k_v线速度比例系数0.8轨迹抖动时减小
k_omega角速度比例系数2.0转向过猛时减小

要说明的是,这些初始值只适合通道宽度在 4 到 6 倍机器人尺寸的地图,如果你把地图整体缩放,需要同步调整这些参数。

5.2 影响半径 d0 的物理含义

d0 决定机器人在多远处开始感知障碍物。本质上它模拟的是传感器探测范围。

d0 设太大,机器人在开阔地带就开始规划绕行,路径会偏离最短路线;设太小,机器人直到逼近障碍物才发现,转向来不及,直接走向障碍物。理想情况下 d0 覆盖一个车身宽度加一个安全距离就可以。

一个快速验证方法:把机器人的轨迹画出来后,把障碍物绘制成半径加了 d0 的虚圆,如果虚圆包络了整个轨迹的拐点,说明 d0 设置合理。

5.3 步长与终止条件的配合

dt 的选择要同时考虑仿真精度和速度。dt 太大,离散误差大,尤其在狭窄通道里,机器人可能一步就越过障碍物边缘;dt 太小,迭代次数会变多,跑一次要等很久。

一般让每步位移不超过地图最小特征尺度的 1/10。例如地图网格是 1 单位一格,最小通道宽度大致 3 个单位,那每步位移控制在 0.3 以内就能接受。控制 v_max 和 dt 的乘积就能控制每步位移。

终止条件里的 threshold 也要和 dt 配合,阈值设得非常小导致小车在终点附近转了很久都判定不了,其实是因为 threshold 比每步位移还小,永远等不到相交。把 threshold 设成 v_max * dt 的两倍以上,问题就基本消失了。

6. 二次开发:从静态避障到动态障碍物环境

前面的内容都是基于静态障碍物,也就是地图上每个圆心点固定不变。二次开发最值得改造的方向是引入动态障碍物,比如地图上移动的其他物体。

最简单的方式是给障碍物结构加一个速度向量,每个仿真步更新障碍物位置。具体改动分三段:初始化时增加 obstacle_vel 矩阵,步长内更新障碍物坐标,然后在同一帧把新坐标传给 repulsionForce。示意如下:

for t = 1:max_iter % 更新动态障碍物位置,obstacle_vel 是 Nx2 的速度矩阵 obstacles(:,1:2) = obstacles(:,1:2) + obstacle_vel * param.dt; [F_att, F_rep] = computeTotalForce(pos, param.goal, obstacles, param); % 后续速度控制、运动学更新保持不变 end

这样跑起来之后你会发现另一个问题:机器人会反复躲避同一个障碍物,明明已经躲开了,障碍物又朝它运动过来,结果轨迹变得很折腾。一个更完善的做法是在斥力计算里引入速度障碍的概念——只对正在靠近机器人的障碍物计算斥力,那些已经远离的直接跳过,这样机器人不会被远去的障碍物吓到绕路。

改造完可以用一个简单的对比实验验收:同一个地图,一组静态障碍物,一组动态障碍物,统计两种工况下的到达率和平均到达时间。动态场景达不到 80% 以上的到达率,说明斥力的时间项没有处理对。

我拆这个项目时留下的习惯是:每次拿到一套新的规划算法,先不急着改功能,而是把默认参数跑一遍,画出轨迹和势场,再动三个参数分别观察行为变化。这个流程做完,再谈二次开发。希望这次的拆解能帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询