尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从零掌握rosbag:ROS数据录制与回放的核心工具实战指南

从零掌握rosbag:ROS数据录制与回放的核心工具实战指南 1. 为什么你的机器人项目迟早离不开rosbag先聊个真实经历。之前我在调试一台搭载激光雷达和IMU的差速小车跑SLAM建图时偶尔会触发一次定位跳变但问题不是每次都出现。当时我只能把小车推到走廊里反复跑同一个路径等到它再次抽风才能抓现场。折腾了两天硬件都换了三块最后才发现跳变根本不是硬件问题而是某个环节的时间戳异常。那两天我要是早一点把数据包录下来对着bag慢慢分析半小时就能定位到问题。这就是rosbag存在的理由。rosbag是ROS生态里最被低估的核心工具它本质上是一套针对ROS话题数据的记录与回放系统可以把节点之间发布的话题消息比如激光扫描、图像、IMU姿态、速度指令、坐标变换完整地按时间顺序写进一个bag文件。后面你可以像放录像一样把这些话题重新发出去让其他节点以为数据是实时从传感器来的。这种机制解决了我上面说的这类问题。简单理解rosbag就是机器人领域的飞行数据记录仪也是调试机器人时的后悔药。你车上没装行车记录仪出了事故说不清楚机器人没录bag算法出了问题也一样说不清楚。这篇教程不假设你有任何rosbag经验我会带你从创建第一个bag文件开始把录制、信息查看、回放这些基础流程全部过一遍。而且录制和回放有命令行和代码两种玩法命令行的效率高适合日常调试代码方式适合写进启动流程里做自动化所以C和Python两侧我都会给出可以直接抄的示例。2. 环境准备与rosbag背后的机制2.1 ROS版本与rosbag安装检查做任何事之前先确认环境。当前社区里ROS 1的Noetic和ROS 2的Humble是两个主流版本两个的rosbag工具差异很大。如果你看到的是rosbag这个命令属于ROS 1以ros2 bag开头的命令属于ROS 2。这里我以ROS 1 Noetic为例因为rosbag在ROS 1里是最成熟、最稳定的状态而且资料最多。用以下命令检查你的环境是否就绪# 检查ROS主环境变量如果显示setup路径就说明环境没问题 echo $ROS_DISTRO # 应该输出 noetic、melodic 或 kinetic 之类 # 检查rosbag命令是否可用 which rosbag # 正常会输出 /opt/ros/noetic/bin/rosbag # 检查rosbag的Python库是否可用 python3 -c import rosbag; print(rosbag.__file__)如果你用的是鱼香ROS一键安装脚本装的环境通常这套东西都是自带的不需要额外折腾。如果上面最后一步报ModuleNotFoundError先重装rosbag相关包sudo apt update sudo apt install ros-$ROS_DISTRO-rosbag注意ROS 1的Python类库依赖Python 2的版本已经在新版本里逐步淘汰Noetic上一般用的是Python 3报错时先确认你的python3是不是指向了正确的解释器。2.2 话题、节点与数据流的基本概念在操作rosbag之前得先明白它到底在录什么。ROS的通信核心是话题机制一个节点发布数据到某个话题另一个节点订阅这个话题就能收到数据。消息是数据的最小单元比如std_msgs/String就是一条字符串消息sensor_msgs/LaserScan就是激光雷达的一帧扫描数据。rosbag做的事情很纯粹它作为一个节点订阅你指定的话题把每一条消息连同它收到的时刻一起写进文件。录制完成后它就变成一个数据库回放时它又从数据库里把消息按原来的时间顺序发出去。整个设计有几个关键点值得你理解一下第一rosbag记录的是消息内容加时间戳不是传感器原始信号。也就是说录下来的数据已经被驱动节点转换成了标准ROS消息格式回放时不再需要真实的传感器硬件。第二话题的名字和消息类型必须匹配。录制时话题是/scan、类型是sensor_msgs/LaserScan回放时订阅/scan的节点收到的也是同一类型。如果类型不匹配订阅端的类型注册表就会报错这一点在回放阶段非常容易出现后面我会详细讲。第三rosbag不关心消息是谁发的它只认话题名。这就意味着哪怕你只有一个节点在发布消息它也能录哪怕你同时有十几个节点发布不同话题它也能一起录。2.3 bag文件格式与压缩逻辑bag文件内部是一段紧凑的二进制格式里面按记录块(record)组织每条消息一个消息记录每个记录都包含话题ID、消息数据和时间戳。文件内部还有连接记录保存了话题的元信息比如消息类型定义、md5校验和这些信息用于回放时校验类型一致性。你可能会问文件里为什么还要存消息类型定义这就是ROS跨机器、跨版本兼容的设计基础。在分布式系统中发布端的机器可能和回放端的机器装了不同版本的ROS编译包但只要消息定义一致bag就能正常读取。如果消息定义有变化rosbag会提示校验和不匹配这时候你就要考虑消息兼容性的问题了。录制出来的bag文件默认是不压缩的非常占空间。一个包含激光雷达和相机数据的bag几分钟就能轻松超过1GB。rosbag支持压缩录制命令如下rosbag record -j /scan /camera/image_raw-j参数表示用bzip2压缩录完的文件体积通常能减小到原大小的五分之一左右。代价是录制时的CPU占用会上升。我在不太老旧的笔记本上试过同时压缩几十个话题也没出现丢帧问题所以日常调试可以放心开压缩。3. 用rosbag record录制第一个数据包3.1 先准备一个稳定的数据源录制的最基础条件是得先有节点在发布消息。如果你手头有真实的机器人或者传感器直接跑驱动就行。如果没有硬件也可以用ROS自带的模拟节点来造数据。我推荐两种造数据的方案都适合练手第一种是用rostopic pub手动发布消息。这个方法的好处是可控性强想发什么内容就发什么内容适合验证rosbag的基本功能。打开一个终端执行# 发布一条文本消息频率10Hz rostopic pub -r 10 /chatter std_msgs/String hello rosbag第二种是启动一个turtlesim小乌龟模拟器。它会发布话题、订阅话题是ROS官方教程里的经典场景对入门来说非常直观而且能看到图形界面反馈# 终端A启动ROS核心 roscore # 终端B启动小乌龟模拟器 rosrun turtlesim turtlesim_node # 终端C启动键盘控制 rosrun turtlesim turtle_teleop_key这里有一个很多人第一次操作时会犯的错小乌龟模拟器本身有话题在发布比如/turtle1/pose是乌龟的位置/rosout是日志但如果你不操作键盘这些消息的发布频率很低录出来的bag文件会很小。录制教程里我建议把键盘控制终端也打开随便按几下方向键让乌龟动起来这样就能录到连续的速度指令消息。先看看当前有哪些话题确认数据源可靠rostopic list输出应该能列出/chatter、/turtle1/cmd_vel、/turtle1/pose、/rosout等话题。3.2 录制全部话题还是指定话题了解了数据源之后进入正式的录制环节。最粗暴的录制方式是-a参数录制所有话题# 在终端A执行注意当前目录会生成一个以时间戳命名的bag文件 rosbag record -a执行这个命令后CtrlC停止你就得到了一个类似2025-05-15-15-30-00.bag的文件。这个文件里包含了所有已发布话题的数据。但-a在真实项目中不推荐原因有两个一是很多系统级话题的发布频率很高比如/rosout、/tf、/odom这些数据量叠加起来非常浪费磁盘空间二是录制范围太广回放时容易出现话题互相干扰的情况而且回放的数据量变大之后CPU占用也会增加导致其他节点处理不过来。更合理的做法是指定话题录制这也是我日常用的方式# 指定多个话题录制话题之间用空格分开 rosbag record /chatter /turtle1/cmd_vel /turtle1/pose # 指定输出文件名 rosbag record -O turtle_demo.bag /chatter /turtle1/cmd_vel /turtle1/pose # 带时间限制录制5秒后自动停止 rosbag record -O turtle_demo.bag --duration5 /chatter /turtle1/cmd_vel /turtle1/pose-O参数一次性指定了输出文件名注意大写-O是覆盖写小写-o是前缀加时间戳两者差很多。--duration5表示5秒后自动结束这个在自动化测试场景里非常实用。录制过程中rosbag终端会实时打印每条消息的信息包括话题名、消息类型、时间戳和大小这些都是正常输出不用慌。3.3 录制时如何保证数据完整录制的完整性和稳定性是调试质量的关键这里有几个我踩过坑之后才总结出来的习惯。第一录制之前先确认目标话题的频率。可以用rostopic hz看看话题发布是否稳定rostopic hz /turtle1/pose如果话题频率忽高忽低录出来的bag数据分布就不均匀回放时下游算法可能出错。这时候先处理消息源的问题再开始录制。第二合理使用--split参数控制单个bag文件的大小。长时间录制时单个bag文件会变得巨大后续拷贝和处理都不方便。可以用--split --size1024把bag按1GB大小自动切分切分后的每个文件独立可读。对长时间运行的机器人来说这个设置几乎是必需品。第三录制时保留当前系统上下文。如果你在录bag前修改了机器人参数或者换了一套地图最好在文件名里体现出来比如turtle_demo_20250515_mapA.bag。否则过了几天你会发现桌面上躺着几十个2025-05-15-15-xx-xx.bag谁都想不起来哪一个是哪个实验录的。这个习惯能帮你省下巨大的复盘成本。提示尽量不要用rosbag record -a来录制大项目这会同时录下IMU和相机图像等高带宽话题不仅文件体积暴涨而且回放时容易导致消息拥塞影响下游算法的实时性。4. 回放前的检查rosbag info与话题分析4.1 查看bag文件的基本信息录制完成后先别急着回放。养成先检查再使用的习惯对后续排障很有帮助。用rosbag info命令查看bag文件的元信息rosbag info turtle_demo.bag正常情况下输出类似于path: turtle_demo.bag version: 2.0 duration: 5.2s start: May 15 2025 15:30:01.50 (1715772601.50) end: May 15 2025 15:30:06.70 (1715772606.70) size: 18.2 KB messages: 522 compression: none topics: /chatter 5 msgs : std_msgs/String /turtle1/cmd_vel 515 msgs : geometry_msgs/Twist /turtle1/pose 2 msgs : turtlesim/Pose这里有几个字段值得关注。duration是bag的时间跨度不是录制过程的真实时长而是第一条消息和最后一条消息之间的时间差。如果某个低频话题最后一条消息离其他话题很久duration会显得比实际录制时间长很多这是正常的。messages是总消息数。如果你指定的话题频率很高这个数字会很大比如/turtle1/cmd_vel是10Hz录了5秒结果大约50条但如果你按键盘按得快cmd_vel可能发布了500多条这个数字就会明显上涨。topics下面是bag包含的话题列表和各自的消息数、类型。这是后续排查问题的基础比如回放的时候节点没收到数据先到这里看看是不是录漏了话题。4.2 更细粒度的数据检查rosbag info只给出整体概况想深入了解某一段数据的分布可以用Python脚本遍历bag里的消息。下面这段脚本可以按话题统计消息间隔import rosbag bag rosbag.Bag(turtle_demo.bag) prev_time None for topic, msg, t in bag.read_messages(topics[/turtle1/cmd_vel]): if prev_time is not None: dt (t - prev_time).to_sec() print(f间隔: {dt:.4f}s, 频率: {1.0/dt:.2f}Hz) prev_time t bag.close()这种统计方式能直观看到话题的消息分布是否均匀。我在调试时遇到过一种情况/odom的频率是50Hz但每条消息的时间间隔忽大忽小最大的间隔能达到100ms。这种数据回放时下游的里程计融合算法就会抖动。知道根源在时间戳上就可以针对性地修正驱动或者做消息平滑。另外建议录制完之后用rosbag filter做一次瘦身。比如你只需要/turtle1/pose这个话题不想被其他数据干扰可以这样# 从原bag中提取指定话题生成一个只含该话题的新bag rosbag filter turtle_demo.bag pose_only.bag topic /turtle1/pose这个命令的第二个参数是过滤表达式支持Python语法可以按话题、按时间、按甚至消息内容做条件过滤。比如提取前10秒的数据rosbag filter turtle_demo.bag first10.bag t.to_sec() 1715772601.50 10.04.3 为什么回放前要做好规划回放前规划的核心问题是你将把哪个话题的数据发出去发给谁。有时候你只需要单独回放某个话题做单元测试比如验证一个订阅激光数据的节点能不能正常工作。这种情况下回放时最好只发布指定的话题不要让bag里所有话题一股脑发出来否则上游节点消费速度跟不上就可能出现消息挤压和延迟干扰测试结果。还有一种更复杂的情况你要回访整段录制的原始状态让所有节点都参与。这种全量回放要求各个话题的时序关系尽量保留原状而rosbag是有能力做到的因为每条消息都带有原始时间戳。5. 用rosbag play回放数据5.1 基本回放流程回放的核心命令是rosbag play# 基本回放所有话题都会发布 rosbag play turtle_demo.bag # 指定话题回放只发布点名的话题 rosbag play turtle_demo.bag --topics /turtle1/cmd_vel # 循环回放适合长时间稳定性测试 rosbag play turtle_demo.bag -l回放之前老规矩先启动roscore或者你需要的核心节点。比如只想看小乌龟的运动回放在终端A里启动roscore终端B里启动turtlesim_node终端C里执行rosbag play turtle_demo.bag这时候你会看到小乌龟自己活过来了在图形界面里按照之前录制的轨迹运动。这个过程关键是时间戳。正常情况下rosbag play会按照消息的原始时间戳等间隔发布消息。比如你录制时/chatter是10Hz回放时它也会按10Hz发送。这样下游节点就能看到与实时采集一致的节奏这是rosbag的核心价值之一。如果你觉得等太久可以用时间比例因子来加速回放# 2倍速回放相当于把消息间隔缩短为原来的一半 rosbag play turtle_demo.bag -r 2.0 # 0.5倍速回放相当于慢放 rosbag play turtle_demo.bag -r 0.5我在调SLAM算法时经常用-r 0.5放慢回放看建图的整体进展在跑长时间压力测试时用-r 5.0加速处理缩短整体测试时间。注意倍速回放会让消息的时间间隔重新缩放但对每一条消息的原始时间戳不做线性映射下游节点如果依赖时间戳做积分会造成数据与预期不一致比如/odom和/imu之间的时间关系会走样。5.2 回放的高级参数除了频率相关参数rosbag play还有几个参数在实战里很常用。第一个是-d延后启动。有时你需要等目标节点的所有模块都ready了再开始发数据比如等待某个视觉节点加载模型这可能需要几秒钟。-d 5表示等待5秒再开始回放。第二个是--pause回放开始时暂停等手动按空格键继续。这个在调试时非常有用你可以先启动好所有节点确认系统稳定后再手动启动数据流# 回放启动后进入暂停状态按空格键继续 rosbag play turtle_demo.bag --pause第三个是--clock把bag里的时间发布为ROS的/clock话题。这个参数在做仿真和算法测试时很关键它让整个系统的运行时间跟着bag时间走保证时间一致性。不过要注意--clock只有在使用仿真时间use_sim_time为true时才有效。如果你没有把节点的参数设置为仿真时间各节点还是取系统墙上时钟和bag时间对不上时间戳相关的问题就来了。回放时还可以通过-t指定一段数据范围比如回放bag的第3秒到第7秒的部分rosbag play turtle_demo.bag -t 3.0 7.0这个功能在做局部问题复现时很高效比如你知道第4到6秒之间出现了定位跳变直接截取这一段反复回放方便观察现场。5.3 回放时的时间戳与目标节点匹配问题回放中最常见的坑是bag里录制的话题和你当前运行的节点话题名不一致。ROS话题机制的严格性是话题名不匹配消息就收不到而且ROS不会给出任何直接警告。节点A发布了/scan节点B订阅了/laser_scan两个节点都正常启动但B就是收不到数据。这种问题在回放时最容易发生因为bag是旧环境录的而当前环境的话题名可能已经变更。解决办法是在回放时做话题重映射# 把bag里的 /scan 话题在发布时重命名为 /laser_scan rosbag play turtle_demo.bag /scan:/laser_scan这个话题重映射的思路也可以反过来用。录制时你可能把雷达话题设为/scan目标算法节点期望的也是/scan但另外还有一个传感器占用了/scan不想冲突那就在线重命名各玩各的。另外需要注意bag里的消息类型。用rosbag info看类型是sensor_msgs/LaserScan回放目标节点却期望sensor_msgs/PointCloud2这种情况下rosbag play本身不会报错但订阅方节点反序列化时就会失败或者根本匹配不上。6. 用C代码读取和写入rosbag命令行适合交互式操作但要集成到程序里自动化处理就得用代码。ROS提供了两种主流编程接口C用rosbag库Python用rosbag模块。下面分别给出可以直接编译运行的完整示例。6.1 C录制与回放示例先写一个写入bag的例子用发布字符串消息和Twist消息来演示基本流程#include rosbag/bag.h #include std_msgs/String.h #include geometry_msgs/Twist.h #include ros/ros.h int main(int argc, char** argv) { ros::init(argc, argv, bag_writer_cpp); // step 1: 创建bag对象并打开文件写模式 rosbag::Bag bag; bag.open(cpp_demo.bag, rosbag::bagmode::Write); // step 2: 构造消息 std_msgs::String str_msg; str_msg.data hello rosbag from C; geometry_msgs::Twist twist_msg; twist_msg.linear.x 0.5; twist_msg.angular.z 0.2; // step 3: 给消息打上时间戳并写入 ros::Time current_time ros::Time::now(); bag.write(/chatter, current_time, str_msg); bag.write(/cmd_vel, current_time, twist_msg); ros::Duration(0.1).sleep(); ros::Time later_time ros::Time::now(); bag.write(/chatter, later_time, str_msg); // step 4: 关闭文件 bag.close(); ROS_INFO(bag written.); return 0; }这段代码的关键点有两个。第一个是bag.write(topic, time, msg)的调用写入的第一参数是话题名第二参数是ros::Time类型的时间戳这个时间戳是下游按时间顺序回放的依据。如果你随便给时间回放的时候消息顺序就会错乱。第二个是写完记得bag.close()这和文件操作一样不关闭可能导致缓冲区没刷盘数据不完整。再写一个读取bag的例子#include rosbag/bag.h #include rosbag/view.h #include std_msgs/String.h #include geometry_msgs/Twist.h #include ros/ros.h #include iostream int main(int argc, char** argv) { ros::init(argc, argv, bag_reader_cpp); // step 1: 打开bag只读模式 rosbag::Bag bag; bag.open(cpp_demo.bag, rosbag::bagmode::Read); // step 2: 创建View可以指定要读取的话题 std::vectorstd::string topics; topics.push_back(/chatter); topics.push_back(/cmd_vel); rosbag::View view(bag, rosbag::TopicQuery(topics)); // step 3: 遍历每一条消息 for (const rosbag::MessageInstance m : view) { std::string topic m.getTopic(); if (topic /chatter) { std_msgs::String::ConstPtr msg m.instantiatestd_msgs::String(); if (msg ! nullptr) { std::cout read from /chatter: msg-data std::endl; } } else if (topic /cmd_vel) { geometry_msgs::Twist::ConstPtr msg m.instantiategeometry_msgs::Twist(); if (msg ! nullptr) { std::cout read from /cmd_vel, linear.x msg-linear.x , angular.z msg-angular.z std::endl; } } // 打印消息的接收时间 std::cout timestamp: m.getTime().toSec() std::endl; } bag.close(); return 0; }这里要注意m.instantiateT()的用法。rosbag内部通过类型注册表把二进制消息反序列化成具体的ROS消息类型。如果你在代码里实例化的类型和bag里存储的不一致instantiate会返回一个小类型的空指针比如消息是std_msgs/String你却用instantiategeometry_msgs::Twist()返回的就是nullptr。所以每次拿到消息实例后先判断是不是对应类型再访问字段。编译C示例需要在你自己的CMakeLists.txt里添加依赖find_package(catkin REQUIRED COMPONENTS roscpp rosbag std_msgs geometry_msgs ) add_executable(bag_writer_cpp src/bag_writer_cpp.cpp) add_dependencies(bag_writer_cpp ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(bag_writer_cpp ${catkin_LIBRARIES}) add_executable(bag_reader_cpp src/bag_reader_cpp.cpp) add_dependencies(bag_reader_cpp ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) target_link_libraries(bag_reader_cpp ${catkin_LIBRARIES})6.2 C代码结构要点与潜在坑位C读写rosbag最容易出错的地方是消息的内存管理。rosbag::View遍历时返回的MessageInstance其实是对bag内部缓冲区的引用不是深拷贝。意思是你拿到的ConstPtr消息在迭代器移动到下一条之后可能就失效了。如果你需要把某条消息保存下来后续再用一定要手动复制一份比如先*msg拷贝成新对象再存到容器里。还有一点rosbag::View默认遍历bag里所有话题的所有消息如果你只关心其中一个话题不指定TopicQuery会白白遍历大量无关数据。在小文件上感受不明显但在几个GB的bag上这个差异就是几十秒和几分钟的区别。所以读取时一定要养成用TopicQuery限制话题范围的习惯。时间戳的处理也要注意。rosbag里的时间戳是ros::Time类型它实际是两个32位整数一个是秒一个是纳秒。如果你打印时用toSec()得到浮点数直接比较浮点数可能会由于精度误差出现问题建议用.sec和.nsec结合的方式来做精确比较。6.3 Python录制与回放示例Python端的写法更简洁非常适合快速写分析脚本。先看写入import rosbag import rospy from std_msgs.msg import String from geometry_msgs.msg import Twist # 初始化ROS节点因为时间戳需要用 rospy.Time rospy.init_node(bag_writer_py) # 打开bag写模式 bag rosbag.Bag(python_demo.bag, w) # 构造消息 str_msg String() str_msg.data hello rosbag from Python twist_msg Twist() twist_msg.linear.x 0.5 twist_msg.angular.z 0.2 # 获取当前时间并写入 stamp rospy.Time.now() bag.write(/chatter, str_msg, stamp) bag.write(/cmd_vel, twist_msg, stamp) # 再写入一条 rospy.sleep(0.1) stamp2 rospy.Time.now() str_msg.data hello rosbag from Python - second message bag.write(/chatter, str_msg, stamp2) # 关闭bag bag.close() print(bag written.)然后是读取import rosbag bag rosbag.Bag(python_demo.bag, r) for topic, msg, t in bag.read_messages(topics[/chatter, /cmd_vel]): if topic /chatter: print(f/chatter: {msg.data}, time: {t.to_sec():.3f}) elif topic /cmd_vel: print(f/cmd_vel: linear.x{msg.linear.x}, angular.z{msg.angular.z}, time: {t.to_sec():.3f}) bag.close()注意bag.write()的参数顺序和C略有不同。C是write(topic, time, msg)Python是write(topic, msg, t)时间戳放在第三个参数传错顺序会在运行时直接报类型错误这个细节很多人第一次写都会踩到。Python读取时返回的t是rospy.Time它有一个to_sec()方法可以直接转浮点秒。如果你是在独立脚本里用没有启动ROS节点也没关系rosbag.Bag的读取不依赖rospy.init_node但写入时用rospy.Time.now()就需要初始化节点否则会报ROS node not initialized的错。6.4 Python脚本的常见运行环境问题在跑Python rosbag脚本时遇到最多的是环境问题主要有三类。第一类import rospy失败。原因是当前shell没有source ROS环境。解决方法是执行source /opt/ros/noetic/setup.bash或者在代码里写死环境路径但不建议这么做不够灵活。第二类import rosbag失败。这在Noetic上比较常见rosbag模块属于ros-noetic-rosbag包如果你的ROS是最小化安装可能缺失。用sudo apt install ros-noetic-rosbag补装即可。第三类Python版本混乱。如果系统里同时装了Python 2和Python 3ROS 1的Melodic之前版本默认用Python 2Noetic才切到Python 3。检查你的rosbag包是给哪个版本装的确保你运行脚本的Python解释器是对的那一个。一个快速的排查命令python3 -c import rosbag; print(rosbag.__file__)如果出错而python2 -c import rosbag; print(rosbag.__file__)正常那就说明你的rosbag模块装在了Python 2体系下。7. 常见问题与排查技巧实录7.1 录制的bag文件是0字节一种很常见的现象是执行rosbag record后立即停止生成的文件大小为0。这通常是因为录制时间太短还没来得及写入任何消息。rosbag写入文件是有缓冲机制的停止时会把缓冲区的内容落盘但如果根本没有订阅到任何消息自然就写不出内容。先检查一下你录制时是否真的在发布话题。用rostopic echo看一眼话题是否有消息输出在录制命令执行前先确认消息在流动。还有一种情况是录制的时间虽然足够但话题名拼写不对比如你录/scan但实际雷达发的是/laser_scan自然一条消息都录不到。7.2 回放时目标节点收不到数据这个问题可以从三个角度排查。话题名是否匹配。用rostopic list看当前系统里有哪些话题再看rosbag play发布出来的话题名如果两者对不上可以用话题重映射解决。消息类型是否匹配。用rosbag info查看bag里的消息类型用rostopic type /话题名查看运行中的节点期望的话题类型。两边不一致的话要么找类型匹配的bag要么改消息适配层。时间戳是否一致。如果目标节点设置了use_sim_time为true但回放时没有加--clock节点内部的时间就不会增长算法会一直等新时间戳表现出来就是卡住或者没反应。反过来也是一样节点用真实时间bag用仿真时间消息到达后节点可能以为消息是未来发来的直接丢弃。7.3 回放卡顿或消息丢失回放时如果CPU占用过高或者下游节点处理不过来消息就会被直接丢弃。这个问题有两个解法。第一个是用倍速回放降速比如-r 0.5给下游节点留出更多处理时间。第二个是只回放必要时的话题用--topics参数限制数据量。如果你同时在跑建图算法、导航算法还回放了图像、点云、里程计等多个高带宽话题瓶颈主要在下游节点的消息队列长度上。这时候可以检查目标节点的订阅队列大小。比如用rospy订阅时subscriber rospy.Subscriber(/scan, LaserScan, callback, queue_size10)如果queue_size太小处理不过来就会丢消息。7.4 bag文件损坏或无法读取有时候因为磁盘满了或者录制中途强杀进程bag文件会损坏。rosbag提供了一些修复尝试。先用rosbag reindex试一下rosbag reindex broken.bag这个命令会扫描bag文件的数据块重建索引并把原始文件备份为.orig。多数情况下能恢复出大部分消息。但如果文件连头信息都损坏了那就没什么好办法了所以录制长时间数据时我会习惯多开一个终端定期用rosbag info看一眼确认文件还在正常写入。7.5 从错误中总结的操作习惯速查表问题最常见原因快速排查动作bag文件为0字节录制时无消息发布或话题名错误rostopic list、rostopic echo确认消息流回放无数据话题名、消息类型、TimeSource不匹配三重检查list、type、clock回放卡顿数据量过大、下游处理慢减倍速、限定话题、调大queue_size时间戳错乱使用了错误的时间源检查use_sim_time与--clock是否一致文件损坏写盘中断、磁盘满清理磁盘、用rosbag reindex修复8. 从命令行到代码的进阶路线到这里rosbag最基本的录制、查看、回放以及两套语言的手写读写能力都已经覆盖了。下一步你可以顺着两个方向再深入。第一个方向是bag数据的量化分析。python脚本遍历bag只是起点更高级的用法是把bag里的消息转成numpy数组直接做误差分析、轨迹可视化、延迟计算。比赛中经常要对算法做metrics评估比如轨迹误差、耗时中位数这些都是用脚本离线跑bag算出来的。这个方向还可以配合rosbag filter做数据清洗把一条长bag按条件切片成若干子集再逐个分析。第二个方向是回放自动化和测试流水线。把回放命令写进launch文件或者CI脚本用roslaunch来统一启动所有节点和数据流做到一键复现问题。更进一步可以结合rostest做回归测试每次代码改动后自动跑一段bag数据验证算法输出是否发生退化。这是工程化项目必备的能力。记忆里有一段特别有感触。有一次团队做了新的里程计模块跑一轮真机实验要半小时成本很高。后来我搭了一套基于rosbag的离线回放流程把之前真机录的十组bag全部自动化回放一遍输出每个模块的精度指标二十分钟就能完成以前两三个小时才能做完的验证。从那以后rosbag就成了我们团队所有算法开发的基础设施几乎所有新需求都是先录数据再离线验算法最后才考虑上真机。最后分享一个小技巧。录制长时间任务时可以考虑给rosbag录制的终端加一个定时存档脚本每隔一定时间把当前的bag拷贝到备份目录并附带一份rostopic hz的输出快照这样即使中途系统崩溃也能从最近的备份点恢复大部分数据。我在多传感器融合的项目里一直这么做多次避免了录了一下午数据却因为机器重启全部丢失的惨剧。rosbag这个东西门槛很低但用不用得好真的会决定你调试机器人的效率。希望这篇教程能帮你把第一个bag顺利录出来、放出来、用起来。
返回列表