☰
基于ROS与Gazebo的AGV仿真系统搭建与自主导航实战
2026/9/27 1:01:33 网站建设 项目流程

最近在一家做工厂物流的朋友那边蹭了个AGV项目,他问我能不能在真车进场之前,先在仿真里把"车能跑、能停准、能多车调度"这套逻辑验证明白。正好他们那边现场环境复杂,直接拿真车试错成本太高,于是我就用ROS加Gazebo搭了一套AGV工业运输系统的仿真环境。做完之后我们发现,这套东西不只是给新人练手用的,哪怕你是要搞真车部署、要验证调度算法,也值得先在仿真里把坑都踩一遍。

这篇文章就围绕"从模型构建到自主导航"这条线,把整个项目里涉及的思路、模型、传感器、SLAM、move_base导航、多车A*调度,以及我在实际调试中踩过的坑,全部按步骤写清楚。适合刚入门ROS和Gazebo的读者当作一个完整的实战参考,也适合在工业物流场景里做AGV预研的工程师拿来对比选型。

1. 项目整体设计思路

做AGV仿真最忌讳一上来就抓着一个点猛调,比如只调雷达参数或者只跑导航,结果整个系统串起来之后到处打架。我更习惯先想清楚:我要在仿真里解决什么问题,技术路线是什么,每一层之间怎么通信。

1.1 为什么选择ROS加Gazebo这套组合

市面上机器人仿真工具不少,Webots、CoppeliaSim、Unity加MLAgents都能做,但AGV领域我最推荐ROS加Gazebo。原因其实很简单:ROS的机器人生态已经事实上成了行业通用语言,从传感器驱动到导航规划,再到多机管理,都有成熟的功能包,你不需要从零造轮子。Gazebo作为仿真器,和ROS的集成是原生级别的,URDF模型加载、传感器数据发布、物理引擎交互这些最核心的需求,它都帮你做好了。

对于AGV这种场景,我们要验证的无非是这四件事:底盘运动学对不对、传感器能不能可信地感知环境、导航算法能不能规划出安全路径、多车之间会不会互相堵路。这四件事在Gazebo里都能闭环。尤其是物理引擎这块,ODE、Bullet这些引擎可以模拟轮子打滑、负重状态下的加减速变化,比单纯的路径规划器"画线"要真实得多。

还有一个现实因素:AGV相关的中文资料和开源代码,绝大多数都跑在ROS1的Noetic版本上。就算项目最终要迁到ROS2,先用ROS1把逻辑验证清楚,再迁移,效率往往更高。所以我这个项目选择了ROS Noetic配Ubuntu 20.04,搭配Gazebo Classic 11。这个组合在稳定性上经过了大量用户验证,踩坑成本最低。

1.2 系统架构与技术选型

我习惯把整个系统拆成四层,每一层各管一摊,调试定位问题的时候非常清晰。

第一层是物理模型层,负责描述AGV长什么样、轮子怎么分布、身体各部分的重量和惯量。这部分在URDF/XACRO文件里定义。第二层是仿真层,由Gazebo负责,它读取URDF模型并放在一个虚拟世界里,同时通过插件模拟激光雷达、IMU、轮式里程计这些传感器的数据输出。第三层是功能层,负责跑SLAM建图、AMCL定位、move_base导航这些核心算法。第四层是任务层,这一层更偏应用,比如一台AGV从A点取货到B点卸货,多台AGV怎么排队,都是在这一层实现。

底盘选择上,工业AGV最常见的是差速驱动,两个主动轮加若干从动轮或万向轮,模型简单、控制成熟、转弯半径灵活。Gazebo里有现成的差速驱动插件,直接用就行。雷达方面选了2D激光雷达,因为室内工业场景用单线雷达做定位和避障是主流方案,数据量小、实时性高、算法生态成熟,建模和导航都够用。3D视觉方案不是不能做,但对仿真机的性能要求高了不少,前期预研阶段没必要。

