基于Simulink的无人艇避障仿真与VFH算法解析
2026/9/12 18:06:09 网站建设 项目流程

简介:基于Simulink的无人艇避障仿真资源包,面向智能船舶、路径规划与机器人避障方向的本科生和硕士生,同时适合教研人员用于课堂演示与课题预研,可满足课程设计、毕业设计以及科研入门等多种场景。压缩包共8个文件,包含4个MATLAB脚本文件、2个Simulink模型文件、1个txt说明文件和1张仿真结果PNG图,整体包体仅64KB,体量轻巧,无需额外资源即可直接打开运行、修改参数或扩展算法。模型中涵盖无人艇运动控制、障碍物检测、避障决策及轨迹输出等核心环节,各脚本分工明确,说明文件提供了详细的运行指引,运行结果图便于读者对照检查,能够帮助使用者快速理解避障仿真流程并在此基础上进行二次开发。作者长期从事优化算法与路径规划研究,使用者如遇到MATLAB版本兼容、模型打不开或运行报错等问题,可私信获得支持。该资源已有305人学习浏览,是快速上手Simulink无人艇避障仿真的实用资料。

1. 为什么无人艇避障仿真比小车更依赖Simulink

Simulink无人艇避障仿真最容易让新手误判的地方,不是避障算法本身,而是无人艇在水面上的运动约束。汽车可以在任意时刻踩刹车,但无人艇失去推力后仍会滑行,低速时舵和喷水导流罩几乎不产生横向力,所以避障决策必须把速度、艏摇角速度和推力响应时间都纳进模型。这也是单纯写MATLAB脚本容易翻车的原因:脚本里很难表现连续时间系统的反馈延迟。这套基于Simulink模拟无人艇避障仿真的工程包,用lihui.mdl承载完整的闭环仿真,用vj.m负责避障决策,适合做路径规划和动态避障相关课程设计,也适合刚接触USV建模仿真的人对照学习。下面从模型结构开始拆。

2. 无人艇Simulink模型搭建:从运动学方程到lihui.mdl

拿到一个Simulink工程包,先不要急着点Run,先把.m文件和.mdl模型之间的依赖关系理清楚。这个包里既有yundong.m这种初始化脚本,也有vj.m这种决策算法,还有penshuituili.m这种执行机构映射脚本。直接点Run大概率报“Undefined function or variable”,因为Simulink模型里的很多参数来自base workspace,必须由脚本先塞进去。下面按我的拆包习惯,从文件作用开始。

2.1 文件结构与顶层模块映射

解压后你会看到lihui.mdlvj.myundong.mpp.mpenshuituili.m4.PNGlihui123lihui123一般是旧版模型或数据备份,如果lihui.mdl能正常打开,可以暂时忽略它。4.PNG是运行结果截图,可以对比自己跑出来的轨迹是否一致。其余文件和Simulink模块的对应关系如下表。

文件作用在lihui.mdl中的位置
yundong.m初始化无人艇运动状态、质量和阻尼系数运动学与动力学模块的常量
pp.m生成全局参考路径点Path Planner 模块
vj.m根据障碍物信息计算期望航向和速度避障决策模块
penshuituili.m把期望总推力和转艏力矩映射到双推进器推力分配模块
lihui.mdl闭环仿真主模型顶层

这里的执行顺序是:先运行yundong.mx0u0m等变量放到工作区,再运行pp.m生成goal_points,最后打开lihui.mdl点Run。如果你解压后看到变量名不是这些,不要硬套,打开模型双击Constant模块看它引用的变量名,再返回脚本搜索赋值语句。

2.2 三自由度运动学方程与yundong.m初始化

无人艇在水平面的运动通常简化为三自由度:纵荡(surge)、横漂(sway)和艏摇(yaw)。忽略横摇和纵摇时,大地坐标系下的位置变化率与船体坐标系速度的关系为:

x_dot = u*cos(psi) - v*sin(psi) y_dot = u*sin(psi) + v*cos(psi) psi_dot = r

其中u是前进速度,v是横漂速度,r是艏摇角速度,psi是艏向角。yundong.m就是给这个微分方程提供初始状态和船体参数的。

