
最近这些年最不缺的就是“ROS教程”但大多数人拿了一堆教程还是卡在装环境、跑通信、打开摄像头、调导航这几步。我自己这份 ROS 学习笔记与其说是笔记不如说是把从零到跑通真实小车的过程重新整理了一遍。文中涉及的都是评论区高频词鱼香ROS一键安装、多机通信、多个节点抢话题、usb_cam打开电脑自带摄像头、相机标定、SLAM建图自主导航。如果你是准备入坑 ROS 的新手或者已经装好了环境但不知道下一步该做什么这篇可以按顺序啃一遍。1. 环境搭建是ROS学习的第一道坎版本选择与一键安装1.1 先选对发行版不然装完了全是坑我在各种群里看到最多的求助就是“为什么我装完 ROS 跑不了”。大部分不是操作问题是版本压根没选对。ROS 对 Ubuntu 版本有严格对齐关系表格里这几个组合是经过大量开发者验证过的直接照抄最省心Ubuntu 版本推荐 ROS 版本说明Ubuntu 18.04ROS MelodicROS1老一批教程的标配现在慢慢少了Ubuntu 20.04ROS NoeticROS1ROS1 最后版本经典机器人课程都在用Ubuntu 22.04ROS2 HumbleROS2长期支持版现在新项目基本都选它Ubuntu 24.04ROS2 JazzyROS2新版本对应新 ROS2新手到底选哪个如果你是想跟国内经典课程学比如古月居、ROS-Academy 这些它们大量基于 ROS1 Noetic我建议直接用 Ubuntu 20.04 装 Noetic。如果你想一步到位学 ROS2以后做产品研发那就 Ubuntu 22.04 加 Humble。不要在 Ubuntu 24.04 上硬折腾 ROS1源码编译会把你劝退ROS1 在 24.04 上基本属于“能装但没必要”。我说的“版本对齐”背后有个现实原因ROS 二进制包仓库只对特定发行版发布预编译包。你装二进制包走的是 apt 仓库不对应版本就找不到包。源码编译虽然能绕过去但依赖库版本一变编译报错会一个接一个对新手来说纯属浪费时间。1.2 鱼香ROS一键安装到底做了什么值不值得用现在网上流行的是“鱼香ROS”一键安装脚本。这个脚本的思路很务实它把软件源配置、ROS 安装、rosdep 初始化这些事情打包到一个交互式脚本里你只要在终端执行一条命令就能进菜单选项选对应版本之后自动装完。说实话对于不想跟软件源搏斗的人这确实能节省一晚上时间。不过我要提醒一句一键安装帮你省了时间但也藏起了细节。很多人用一键装完之后出了问题完全不知道去哪排查。我的建议是第一台 ROS 环境你可以用一键安装但装完之后必须做一次自查把下面这几条走一遍env | grep ROS echo $ROS_DISTRO which roscore roscore如果 env 里看不到 ROS_MASTER_URI、ROS_DISTRO 这些变量说明环境没生效先 source 一下 ROS 的 setup 脚本再检查。如果你执行 roscore 之后终端卡住不动但没报错这才是正常的ROS Master 本来就是个常驻进程看到 Started core 之类输出就说明环境基本通了。我自己也用过一键安装实测它在 Ubuntu 20.04 上装 Noetic 的成功率挺高尤其是新手容易卡在初始化 rosdep 这一步脚本会帮你做国内镜像源的处理。但我不建议完全依赖它至少你要知道最终装到哪里去了一般装在 /opt/ros/noetic 或 /opt/ros/humble环境配置写在 ~/.bashrc 里。1.3 装完 ROS 后的工作空间创建与自检清单环境装好只是第一步真正学习时的日常操作都在工作空间里。所谓工作空间就是你的代码工程目录一般叫 catkin_ws 或者 dev_ws。ROS1 里创建和编译工作空间的命令是mkdir -p ~/catkin_ws/src cd ~/catkin_ws catkin_make编译完之后你会看到 devel 目录生成这时候必须 source 一下否则系统找不到你自定义的那些功能包source devel/setup.bash我见过太多次“明明编译成功了roslaunch 却提示找不到包”的情况九成是因为没 source。如果你不想每次开终端都手动 source可以把它写进 ~/.bashrcecho source ~/catkin_ws/devel/setup.bash ~/.bashrc source ~/.bashrc还有一个新手经常犯的问题安装了多个 ROS 版本或者电脑上既有系统 Python 又有 conda 的 Python导致 roscore 起来后节点互相崩溃。这种情况通常不是 ROS 本身的问题而是环境变量里的 Python 路径被改掉了。排查方法很简单看报错里有没有 Python 库找不到的关键字。如果有临时退出 conda 环境或者把 conda 从 PATH 里暂时去掉再跑。2. 节点、话题、消息先搞明白ROS的通信底层2.1 三个核心概念用群聊来理解在敲 ROS 命令之前我建议先用生活化的方式把它的通信模型搞明白。ROS 里最重要的三个词是节点、话题、消息。你可以把它们类比成微信群节点是群成员话题是聊天频道消息是每个人发到频道里的内容。节点之间不需要知道对方是谁只要知道话题名字就能收发数据。比如一个摄像头驱动节点它不停往“图像话题”里发图像消息另一个节点比如二维码识别节点只负责订阅这个话题就能拿到图像。背后谁发的、发了几次这些细节都不需要关心。这也是 ROS 最核心的设计理念解耦。ROS 里除了话题这种“广播式”通信还有服务Service一问一答式和动作Action长时间任务式。新手第一阶段不用全学先把话题吃透因为机器人里 80% 的数据流都是话题。激光雷达的 /scan、里程计的 /odom、底盘速度指令的 /cmd_vel、地图的 /map全都是话题。2.2 多个节点发布移动指令底盘节点到底听谁的这是评论区问得特别多的问题也是真实小车开发中最容易炸的场景手柄节点在发布 /cmd_vel导航节点 move_base 也在发布 /cmd_vel甚至你自己的遥控程序也在发 /cmd_vel底盘驱动节点收到这些指令时怎么取舍先说结论ROS 默认不会帮你仲裁。如果多个节点同时往同一个话题发布消息后面的消息会覆盖前面的。结果就是你以为导航在控制小车实际上手柄发出的高频指令把导航低频指令覆盖了小车行为完全不可控。正经的解决办法有三种第一种话题分路。手柄节点发 /cmd_vel_teleop导航节点发 /cmd_vel_nav底盘驱动节点订阅两个话题内部按优先级和时间戳决定用哪条。优先级的话一般手动遥控大于自动导航因为安全优先。第二种用现成的多路复用节点比如 topic_tools 里的 mux。它可以配置多个输入话题选其中一个转发到输出话题。命令大致是rosrun topic_tools mux cmd_vel cmd_vel_teleop cmd_vel_nav这样只需要一个 mux 节点做切换底盘驱动还是只订阅 /cmd_vel整体思路更干净。第三种在底盘驱动节点里做超时判断。无论哪个来源都记录消息到达的时间戳如果超过一定时间比如 0.5 秒没收到新指令默认输出零速度防止小车因为断连而一路狂奔。这部分我在代码里通常这样做def teleop_cb(msg): self.teleop_msg msg self.teleop_time rospy.Time.now() def nav_cb(msg): self.nav_msg msg self.nav_time rospy.Time.now() def pick_cmd(self): now rospy.Time.now() if self.teleop_msg and (now - self.teleop_time).to_sec() 0.5: return self.teleop_msg if self.nav_msg and (now - self.nav_time).to_sec() 0.5: return self.nav_msg return zero_vel()这个思路等于你自己实现了一个带优先级的仲裁器真实产品里非常常用。2.3 时间戳与 TF 坐标系新手最容易忽略的底层逻辑话题消息里常常带一个 header里面有 timestamp 和 frame_id。timestamp 是数据产生的时间frame_id 是数据所在的坐标系。这两个字段往往被初学者忽略但它们恰恰是很多疑难杂症的根源。比如激光雷达单纯发一堆距离数据不够导航模块还要知道这些数据是在哪个坐标系下测的。如果 frame_id 写错或者 TF 树里根本没有这个坐标系move_base 就会报 TF 相关的错误地图更新失败机器人表现为“原地卡死”。我习惯用两个命令排查这类问题rosrun rqt_tf_tree rqt_tf_tree rostopic echo /scan/headerrqt_tf_tree 显示当前所有坐标系之间的变换关系只要这棵树上有一个断点导航就没法工作。rostopic echo 看话题里是否有持续更新的时间戳如果时间戳是零或者跳变说明驱动没有正确填 header要回到驱动节点去修。另外一个隐藏大坑是 TF 树是树形结构不允许出现环。很多人在 robot_state_publisher 里重复发布了同一个 frame导致 TF 树出现循环roslaunch 不报错但一运行导航就乱。遇到这种情况仔细检查 urdf 里每个 joint 的 parent 和 child确保每个坐标系只被一个 parent 引用。3. 实操用 usb_cam 打开摄像头并完成相机标定3.1 用 usb_cam 打开电脑自带摄像头很多初学者第一步想做的事就是让机器人“看见”。最简单的是把电脑自带摄像头或 USB 摄像头接入 ROS用 usb_cam 驱动包。安装命令sudo apt install ros-noetic-usb-cam启动方式roslaunch usb_cam usb_cam-test.launch启动后查看话题rostopic list | grep camera rqt_image_view /usb_cam/image_raw如果 rqt_image_view 里面黑屏大概率是摄像头设备号不对。USB 摄像头有时是 /dev/video0有时是 /dev/video2。你可以先看ls /dev/video*然后在 usb_cam 的 launch 文件里把 video_device 参数改成实际设备号。笔记本自带摄像头有时会被其他程序占用比如浏览器、微信先关掉这些程序再试。权限问题也常见把当前用户加入 video 组sudo usermod -aG video $USER改完要重新登录一次才生效。从 usb_cam 发布出来的图像话题有 /usb_cam/image_raw 和 /usb_cam/camera_info。前者是原始图像后者是相机信息包括分辨率、内参矩阵、畸变模型。为什么 camera_info 很重要因为后面做视觉或标定都要基于它来算。3.2 相机标定的完整流程与参数落地相机标定目的就是求内参和畸变系数。你需要准备一张标定板网上找一张棋盘格图片打印出来贴在硬纸板上。标定板的内角点尺寸要准确量好单位是米。启动标定工具前先启动摄像头roslaunch usb_cam usb_cam-test.launch再启动标定程序rosrun camera_calibration cameracalibrator.py image:/usb_cam/image_raw camera:/usb_cam --size 8x6 --square 0.025这里 --size 8x6 表示棋盘格内角点的行数和列数注意不是格子的总数是内部角点数量。--square 0.025 是每个格子的边长单位米。常见 A4 纸打印的棋盘格格子边长一般是 2.5 厘米或 3 厘米。标定的过程听起来简单但做起来有讲究。你要拿着标定板在画面里慢慢移动让棋盘格出现在图像的四角、中间、左右远近不同位置同时让棋盘格有一定倾斜角度。标定程序会持续检测角点界面上看到角点分布越均匀样本越丰富最终结果越好。一般收集二三十个有效样本就可以点 CALIBRATE。如果界面上 CALIBRATE 按钮一直是灰色的说明采集的样本角度覆盖不够继续移动标定板别急着点。标定完成后点一下 SAVE 和 COMMIT程序会把参数写到 ~/.ros/camera_info 目录下。生成的文件长这样camera_matrix: rows: 3 cols: 3 data: [fx, 0, cx, 0, fy, cy, 0, 0, 1] distortion_coefficients: data: [k1, k2, p1, p2, k3]fx、fy 是焦距相关的量cx、cy 是光心坐标。畸变系数里 k 是径向畸变p 是切向畸变。这些参数后续在视觉抓取、目标测距、三维重建里都要用到属于机器人的“视觉底子”。这里要特别提醒三个坑。第一标定板不能反光打印出来要用哑光纸不然角点检测会跳动。第二标定环境的光线要稳定最好关掉自动曝光和自动对焦否则焦距一直在变标定结果完全不能用。第三很多人以为标定样本越多越好其实更重要的是“覆盖不同位置”集中在画面中央的几十张反而会让边缘畸变算不准。3.3 标定结果怎么用图像矫正与录制数据集拿到相机参数后最常见用途是图像去畸变。ROS 里的 image_proc 可以做实时矫正rosrun image_proc image_proc image:/usb_cam/image_raw它会自动读取 camera_info然后发布矫正后的图像话题名类似 /usb_cam/image_rect。如果你的视觉程序是基于矫正后图像来做的特征提取和标定匹配的精度会明显提升。另外如果你想录制自己的数据集rosbag 是绕不开的工具。比如同时录制图像、激光、里程计rosbag record /usb_cam/image_raw /scan /odom -O my_data.bag录完数据后可以用 rosbag play 回放相当于把现场还原了一遍。这一步在做算法调试时价值非常大因为你可以反复使用同一份数据不用每次都推车出去跑。工业相机比如海康相机也有基于 MVS SDK 的 ROS 驱动发布的话题结构同样是 image_raw 和 camera_info录制 rosbag 的思路完全一样。唯一要注意的是工业相机的像素格式可能和 usb_cam 不同有时要设置 image_transport 的编码参数否则画面颜色会错乱。4. 多机通信配置让两台电脑像同一台机器人那样协同4.1 多机通信的配置方式与原理机器人小车往往是多机结构工控机负责导航规划树莓派或单片机负责底盘驱动还有一台电脑负责远程监控。这时需要让所有机器上的 ROS 节点连接同一个 Master才能互相通信。配置核心就两个环境变量ROS_MASTER_URI 和 ROS_HOSTNAME。ROS_MASTER_URI 告诉每台机器去哪儿找 MasterROS_HOSTNAME 告诉别人自己这台机器的网络地址是什么。很多人只设置 ROS_MASTER_URI不设置 ROS_HOSTNAME结果就是节点注册时上报的是 localhost。其它机器因为这个地址访问不到通信失败。我见过最多的报错是 “Unable to communicate with master”多数情况就是因为 ROS_HOSTNAME 没配对。4.2 配置步骤与踩坑记录假设你有两台电脑主机 A 负责跑 roscoreIP 为 192.168.1.100从机 B 跑传感器和底盘节点IP 为 192.168.1.101。两台机器要在同一个局域网先互相 ping 通。主机 A 上写入 ~/.bashrcexport ROS_MASTER_URIhttp://192.168.1.100:11311 export ROS_HOSTNAME192.168.1.100从机 B 上写入 ~/.bashrcexport ROS_MASTER_URIhttp://192.168.1.100:11311 export ROS_HOSTNAME192.168.1.101然后两台机器都重新 source 一下主机先启动 roscore从机再启动任何节点主机上用 rostopic list 就能看到从机发布的话题。这个过程里最容易翻车的有四个地方。防火墙。Ubuntu 自带的 ufw 如果开着默认会拦截 11311 端口。你可以临时关闭防火墙或者只放行端口sudo ufw allow 11311多网卡。笔记本同时连着 Wi-Fi 和有线网时ROS_HOSTNAME 必须填能被对方访问的那个网卡的 IP不要填 localhost也不要填公网 IP。时间同步。多台机器的时间如果不一致消息里带的时间戳会有偏差轻则 TF 报警重则导航算法直接认为数据不合法。建议在局域网里配一台 NTP 时间服务器或者用 chrony 做同步。还有一个隐蔽问题千万不要在每台机器上各起一个 roscore。很多人的习惯是每台电脑开一个终端敲 roscore结果每台机器都有自己的 Master节点之间根本不在同一个网络里。记住一个 ROS 系统只有一个 Master其它机器都通过 ROS_MASTER_URI 连到它。5. SLAM 建图与自主导航仿真的完整流程5.1 Gazebo 仿真环境搭建要点在没有真实机器人的时候Gazebo 是最佳练兵场。它可以模拟差速轮运动、激光雷达、IMU、相机和物理碰撞。环境搭建看起来简单跑起来却经常崩我把关键点列出来sudo apt install ros-noetic-gazebo-ros-pkgs ros-noetic-gazebo-ros-control一个能用导航仿真的机器人模型至少要有这几部分urdf 模型描述、差速驱动插件、激光雷达插件。模型里必须有 和 否则机器人一进仿真就直接漏到地面以下。很多新手从网上下载的模型没有写惯性参数在 Gazebo 里就会看到机器人“陷进地板”。这个问题排查起来也简单打开模型看每个 link 是否都有 标签质量 m 至少给一个正的小值。激光雷达插件一般用 gpu_laser话题名往往是 /scan发布 sensor_msgs/LaserScan 消息。要注意的是 /scan 坐标系必须和导航配置里的 sensor_frame 一致不然 move_base 拿到激光数据不知道它从哪里来。Gazebo 卡顿也是常事。我通常把仿真物理更新频率调低比如 real_time_update_rate 改为 100 左右同时可以加 headless 模式运行也就是不显示图形界面只做后端计算。这样跑起来效率高很多适合批量测试。5.2 建图gmapping vs cartographerSLAM 建图是把传感器数据变成地图的过程。ROS 生态里最常用的 2D 建图算法是 gmapping 和 cartographer二者侧重点不一样算法核心思路适合场景上手难度gmapping基于粒子滤波室内小场景、单激光简单cartographer基于图优化大场景、支持多传感器融合中等偏复杂hector_slam基于扫描匹配无里程计、无人机简单但容易飘如果你在仿真里做 2D 建图用 gmapping 就够了参数也直白。关键参数一般有base_frame机器人底盘坐标系比如 base_linkodom_frame里程计坐标系一般是 odommap_frame地图坐标系一般是 maplinear_update 和 angular_update更新阈值建图时操作机器人慢慢移动让激光覆盖整个环境。建完后保存地图rosrun map_server map_saver -f ~/map/gmapping_map保存出来有两个文件gmapping_map.pgm 是图像文件gmapping_map.yaml 是地图的元数据。很多人不知道 yaml 里 resolution 表示每个像素对应多少米0.05 表示一个像素 5 厘米。这个值会影响导航精度不要随意改。5.3 自主导航move_base、AMCL 与路径规划有了地图之后接下来做自主导航。导航的整体结构是AMCL 负责定位move_base 负责路径规划costmap 负责代价地图base 底盘控制器负责执行。导航启动后你通过 RViz 给一个目标点程序会调用全局路径规划器在整张地图上找一条路径然后交给局部路径规划器边走边避障。局部规划器常用的是 DWA它根据机器人运动学模型在一堆候选速度里选一条最合理的轨迹。真正用起来你会发现导航问题永远不在“没路径”而在“路径奇怪”或者“到了目标点附近一直打转”。这里我积累了一套排查顺序先看 TF 树是不是完整map、odom、base_link、laser 这四者的关系必须都在缺任何一个都没法导航。再看 costmap 参数。inflation_radius 设置太小机器人会贴着墙走很容易刮蹭设置太大窄路会被膨胀层堵死机器人在门口来回转。经验值在 0.2 到 0.5 之间根据机器人实际尺寸调。最后看目标容差。到达目标点附近一直转圈可以调大 xy_goal_tolerance 和 yaw_goal_tolerance代价是停得没那么精确。实际项目中室外机器人通常要求 xy 容差小于 0.1 米室内精细对接时甚至要开到 0.02 米。还有一个容易忽略的问题机器人的初始位置。AMCL 是基于粒子滤波定位的首次启动时粒子分布全图撒开压力很大。你最好在 RViz 里用 2D Pose Estimate 手动给一个初始位置帮它快速收敛。如果不给小车跑起来可能在地图上呈现“漂移”。6. 从机械臂到整车ROS进阶方向与我的几个学习习惯6.1 机械臂开发中的 MoveIt 与运动规划导航玩顺之后很多人开始转向机械臂。机械臂在 ROS 里的主力框架是 MoveIt。它的核心思路是加载 urdf 模型配置规划组执行正逆运动学求解运动轨迹。用 MoveIt Setup Assistant 生成配置包之后你可以用 RViz 里的拖拽功能把机械臂末端拖到目标位置然后点击规划MoveIt 会调用 OMPL 库里的规划算法找一条无碰撞路径。但机械臂的坑比小车多得多。首先urdf 里每个关节的 limits 必须和真实机械臂手册一致角度范围如果差一点规划出来的轨迹可能直接撞到机械限位。其次运动规划前要设置起始状态很多人直接从默认关节角度开始规划结果机器人先做一个大幅度的“热身”动作这在产线上是不可接受的。第三奇异点附近规划器会报错因为逆运动学在这个位形附近无解。遇到这种情况可以在规划时加姿态约束或者把路径规划算法从 RRT 换成 RRTConnect成功率会高不少。我见过最无语的一类问题是 MoveIt 规划出来的轨迹仿真里很正常真机上一执行就抖。这通常不是 MoveIt 的问题是轨迹速度和加速度没做平滑处理。真实机器人整定控制器之前先降低速度缩放比例比如设置 execution_velocity_scaling_factor 为 0.1把机械臂动作放慢十倍让控制器有个适应过程。6.2 把视觉、底盘和机械臂串起来才是真正的机器人如果只是单个传感器或者单个底盘其实谈不上系统集成。ROS 真正的价值在于把视觉、激光、底盘、机械臂串成一套完整流程。我遇到过很多学员每个模块都单独跑通过但一合起来就乱套。关键原因是从模块思维切换到了系统思维。举一个典型例子用 YOLOv5 做无人小车目标识别跑起来的逻辑是把相机图像发布成 ROS 话题检测节点订阅图像后用训练好的模型识别目标把目标的位置转换成机器人坐标系下的坐标再把坐标发布成目标话题最后由上层决策节点根据目标话题发布 /cmd_vel 控制指令去追踪目标。整个链路里图像话题、检测结果话题、速度指令话题都解耦哪一环出了问题都可以单独测试。这个结构跟我之前讲的 mux 仲裁完全能搭配起来用手动遥控、目标追踪、导航避障三条指令流通过仲裁器切换。真正把整车跑顺后你会发现 ROS 最大的价值不是某个算法而是“可复用的软件架构”。今天换一个品牌的激光雷达只需要改驱动明天把底盘从差速换成阿克曼只需要改 base controller算法层完全不用动。这种模块化能力在做工程项目时太重要了。6.3 我的几个学习习惯送给卡在瓶颈期的你最后分享几个我自己的学习习惯不是什么机密但能让你少走很多弯路。第一不要只看不敲。网上视频再多你不动手在终端敲一遍等于没学。每次运行完一个 launch 文件我都会嵌套一层问题意识这个 launch 启动了哪些节点、发布了哪些话题、消息结构长什么样。用嚷下面的命令去验证rosnode list rostopic list rostopic echo /scan rqt_graph第二养成看日志的习惯。roslaunch 输出里经常含有 WARN 和 ERROR有人一看到 ERROR 就慌。其实很多 ERROR 是无害的比如“transform timeout”偶尔出现可能是启动顺序问题。正确的做法是看完整日志判断这个错误是持续性的还是一闪而过的持续性错误才值得花时间排查。第三学会自己改写简单节点。很多人跑了十年的 turtle_tf 示例但让自己写一个订阅 /scan、把最小障碍距离发布出去的节点就卡住了。我建议从最简单的 talker 和 listener 开始自己动手把话题改一改、消息类型换一换理解发布订阅的生命周期后面再碰底盘仲裁、多机通信这些复杂问题你就不会慌。说实话ROS 这门技术并不难难的是太多教程停留在“给你一个 demo跑起来就算成功”的阶段。环境、通信、传感器、底盘、导航这些节点串起来之后你才会真正理解机器人系统的复杂度在哪里。我这些笔记里没有放特别高深的理论但每一步都是我一次次踩坑后觉得最值得记录的东西照着走至少能让你少走我走过的那些弯路。