导航方案采用navigation栈里的move_base,全局规划器用A*或Dijkstra,局部规划器用DWA。后续要扩展成多AGV系统时,在调度层加一张"路径占用时间表",每台车规划完路径之后去查表,冲突就让低优先级车辆等待或改道。整个架构做到后面其实非常接近真实的AGV调度系统。

2. AGV底盘与传感器模型构建

仿真的第一步是"造车"。很多新手在URDF上栽跟头,一启动模型就乱飞或者轮子陷进地里,其实都是模型描述有问题。URDF的核心思想不复杂:把机器人拆成若干刚体,每个刚体叫一个link,link和link之间用joint连接,joint决定它们之间的相对运动方式。

2.1 URDF/XACRO建模核心

一个最简AGV底盘,至少要有这几部分:底盘本体(base_link)、两个主动轮(wheel_left_link、wheel_right_link)、一到两个万向支撑轮、还有放雷达和IMU的传感器安装座。每个link都要写清楚视觉(visual)、碰撞(collision)和惯性(inertial)三个属性。很多人只写视觉不写碰撞,结果Gazebo里车和货架穿模或者雷达扫描不到东西;还有人不写惯性,模型加载后直接翻车。

我习惯用XACRO而不是直接写URDF,因为XACRO支持宏定义和数学表达式。两台轮子参数几乎一样,写一个wheel宏,传不同参数就能生成左右轮,改轮距的时候只改一个变量就行。下面是一个核心片段,可以作为参考:

<xacro:macro name="wheel" params="prefix x_reflect"> <link name="${prefix}_wheel"> <visual> <geometry> <cylinder radius="0.11" length="0.08"/> </geometry> <origin rpy="0 0 0" xyz="0 0 0"/> </visual> <collision> <geometry> <cylinder radius="0.11" length="0.08"/> </geometry> </collision> <inertial> <mass value="2.0"/> <inertia ixx="0.02" ixy="0" ixz="0" iyy="0.03" iyz="0" izz="0.02"/> </inertial> </link> <joint name="${prefix}_wheel_joint" type="continuous"> <parent link="base_link"/> <child link="${prefix}_wheel"/> <origin xyz="${0.28 * x_reflect} 0 -0.11"/> <axis xyz="0 0 1"/> </joint> </xacro:macro>

特别注意joint的origin坐标,它定义了轮子相对底盘的位置。轮子半径0.11米,那么轮子关节的z坐标就得是-0.11,让轮子底部正好和底盘底面对齐,这样模型放在地面上才是正常姿态。

还有一个坑是惯性张量。对于圆柱体轮子,转动惯量可以直接按圆柱公式估算,ixx和izz通常比iyy大,因为轮子绕直径方向的转动惯量相对小,绕轴的转动惯量要看轮辐分布。如果随便填一组很小的值,Gazebo物理引擎解算时就会出现高频抖振,车像得了帕金森一样。解决方法是先把质量填对,再用常见几何体的惯量公式算一遍。

2.2 传感器插件配置

模型建好了之后,要给AGV装上"眼睛"和"感知器官"。Gazebo里所有传感器都是通过插件实现的。2D激光雷达用的是ray sensor系列插件,IMU有imu传感器插件,轮式里程计可以直接通过差速驱动插件发布odom话题,不需要额外建模。

雷达配置里最关键是那几个参数:更新频率、激光线束数量、角度范围和最大测距。我常用的是10Hz、360度范围、0.25度分辨率,对应1440个采样点,最大测距30米。室内仓库场景需要注意:如果雷达安装高度太低,扫描会打到地面凸起;太高又可能漏掉低矮障碍物。工业AGV的雷达一般装在0.2到0.4米高度,这个高度正好能扫到货架腿和托盘边缘。