% yundong.m - 无人艇初始状态与动力学参数 x0 = [0; 0; 0]; % 初始位置x,y(m)和艏向角psi(rad) u0 = 2.0; % 初始前进速度 m/s v0 = 0.0; % 初始横漂速度 m/s r0 = 0.0; % 初始艏摇角速度 rad/s m = 18.0; % 无人艇质量 kg Iz = 2.7; % 艏摇转动惯量 kg*m^2 Xu = -2.0; % 纵荡阻尼系数 Yv = -7.0; % 横漂阻尼系数 Nr = -1.5; % 艏摇阻尼系数

这段代码的逻辑很直接:前四行是初始条件,后面五行为动力学仿真提供模型参数。阻尼系数的负号表示水动力总是阻碍船体运动,数值越大阻力越大。没有实际船体参数时,用这里的量级也能跑通,但要注意阻尼太小会船速缓慢发散,太大则转向迟钝。v0=0意味着仿真从无横漂的直线航行开始,避障发生后才逐渐产生横漂,这个设定符合静态水域的初始状态。

2.3 在Simulink中实现运动学与动力学

lihui.mdl里的运动学模块,我建议用MATLAB Function块而不是一堆三角函数模块。代码简洁且便于改写成S-Function。运动学核心代码如下。

function [x_dot, y_dot, psi_dot] = kinematics(u, v, r, psi) % 三自由度运动学:输入船体系速度,输出大地系速度 x_dot = u * cos(psi) - v * sin(psi); y_dot = u * sin(psi) + v * cos(psi); psi_dot = r; end

代码逻辑:输入u,v,r,psi,其中转艏角速度r由动力学模块计算,psi由积分器回授。输出三个导数后接Integrator模块,得到x,y,psi。注意所有角度必须一致使用弧度制,如果pp.m里给出的路径点是角度直角坐标,这里要手动转成弧度的方向角。

动力学模块可以写成同样的MATLAB Function块,或者用Simulink自带的State-Space模块。以阻力线性化为例,加速度方程为u_dot = (Fx + Xu*u)/mr_dot = (M_yaw + Nr*r)/Iz。如果仿真中出现船速持续增加不收敛,多半是阻尼符号写反了,把Xu的负号去掉试试。

2.4 控制器连接与信号流

从避障算法输出的期望航向psi_ref到实际船舵,中间还需要一个航向控制器。常见做法是在vj.m之后接angdiff模块计算角度误差,再用PID Controller输出转艏力矩。一组能跑通的初始参数是P=2.5, I=0.1, D=0.2,微分项不宜过大,因为避障算法输出的期望航向本身可能带有跳变,微分项会把跳变放大成力矩冲击。这种分层的信号流把“往哪走”和“走不走得到”分开,排查问题时也能单独验证避障逻辑或控制器哪一环出问题。

3. vj.m避障算法解析:从障碍物栅格到航向修正

vj.m是这套仿真里最值得读的脚本。它处理的对象不是完整地图,而是船体坐标系下的障碍物距离和角度序列。这种感知输入对应激光雷达、毫米波雷达或视觉测距传感器。算法思想接近VFH(Vector Field Histogram),实时性好,没有A*和Dijkstra那样的大规模地图开销。

3.1 为什么用扇区直方图而不是全局规划

全局路径规划算法需要已知完整地图,且遇到动态障碍物时要重新求解,计算量大。人工势场法实现简单,但容易在狭窄水道陷入局部最小值,船会来回振荡。VFH的思路是把船周围的环境分成若干个扇形区域,统计每个扇区的障碍物密度,找出密度低于阈值的扇区作为候选方向,再结合目标方向选一条代价最小的航向。这个逻辑非常依赖感知数据,但也因此非常适合动态避障场景。

3.2 vj.m核心代码与参数含义

下面是vj.m的简化版本,保留了完整的决策流程。实际使用时可以把它改成带function定义的脚本,供Simulink的MATLAB Function块调用。

