ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于ROS与Python的AGV激光SLAM导航避障实战指南

2026/10/2 4:04:57 拓冰建站 浏览量
基于ROS与Python的AGV激光SLAM导航避障实战指南 做AGV自动导引车的朋友应该都有同感硬件装好只是第一步真正让人挠头的往往是软件那一大串——激光SLAM怎么建图、路径规划怎么调、避障代码怎么写。这篇文章我就用PythonROS这套组合把从激光SLAM建图到自主导航避障的完整链路拆开讲清楚。内容适合正在做AGV样机、服务机器人或想入门机器人导航的开发者也适合那些已经跑通demo、但对参数怎么调心里没底的工程师。我会把我实际踩过的坑和常用代码直接放出来按步骤操作就能跑。1. 项目整体设计与方案选型1.1 为什么选ROSPython这套组合选ROS的原因很直白它把传感器驱动、定位、规划、可视化这些模块都做成了标准化的节点和话题你不用从零造轮子。激光雷达驱动、map_server、amcl、move_base这些都是现成的拿来就能拼。Python作为主开发语言主要是图它的开发效率调参数和改逻辑非常快尤其在避障代码这种“规则为主”的模块上Python写起来比C省一半代码阅读和维护也轻松得多。ROS Noetic 开始对 Python 3 的支持已经很完善直接用 Python 3 写节点没有历史包袱。当然如果你做的是实时性要求极高的底层控制再用 C 写插件也不迟导航上层用 Python 完全够用。1.2 硬件方案雷达、底盘和计算平台怎么搭激光SLAM导航第一步是选好传感器和底盘。2D激光雷达单线是目前最常见的选择价格从几百到几千都有室内AGV用单线雷达完全够。底盘结构建议优先选差速驱动控制简单、原地旋转方便做路径规划时的运动模型也容易理解全向轮机器人麦克纳姆轮更灵活但控制代码和参数调试会复杂一些阿克曼转向底盘做的是汽车运动导航里要加很多运动约束新手不建议碰。计算平台方面Jetson Orin Nano、树莓派4B、x86工控机都可以关键看你要跑什么算法只用gmapping建图树莓派4B也能勉强应付如果上cartographer建议还是Jetson或x86内存和CPU会更从容。一个比较典型的室内AGV硬件配置大概是这样部件选型建议备注激光雷达RPLIDAR A1/A2、YDLIDAR X42D单线10Hz以上底盘差速轮式AGV底盘编码器精度尽量高一点主控Jetson Orin Nano / 树莓派4B跑gmapping够用IMUMPU6050及以上可选但强烈建议加电机驱动支持ROS串口/Can通信的驱动板必须有/cmd_vel接口IMU不是必须的但有了IMU可以让里程计信息更准建图不容易飘。很多AGV底盘自带IMU或者可以用轮式里程计加上简单滤波也能跑出不错的效果。我自己的习惯是室内小场景先不上IMU跑通流程再逐步升级硬件这样定位问题出现时更容易判断是算法问题还是传感器问题。1.3 软件架构一台AGV上的节点怎么分工AGV的导航可以分成三个层次。感知层激光雷达发scan话题里程计发odomIMU发imu。决策层SLAM负责建图amcl负责定位move_base负责路径规划和避障。执行层底盘控制节点订阅/cmd_vel把速度指令下发给电机驱动板。节点之间只通过话题通信互不干扰、可以随时替换这就是ROS最有价值的地方。我遇到很多新手一上来就想着自己写路径规划算法动不动就要从Dijkstra开始实现。其实先搞懂move_base怎么用、参数怎么调已经能解决80%的AGV导航需求。真正需要自己写代码的部分绝大多数是避障策略、任务调度、多传感器融合这些细节。所以软件架构上我建议你把move_base当成黑盒先跑通再逐步替换里面不满意的模块这样排查问题会舒服很多。2. 环境搭建与核心依赖2.1 ROS发行版选择和安装如果你是全新的项目我建议直接用 Ubuntu 20.04 ROS Noetic。如果是ARM板子RK3588、树莓派Noetic也有对应移植版。Ubuntu 22.04 ROS 2 Humble这条路也可以走生态在快速成熟但很多老的gmapping、move_base教程还是ROS 1对新手来说遇到问题不好搜所以我先把ROS 1这套讲透。安装ROS桌面完整版本质就是一条apt命令的事sudo apt update sudo apt install ros-noetic-desktop-full装完以后记得把环境变量写进~/.bashrc否则每次开终端都要手动sourceecho source /opt/ros/noetic/setup.bash ~/.bashrc source ~/.bashrc国内如果下载太慢可以换清华源、阿里源。社区里也有一键安装ROS的脚本适合不想折腾环境的朋友本质上是把装依赖、配置源、安装ROS的流程自动化了我用过几次省去了不少重复劳动。2.2 激光雷达驱动和数据验证不同厂家的雷达驱动不一样。以rplidar为例安装驱动后roslaunch rplidar_ros rplidar_a1.launch就会发布/scan话题。装完驱动别急着建图先用rostopic echo /scan查看一下数据格式再在RViz里添加LaserScan显示确认雷达正前方在图像上的朝向和车体一致。这一步非常关键雷达装反或者坐标系弄错建出来的地图会是镜像的后面定位全乱。验证雷达数据最简单的命令组合是rostopic hz /scan rostopic echo /scan -n1rostopic hz能看到雷达发布频率是不是10Hzecho能看到ranges数组长度。如果雷达数据是乱的优先检查USB供电和串口权限常见问题就是雷达转起来了但数据全是0或者频繁断线。2.3 Python环境和catkin工作空间Noetic完全使用Python 3系统自带的Python 3.8就够了。ROS节点里常用numpy、scipy直接用pip装就行pip3 install numpy scipy然后创建工作空间mkdir -p ~/agv_ws/src cd ~/agv_ws catkin_make创建自己的功能包cd ~/agv_ws/src catkin_create_pkg my_agv rospy roscpp geometry_msgs sensor_msgs nav_msgs tf2之后写的避障节点、URDF模型、launch文件都放到my_agv包里面。一个常见的坑是忘记source工作空间的环境变量source ~/agv_ws/devel/setup.bash新写的节点rosrun找不到。建议把这句话也追加到~/.bashrc里不过要注意必须放在ROS自带的setup之后。3. 激光SLAM建图实战3.1 建图原理别靠背参数SLAM说白了就是机器人边走边回答两个问题我在哪周围环境长什么样先根据激光帧做匹配前端算出雷达移动了多少再把所有关键帧放到一起做优化后端让整体地图一致最后靠回环检测把走了一圈后重访的位置闭合起来。打个不太准确的比方就像你在黑暗房间里拿着手电筒走一圈每走一步都靠看附近墙壁的位置推断自己走没走偏最后回到门口时还得让门的位置跟记忆里对上。如果走太快或者某段路特征太少你就容易觉得哪里都对不上这就是地图漂移。理解了这套逻辑你就能明白为什么建图时要慢走、要回绕、要保证两侧都有墙而不是急急忙忙跑一圈就能出好图。3.2 常用建图算法怎么选主流的2D激光SLAM算法我基本都试过简单说说适用场景。算法场景优点缺点gmapping小场景、室内房间计算量小、成熟稳定无回环优化大场景会飘cartographer大场景、工厂车间回环优化精度高参数多、内存占用大hector_slam无里程计场景只用激光数据对雷达频率和精度要求极高karto2D建图有回环使用人数少资料少小房间学习用gmapping就够了两百平以上、有长走廊的场景直接上cartographer别在gmapping上浪费时间调半天还飘。另外提醒一句建筑结构越简单、特征越少SLAM越容易失效。激光SLAM在空旷大平层里往往会“迷路”这种场景要提前布置一些立柱、货架或者贴反光条来增加特征。3.3 从零建图的操作流程标准流程四步。第一步启动雷达和底盘驱动确认scan和odom话题正常。第二步启动SLAM节点。gmapping的launch文件一般长这样launch node pkggmapping typeslam_gmapping nameslam_gmapping outputscreen param namebase_frame valuebase_link/ param nameodom_frame valueodom/ param namemap_frame valuemap/ param nameparticles value30/ param namelinearUpdate value0.1/ param nameangularUpdate value0.1/ param namemap_update_interval value5.0/ /node /launch第三步用键盘遥控小车建图。rosrun teleop_twist_keyboard teleop_twist_keyboard.py然后以“之”字形慢慢走一圈拐弯要慢回到起点附近再绕半圈让回环闭合。第四步地图满意后执行rosrun map_server map_saver -f map_name生成map_name.pgm和map_name.yaml两个文件。这里没有捷径建图过程走太快、转太急地图必飘。走到过道时尽量让雷达能扫到两侧墙壁不要贴着一面墙走一大段不然另一侧会误差累积。3.4 建图参数调优以gmapping为例重点就几个参数。particles粒子数默认30场景大或噪声大可加到60-80但明显消耗CPU。linearUpdate和angularUpdate控制每移动多少米、转多少弧度更新一次地图过密费CPU过疏容易丢特征。map_update_interval地图整体更新频率默认5秒够用。cartographer的话主要调loop_closure相关参数、num_subdivisions_per_laser_scan和数据源类型。调参没有捷径一次只改一个参数记录结果对比地图质量。我习惯用固定路线测试——同一个房间里走同样的路线修改某个参数后对比地图的墙线是否平直、回环是否重合这个习惯比任何调参脚本都有用。4. 路径规划与避障实现4.1 move_base导航框架的组成真正跑导航的时候move_base是核心。它接收一个目标点调用global_planner在全局地图上算出一条从起点到目标点的路径同时local_planner根据实时激光数据不断修正轨迹避开意外出现的障碍物。整个框架还需要三样东西配合map_server发布静态地图amcl做蒙特卡洛定位以及底层发布的odom和tf变换。很多AGV项目卡住不是路径规划算法本身的问题而是tf没配好或者定位抖动导致的。建议先把rqt_tf_tree打开看一眼map、odom、base_link、laser这些坐标系之间的关系是不是严格按树状连接。tf断了或者跳变后面所有规划全是白搭。我遇到过一个案例车来回跑几趟以后定位越来越偏最后发现是odom坐标系里混了两个节点在发rqt_graph一看就发现问题了。4.2 全局路径规划参数配置全局路径规划器有两个选择navfn/NavfnROS使用Dijkstra算法global_planner/GlobalPlanner可以实现A和Dijkstra可选。室内AGV一般用A就够了走出的路径更直计算量小。costmap参数里核心是inflation_radius膨胀半径它决定了路径离障碍物有多远。我一般设成机器人半径加0.1到0.2米的余量太大路径绕远太小容易贴着墙走。下面是costmap_common_params.yaml里最基础的一段robot_radius: 0.2 inflation_radius: 0.35 observation_sources: scan scan: data_type: LaserScan topic: /scan marking: true clearing: truerobot_radius一定不能比真实车体小这是我从一次AGV卡在门框上的事故里学到的教训。测机器人半径时要把底盘上凸出来的零部件、外挂传感器都算进去别只量底盘主体。另外inflation_radius设置得太小路径会贴着障碍物走AGV稍微偏一点就撞上太大车会在走廊中间刻意绕远效率低。实际项目里我一般会先用RViz的“Publish Point”功能在代价地图上看膨胀区域的效果再决定具体值。4.3 局部规划器与实时避障局部规划器主流两个DWA和TEB。DWA的原理是在每个控制周期内采样多组线速度、角速度组合模拟未来一小段时间的运动轨迹然后从多条轨迹中选一条避开障碍、离目标最近的。通俗说就是在“往前走多远”“转多大弯”这个参数空间里不断试错选个最好的。优点是参数少、效果好适合大多数室内AGV。TEB则把轨迹当成一条可变形的“弹性带”约束它尽量短、平滑、无碰撞优点是可以显式设置速度快慢和转弯半径适合那种需要在工位间精准定位、轨迹讲究的场合。缺点就是参数多新手容易被几十个参数搞晕而且调不好容易出现速度震荡。我的建议先跑通DWA满足不了再上TEB。实际项目里大部分AGV用DWA就够了TEB更多用在服务机器人绕桌、贴边走的场景。4.4 一个可以直接跑的Python避障节点这里我给一个简化版的激光避障节点核心思路很简单把激光数据分到前方、左侧、右侧三个区域然后根据距离判断直行、停车或者转向。注意这是教学用的极简版本生产环境建议在move_base的局部costmap基础上做更完整的避障策略。#!/usr/bin/env python3 # -*- coding: utf-8 -*- laser_avoidance.py 订阅 /scan 激光数据输出 /cmd_vel 速度指令 分区策略前方有障碍 - 停车或转向前方安全 - 直行 适合室内AGV教学/原型验证生产环境请结合 move_base 使用 import rospy from sensor_msgs.msg import LaserScan from geometry_msgs.msg import Twist class LaserAvoidance: def __init__(self): rospy.init_node(laser_avoidance) self.pub rospy.Publisher(/cmd_vel, Twist, queue_size1) rospy.Subscriber(/scan, LaserScan, self.scan_cb) # 距离阈值单位米 self.stop_dist 0.25 # 前方小于该距离直接停车 self.safe_dist 0.45 # 前方大于该距离就放心前进 self.max_speed 0.22 # 最大前进线速度 self.max_turn 0.6 # 最大转向角速度 self.front float(inf) self.left float(inf) self.right float(inf) rospy.loginfo(laser_avoidance node started) def scan_cb(self, msg): n len(msg.ranges) if n 0: return # 把无效值(0或inf)处理一下 ranges [r if r ! 0.0 else float(inf) for r in msg.ranges] # 航向划分正前、左前、右前具体角度范围按雷达安装方式调整 # 这里假设雷达0度在正前方360个点均匀分布 front ranges[:n//12] ranges[-n//12:] left ranges[n//12 : n//4] right ranges[-n//4 : -n//12] self.front min(front) self.left min(left) self.right min(right) self.decide() def decide(self): cmd Twist() if self.front self.stop_dist: # 太近原地停止 cmd.linear.x 0.0 cmd.angular.z 0.0 elif self.front self.safe_dist: # 前方空旷优先直行 cmd.linear.x self.max_speed cmd.angular.z 0.0 else: # 前方进入危险区停车并转向空旷一侧 cmd.linear.x 0.0 if self.left self.right: cmd.angular.z self.max_turn else: cmd.angular.z -self.max_turn self.pub.publish(cmd) if __name__ __main__: try: LaserAvoidance() rospy.spin() except rospy.ROSInterruptException: pass这段代码可以直接放进~/agv_ws/src/my_agv/scripts/laser_avoidance.pychmod x后rosrun my_agv laser_avoidance.py运行。实际项目里建议用dynamic_reconfigure把阈值做成动态参数方便现场调别像我第一版那样改个安全距离还要重新编译重启。4.5 move_base与局部代价地图参数调试move_base的yaml配置常见的就是下面这些。controller_frequency控制局部规划器执行频率我一般设10Hz太高耗CPU太低避障反应慢。速度上限max_vel_x、max_vel_theta要跟底盘电机能力匹配设大了底盘容易打滑设小了AGV显得“迟钝”。acc_lim_x和acc_lim_theta限制加减速度这组参数直接影响起步和停车是否平稳。controller_frequency: 10.0 max_vel_x: 0.3 min_vel_x: -0.1 max_vel_theta: 0.8 min_vel_theta: 0.1 acc_lim_x: 0.5 acc_lim_theta: 0.5 xy_goal_tolerance: 0.1 yaw_goal_tolerance: 0.1xy_goal_tolerance和yaw_goal_tolerance是到点判断的容忍度。仓储自动对接场景设严一点比如0.05米观光导航设宽一点0.2米反而体验好。调参思路我从是“慢到快”先把速度调到很低确认能安全到达目标点再逐步提高速度缩小安全距离直到出现临界情况再回头微调。只有这样做你才能分清到底是哪个参数引发了问题。5. 常见问题与排查技巧实录5.1 建图时地图漂移甚至叠影这是新手遇到最多、也最容易劝退的问题。原因无非三种里程计不准、激光帧率低、建图走太快。先看odom话题有没有跳变打滑厉害的轮子里程计会抖AGV如果是差速底盘建议给轮子加编码器或补充IMU做融合。再看雷达频率大多数2D雷达10Hz建图频率并不高人推着走每分钟别超过0.3米/秒才稳。最后看回环你走完一圈回到起点如果地图在起点位置对不上就是累积误差太大只能控制速度再来一遍。5.2 导航过程中小车停住不动或原地乱转第一种情况小车停在原地不动先看amcl定位协方差。如果定位漂了全局路径根本规划不出来用RViz的“2D Pose Estimate”手动修正初始化位置是定位问题最直接的临时解法。第二种情况小车原地乱转多半是局部规划器的旋转速度设置太低或者costmap膨胀半径太大导致它认为两侧都过不去。把膨胀半径改小一点看现象也是一个很重要的定位问题的手段。还有一个小概率原因底盘的/cmd_vel被其他节点占用了。可以用rqt_graph看看是不是有多个节点同时在发速度指令。我之前就遇到过键盘遥控节点没关、move_base也同时在发指令的情况车在导航时自己不受控制地乱动查了半天才发现是节点冲突。5.3 避障偶尔失效撞到矮障碍物2D激光雷达扫描平面以下的物体是完全看不见的所以雷达安装高度一般在20到40厘米左右。太低看不清远处太高会漏掉地面矮物。如果你经常在办公区测试雷达高度30厘米左右、膨胀半径稍大一点是比较稳的组合。另外避障策略里“反应时间”也很关键以0.3米/秒的速度行驶safe_dist只有0.4米时留给停车的时间只有一秒左右雷达每个周期10Hz意味着只有10次激光数据来反应。所以速度、安全距离、传感器帧率三者必须一起调单独调任何一个都容易出事故。5.4 故障排查速查表现象常见原因优先排查方向地图漂移里程计误差大、回环没闭合检查odom、走慢一点重新建图导航目标设不了amcl没定位用2D Pose Estimate手动初始化路径贴合墙壁膨胀半径太小调大inflation_radius规划路径绕远膨胀半径太大调小膨胀半径小车原地乱转局部规划器速度限制太低提高max_vel_theta避障失效雷达盲区、速度过快降低速度、增大safe_dist里程计突然跳变轮子打滑、编码器松动检查底盘机械和驱动5.5 几个我踩过之后记住的习惯最后分享几个实操习惯。第一改任何参数前先把原配置文件复制一份存着调坏了能秒回滚。第二每次改动都先看rostopic echo /cmd_vel确认速度指令确实发出来了再去查机械问题。第三真机上测试前先在Gazebo仿真里把move_base调通再把同一套参数搬到实车能省掉很多不必要的半夜调试时间。第四激光雷达的安装角度一定要标定哪怕差五度建图和导航的体验都会差很大用一个量角器或者激光测距仪确认雷达中线与车体前进方向平行这比任何算法调优都来得可靠。做AGV项目这几年我的体会是激光SLAM导航的核心其实不在于算法有多么高深而在于你把整个系统的每一环都理解到位并且愿意一点一点调参、记录、对比。ROS和Python把开发门槛降得很低但能不能让车稳定地跑起来靠的还是对建图、定位、规划避障这套链路的熟悉程度。最后再分享一个小建议把你的调参过程和现场问题写成笔记我每次项目复盘都会发现之前踩过的坑在下一个项目里还会以类似的方式再出现一次。保持记录这是AGV项目里性价比最高的习惯。