IMU的作用主要是提供角速度反馈,帮助定位算法在机器人转弯时修正朝向。很多人觉得摄像头对AGV导航没必要,但如果你后面要加二维码识别、托盘对接,那仿真里就得提前加好相机link和插件。我在这个项目里预留了一个朝下安装的相机位置,方便后面做二维码定位扩展。

传感器数据发布到ROS之后,一定要养成用rqt_graph看话题流向的习惯。我调试时见过不少次:模型加载了、Gazebo也没报错,但rostopic list里根本没有scan话题,八成是插件参数里namespace或者frame_id拼错了。

2.3 从Blender到Gazebo:模型导出避坑

很多项目为了演示效果好,会先用Blender建一个精细的外观模型,再导入Gazebo。这个流程确实能出效果,但坑也多。

第一个坑是单位。Blender默认米制,但导出Collada(DAE)格式时有的版本会默认厘米,导致导入之后模型尺寸放大了100倍。所有内容瞬间变成哥斯拉。建议导出前先检查Blender场景单位,导出后到Gazebo里量一下模型包围盒再继续用。

第二个坑是碰撞体不能太精细。URDF里碰撞模型应该用尽量简单的几何体,比如方盒、圆柱体,这样物理引擎跑得快、也不容易卡顿。外观模型可以精细,但碰撞体尽量用原始几何体代替。如果你把几万面片的精美房车模型直接当碰撞体用,Gazebo的碰撞检测计算量会直接爆炸,仿真帧率掉到惨不忍睹。

第三个坑是坐标轴朝向。Blender里默认z轴向上,但有些三维软件是y轴向上。导出的模型位置和朝向不对,车会"躺"在地面上。解决办法是在Blender里先把模型摆正,再在URDF的visual和collision标签里补一个origin偏移来微调。

我的经验是:工业仿真里外观没有想象中重要。轮子、底盘、雷达安装位置对了,功能就能验证,一张干净的白色底盘子配上黑色轮子完全够用。外观留给最后汇报演示时再加,优先把逻辑跑通。

3. 仿真世界与传感器细节

模型本身没问题了,接下来的重点就是让Gazebo环境尽量贴近真实车间。这里包括地面的物理属性和传感器的噪声,很多人忽略了后者的重要性,导致仿真里一切完美,上真车就完蛋。

3.1 Gazebo世界文件与物理参数

Gazebo世界文件(.world)定义了仿真环境里有什么、物理引擎怎么工作。一个典型的仓库场景,至少要有地面、墙面、货架、通道标志线。开源的仓库模型很多,也可以自己用Gazebo提供的建模工具搭建。比起环境外观,我更关注物理参数设置。

物理引擎我一般选ODE,稳定且速度可接受。关键参数是max_step_size和real_time_update_rate。max_step_size代表物理仿真的步长,一般设0.001秒到0.005秒之间。步长越小越精确,但CPU消耗成倍增长;步长太大会出现穿透、抖动,看起来很假。real_time_update_rate设1000左右,表示每秒种更新1000次物理计算,两个参数配合不对就会出现"仿真时间比真实时间慢"的现象。

地面摩擦也很关键。货车行驶在环氧树脂地面和水泥地面,打滑程度完全不一样。在模型插件里通过mu1、mu2、kp、kd这些参数控制滑动摩擦、滚动摩擦和接触刚度,值设小了车会漂移,设大了车转弯会显得"过度抓地"。我一般先把mu1和mu2设为1.0左右,跑起来再根据转弯和刹车表现微调。

还有一个细节是负重。工业AGV基本都是带货跑的,在车尾加装一个load参数或者直接在Gazebo里放一个货架模型,用固定关节绑到车体上。这样可以验证载重之后加速变慢、停车距离变长这个真实物理行为,对后面调度算法的时间窗计算非常有用。

3.2 给传感器增加"真实感":噪声与频率

默认的Gazebo传感器是"理想传感器",数据干净得像纸面参数,但真机不是这样。雷达会跳点,IMU会漂移,里程计会打滑。不处理这些噪声,你在仿真里辛苦调好的参数,部署到真车时往往会失灵。