% vj.m - VFH风格避障决策 function [psi_ref, speed_ref] = vj(obs_dist, obs_angle, psi_current, psi_goal) sector_res = pi / 18; % 扇区分辨率:10度 num_sector = round(2 * pi / sector_res); hist = zeros(1, num_sector); % 障碍物密度直方图 % 把探测点累加到对应扇区 for i = 1:length(obs_dist) if obs_dist(i) < 5.0 % 只统计5米内障碍物 idx = ceil(obs_angle(i) / sector_res); idx = mod(idx - 1, num_sector) + 1; hist(idx) = hist(idx) + 1; end end % 密度低于阈值的扇区视为安全 safe_sector = find(hist < 2.0); if isempty(safe_sector) psi_ref = mod(psi_current + pi, 2*pi); % 四周被堵,原地转艏 speed_ref = 0.2; return; end % 对每个安全扇区计算代价,选择最优航向 best_cost = inf; for k = 1:length(safe_sector) theta_c = safe_sector(k) * sector_res - pi; cost = abs(angdiff(theta_c, psi_goal)) + 0.2 * hist(safe_sector(k)); if cost < best_cost best_cost = cost; psi_ref = theta_c; end end speed_ref = 1.0; end

代码逻辑分三段:第一段把障碍物距离换成角度索引,累加出直方图;第二段用密度阈值找出可行扇区,如果全堵就原地回转;第三段用“与目标航向的差 + 轻微障碍物惩罚”构成代价函数,选最小代价扇区的中心角作为期望航向。这样既保证向目标靠近,又不会贴着障碍物边缘走。

参数说明:obs_dist是障碍物距离数组,单位米;obs_angle是障碍物相对船头的夹角,单位弧度。5.0米是感知距离阈值,船速2m/s时大约提前2.5秒看到障碍物,留给控制器足够的响应时间。扇区密度阈值2.0表示一个扇区里超过两个探测点就认为有障碍物,激光雷达点数密时要调大,稀疏时调小。代价函数里的0.2是障碍物惩罚权重,权重太小会走危险空隙,太大会绕远路。

3.3 把vj.m接进Simulink闭环

lihui.mdl中,vj.m被封装成MATLAB Function块。输入obs_distobs_angle来自Sensor模块,psi_current来自运动学反馈,psi_goal来自pp.m生成的路径追踪模块。输出psi_refspeed_ref分别送入航向控制器和速度控制器。这里最容易犯的错是把vj.m当普通脚本用run逐次执行,然后手动更新Simulink输入,这样控制周期完全对不上,避障动作会滞后。

使用MATLAB Function块时,要把输入输出类型显式定义好,尤其是obs_dist的长度。MATLAB 2019a以下版本对变尺寸数组支持有限,建议把obs_dist定义成固定长度,比如zeros(1,36),不足的部分用一个大数填充,表示“无障碍物”。MATLAB 2014里没有内置angdiff时,可以用atan2(sin(a-b), cos(a-b))代替,效果一样。

4. 仿真运行与参数整定:从yundong.m到penshuituili.m

这个工程包不是双击模型就能跑的类型,必须按顺序初始化脚本。运行环境我用的是MATLAB 2019a,如果你用的是2014a,部分模块可能显示为红色,需要把MATLAB Function改成Interpreted MATLAB Function,或者更新模块版本。

4.1 正确的运行顺序

按照下面的顺序操作,可以避免90%的“变量未定义”报错。

  1. 把解压后的文件夹加入MATLAB路径:右键文件夹,选“添加到路径”。
  2. 在命令行执行yundong,看到工作区出现x0u0m等变量。
  3. 执行pp,生成全局参考路径点goal_points
  4. 打开lihui.mdl,检查仿真时间设为60秒,步长设为定步长0.01秒。
  5. 点击Run,等待仿真结束,打开Scope查看轨迹和避障过程。

为什么强调定步长?因为vj.m里存在阈值判断和扇区索引计算,变步长在阈值边界处会反复缩小步长,仿真速度极慢,甚至卡死。定步长虽然牺牲一点效率,但结果可复现,方便对比不同参数下的避障轨迹。

4.2 推力分配模块penshuituili.m