给激光雷达加噪声有几个常用手段。第一个是加高斯噪声,在传感器插件里通过noise元素设置均值和标准差。第二个是模拟测距丢失,让雷达在某些角度偶尔返回极大值,相当于真机遇到透明或高反光物体时的丢点现象。第三个是限幅,超过最大量程的数值直接截断。这些设置能让costmap更真实地出现误障碍物或者间隙,逼着导航算法去应对这些"意外"。

更关键的是里程计。Gazebo的差速驱动插件默认发布的是理想里程计,轮子转了多少,位置就变化多少。但实际上轮胎会磨损、地面会打滑。我一般会在里程计发布链路里叠加一个小的线性漂移量,或者直接写一个简单节点订阅理想odom,再叠加高斯噪声后转发到真正的导航节点使用。这样后面调试AMCL时才会发现"单纯靠轮速积分根本定位不准,必须融合激光和粒子滤波",这才是真车上的常态。

4. 从建图到自主导航:核心算法怎么落地

模型和环境都有了,现在就到了这个项目最核心的部分:让AGV知道自己在哪、要去哪、怎么去。这一章我会从头到尾拆解2D激光SLAM、move_base导航框架,以及多台AGV基于A*的调度思路。

4.1 2D激光SLAM选型与实操

AGV定位建图方案,最常见的是gmapping、slam_toolbox和cartographer三选一。gmapping是老牌方案,代码简单,但对计算资源要求随地图增大而快速上升,适合小场景练手。Cartographer精度高、支持闭环,但配置复杂,上手成本太高。slam_toolbox是折中方案,基于图优化,支持2D激光,有显式回环检测,保存地图也方便,仓库这种典型结构化环境里效率很高。所以我的建议是先试slam_toolbox。

建图的前置条件是把AGV控制起来。最简单的方式是写一个teleop键盘控制脚本,让车子以稳定速度在仓库里跑一圈。这里有个注意点:建图时车速不能过快,雷达更新频率10Hz时,车速建议控制在0.3m/s以内。走太快会导致帧间匹配漂移,建出来的地图会重影或扭曲。转弯时更要慢,一次转角不要超过30度,这样激光匹配才有足够的重叠区域。

地图建好之后,slam_toolbox会保存出一个PGM图片和对应的YAML配置。这个地图文件非常关键,以后每次启动导航都要加载它。如果场景变了,比如货架位置移动了,地图需要重新构建,否则AMCL粒子会全部飘在墙上。

4.2 move_base导航框架与代价地图

导航我用的是ROS经典navigation栈里的move_base节点。很多人嫌它老,但它其实是一个清晰的框架:先有一个全局代价地图,再由全局规划器在这张地图上找出一条从当前位置到目标点的路径;然后有一个局部代价地图,由局部规划器在跟随全局路径的同时,实时躲避动态障碍物。

全局规划器默认算法是Dijkstra,但工业AGV场景里我更推荐A*。Dijkstra会均匀地向四周探索,路径不见得差,但搜索范围大、耗时更长。A多了启发式函数,目标方向明确,搜索效率更高,尤其在地图大、节点多的仓库里优势明显,这也是很多人提到"三条AGV基本A算法"时最看重的点。修改方式是在move_base参数里选择算法类型并设置相关权重,让路径尽量贴墙但不擦墙。

代价地图是导航效果好坏的关键。inflaion_radius设大了,AGV会离障碍物太远,在窄通道里可能直接认为无路可走;设小了,路径会擦着货架走,车体稍有偏差就会撞上。我的经验是先测量车体最大外接圆半径,作为robot_radius,再在这个基础上加10厘米作为inflation_radius,既能保证安全,又不会把通道堵死。

AMCL定位参数同样需要调。粒子数量是个权衡:太少,定位不稳定;太多,CPU占用高。1000到2000个粒子对仓库场景一般够了。update_min_d和update_min_a这两个参数决定粒子更新的阈值,频繁更新会浪费资源,更新太慢则定位滞后。我一般设0.2米和0.1弧度。

4.3 多台AGV的A*路径规划与调度

单台AGV能导航还不够,工业场景下必然涉及多台车协同。这里最基础的问题是如何避免两台车在通道里正面相遇。解决这个问题不能只靠局部规划器的DWA,因为DWA的视角太短,只有几十厘米,等它发现对面来车时很可能已经刹不住了。

我的做法是:在move_base外面加一个调度节点,把地图栅格化之后,每台车规划出一条路径时,就把这条路径上每个栅格的"占用时间窗"登记到一张共享表里。当另一台车也规划路径时,先查这张表,如果它准备占用的栅格在某个时间段内已经被其他车占用,就根据优先级决定等待或者换一条路。底层全局规划器仍然用A*,但加上了时间维度的冲突检测之后,就变成了类似"路径预留"的交通管制机制。

在实际Gazebo仿真里,我放了3台同样的AGV。调度节点给它们分配了三个任务点,让它们在十字路口附近交叉行驶。第一次调试时,两台车在路口僵住了,谁都不让谁,因为优先级判断逻辑写反了。后来改成了严格的主从优先级加超时重新规划策略,才算顺利跑通。这一步让我意识到,多机调度的难点其实不在路径算法本身的数学复杂度,而在各种边界情况:同时到达路口、任务取消、车体故障阻塞通道等。

5. 整个实操流程:从零到完整跑通

这一章我把从环境安装到navigation全流程的关键步骤列出来,你照着操作,可以很稳地把AGV模型开起来并完成一次自主导航循环。

5.1 环境准备与安装

我的组合是Ubuntu 20.04加ROS Noetic加Gazebo Classic 11。ROS安装方法很多,如果不想折腾依赖关系,直接用社区比较流行的一键安装脚本(比如鱼香ROS一键安装)会省很多时间。它会把ROS本体、依赖和常用工具一次性配好,对新人来说非常友好。

手动安装也可以,核心命令是:

sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list' sudo apt install ros-noetic-desktop-full sudo apt install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control

装完之后记得初始化rosdep并配置环境变量:

sudo rosdep init rosdep update echo "source /opt/ros/noetic/setup.bash" >> ~/.bashrc source ~/.bashrc

这里有个容易踩坑的地方:rosdep init如果提示已经存在,可以不管它,直接跳到下一步。还有,如果你的显卡比较老,Gazebo启动黑屏或者闪烁,大概率不是安装问题,而是渲染环境变量没设置好。

5.2 工作空间与launch文件组织

项目代码我建议全部放在catkin工作空间里:

mkdir -p ~/agv_ws/src cd ~/agv_ws catkin_make source devel/setup.bash

src下面分几个包:agv_description放URDF模型和meshes,agv_gazebo放world文件和launch,agv_navigation放move_base和AMCL的配置文件,agv_scheduler放多车调度节点。包与包之间职责分明,后续维护也不用在一堆launch文件里翻来翻去。

launch文件是整个系统的"总开关"。我的主启动文件大致包含三块:加载AGV模型并生成到Gazebo世界、启动传感器和差速控制器、启动rviz可视化。后面建图和导航阶段,再单独启动各自的算法节点。

一个很实用的做法是把所有可调参数单独放到yaml文件里,不要在launch里硬编码。这样后续做参数扫描、对比实验时,只需要改yaml,不用动代码。Gazebo加载模型时,URDF最好通过xacro命令动态解析,不要在launch里写死,因为模型文件改完之后不需要手动生成URDF,启动时它自己会更新。

5.3 跑通建图与导航全流程的关键命令

启动仿真环境:

roslaunch agv_gazebo agv_empty_world.launch

启动键盘遥控节点,让AGV缓慢在仓库里跑几圈:

rosrun teleop_twist_keyboard teleop_twist_keyboard.py