penshuituili.m的任务是把航向控制器输出的转艏力矩和速度控制器输出的总推力,转换为左右两个喷水推进器的实际推力。双推进器差速转向是无人艇最常见的转向方式。

% penshuituili.m - 双喷水推进器推力分配 function [T_left, T_right] = penshuituili(T_total, M_yaw, B) T_left = T_total / 2 + M_yaw / B; T_right = T_total / 2 - M_yaw / B; % 限幅,模拟推进器物理约束 T_left = max(0.0, min(T_left, 10.0)); T_right = max(0.0, min(T_right, 10.0)); end

代码逻辑:T_total是期望总推力,M_yaw是期望转艏力矩,B是两个推进器之间的横向距离。左侧加力矩除以力臂,右侧减,形成正向转艏。限幅范围[0,10]对应推进器可输出的推力区间,下限设0是因为喷水推进器一般不反转,如果出现负值就截断。你可以把上限改成5看低速避障时的表现,转向会变慢,避障轨迹会外扩。

4.3 调参方向与常见问题

调参时先调避障感知参数,再调控制参数,顺序反了会越调越乱。下表给出了几个关键参数的经验范围。

参数默认值调节方向说明
扇区数量36减少到18感知噪声大时减少扇区,航向更平稳,但会漏掉窄缝
障碍物距离阈值5.0 m增大到8提前避障,适合高速,但会绕远路
扇区密度阈值2减小到1更保守,容易在障碍物密集区犹豫
参考速度1.0 m/s降到0.5狭窄水域低速通过,给控制留余量

仿真发散是出现最多的异常。先看角度单位,vj.m内部用的是弧度,控制器PID和yundong.m里的psi也必须是弧度,任何一处用了角度都会让航向误差周期震荡。再看微分项,D=0.2已经偏高,如果Scope里的实际航向高频抖动,把D降到0.05。最后检查步长,0.01秒跑不通就换0.005秒,尤其是船体质量小、阻尼小的情况,动力学响应快,步长必须相应缩小。

5. 进阶验证:把避障仿真结果推向实船部署

仿真跑通之后,下一步不是直接装到实船上,而是做更贴近真实工况的验证。这里分享一个我常用的技巧:用External Mode验证传感器延迟对避障算法的影响。

5.1 用External Mode观察延迟下的决策

在Simulink模型配置里把仿真目标改成External,然后把lihui.mdl中的Sensor模块替换成串口接收模块,障碍物信息来自真实的激光雷达或毫米波雷达。此时vj.m依然运行在Simulink中,但输入已经变成真实数据流。你会在Scope里看到,真实传感器每隔50ms才更新一次数据,psi_ref在两次更新之间保持不变,航向控制器必须靠积分作用把误差压下去。如果算法在仿真里能避障,但External Mode下碰撞,问题多半出在感知延迟而不是算法本身。解决办法是让vj.m在感知间隙用上一帧数据做预测,也就是给obs_dist加一个零阶保持器。

5.2 导出FMU模型与代码生成

如果要用ROS节点或者其他仿真平台接你的避障算法,可以把vj.m所在的子系统导出为FMU。MATLAB 2019a支持从Simulink子系统导出FMU,前提是块内代码全部支持C代码生成。vj.m里的动态数组要改成固定长度,例如定义成1x36的定长数组,否则报错。导出后的FMU可以在Carsim、Amesim或者其他工具里直接调用,相当于把避障逻辑做成了独立模块。

5.3 验证算法是否合格的对照方法

避障是否成功不能只看最终轨迹有没有绕过障碍物,还要看期望航向和实际航向的偏差。把psi_ref和实际psi同时记录到工作区,画在同一个坐标轴里。如果实际航向始终落后于期望航向超过0.5秒,说明航向控制器带宽不够,盲目加大PID增益只会导致振荡。正确做法是降低参考速度,或者减小避障算法里的扇区密度阈值,让期望航向的变化更平滑。这样调完一轮,再回到仿真里改变障碍物位置,看算法是否仍然稳定,才算完成了闭环验证。

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

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

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

立即咨询