启动slam_toolbox开始建图:

roslaunch agv_navigation slam_toolbox.launch

建图完成后保存地图:

rosrun map_server map_saver -f ~/maps/warehouse_map

之后启动AMCL定位和move_base导航:

roslaunch agv_navigation amcl_move_base.launch map:=/home/yourname/maps/warehouse_map.yaml

在rviz里通过2D Nav Goal按钮设置目标点,AGV就开始规划路径并移动了。整个流程跑通之后,建议用rqt_graph截图保存,方便以后排查话题连接问题。

6. 常见问题排查与避坑速查

最后一部分我把这一路踩过的高频问题汇总一下,这些问题在各类社区里天天有人问,我整理成表格,你可以直接当速查手册用。

现象可能原因处理方式
Gazebo界面一直在闪显卡驱动问题或渲染后端冲突先安装显卡驱动,再用LIBGL_ALWAYS_SOFTWARE=1 gazebo强行软件渲染
模型乱飞或者翻车URDF惯性参数缺失或joint方向错误检查每个link的inertial设置,确认joint轴方向正确
轮子陷进地里碰撞模型缺失或地面参数异常给每个link添加collision,检查世界文件地面高度
雷达话题无数据插件配置错误或frame_id不匹配用rostopic list确认话题存在,检查雷达插件里的topic和frame设置
建图重影、扭曲车速太快或转弯过急放慢车速,控制每次转角小于30度
导航路径穿墙代价地图膨胀半径太小增大inflation_radius,同时检查地图文件是否正确
AMCL粒子散落不收敛定位初始位姿错误或地图陈旧在rviz中重新设置初始位姿,确认地图与当前场景一致
多车路口死锁调度优先级逻辑不完善增加主从优先级,配置超时后重新规划路径
CPU占用过高、Gazebo卡顿物理步长太小或碰撞体过于精细适当增大max_step_size,简化碰撞体几何结构

6.1 Gazebo界面闪烁或黑屏的排查

Gazebo界面闪是老问题了,尤其是集成显卡或者N卡Optimus双显卡笔记本上。最直接的排查顺序是:先确认显卡驱动装好,再尝试启动时添加环境变量强制用软件渲染,比如LIBGL_ALWAYS_SOFTWARE=1。如果只是想跑仿真不想看画面,也可以加headless参数把GUI关掉,完全用命令行访问话题数据,这样性能反而更高。

6.2 模型跑飞和里程计漂移问题

模型一启动就"爆炸",基本就是物理参数没写对。先检查URDF里有没有漏掉collision,再看每个link的惯性张量数值是否合理。如果是启动后缓慢漂移,那是里程计没有噪声补偿或者AMCL没有启用。可以对比odom话题和amcl输出的位姿差异,差异越来越大说明轮式里程计在仿真里的表现已经和"真实"产生了偏差,这时候就看粒子滤波能不能把它拉回来。

6.3 雷达跳点和导航规划失败

雷达偶尔跳几个点本来就不影响大局,但如果跳点太多会导致代价地图上出现"幽灵障碍物",车走到那附近就停下来。解决思路是把雷达噪声压低一点点,同时在costmap配置里增大obstacle_range和raytrace_range的差值,让代价地图能区分真实障碍和噪声跳点。规划失败还有一部分原因在地图精度,建图时走过的路径一定要覆盖机器人日后要走的全部区域,否则AMCL很容易定位失效。

最后说一点个人体会

这一套AGV仿真项目做下来,我最深的感受是:仿真并不能替代真机测试,但它真的能把"低级错误"全部挡在真车调试之前。URDF没建好,真车上就是电机堵转;导航参数没调好,真车上就是撞货架。而这些东西在Gazebo里花几分钟就能暴露出来。如果你正准备搞AGV相关项目,我建议先别急着买硬件,把ROS和Gazebo这套仿真吃透,再上真车你会感谢自己的。

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

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

立即咨询