ROS 实战指南:从 rosbag 高效提取 RGB 与深度图数据
1. rosbag基础操作与核心概念
在机器人开发领域,rosbag就像是一个万能的数据记录仪。想象一下你正在调试一个机器人视觉系统,传感器数据像流水一样不断涌来,这时候rosbag就能帮你把关键数据"冻住",方便后续反复分析。我经常用它来录制Kinect或RealSense相机的RGB和深度数据流,效果非常稳定。
录制数据就像用手机拍视频一样简单。打开终端输入:
rosbag record -O my_data /rgb/image_raw /depth_to_rgb/image_raw这条命令会把/rgb/image_raw和/depth_to_rgb/image_raw两个话题的数据保存到my_data.bag文件。我习惯用大写的-O参数指定完整文件名,避免自动生成的时间戳造成混乱。
回放数据时有个实用技巧:用--pause参数先暂停播放,等所有节点都准备好再按空格键开始:
rosbag play --pause my_data.bag查看bag内容我推荐两个方法:
- 快速预览:
rosbag info my_data.bag会显示包含的话题、消息数量和时间范围 - 详细检查:
rqt_bag工具可以可视化消息时间线,特别适合检查RGB和深度图是否同步
提示:录制前务必用
rostopic list确认话题名称,不同相机驱动发布的话题名可能不同
2. Python提取RGB与深度图的完整方案
第一次提取深度图时我踩过大坑——保存的图片全是黑的!后来发现是忽略了数据格式转换的问题。下面这个脚本是我优化过的版本,解决了深度图归一化和格式转换的常见问题。
2.1 环境准备
需要安装的关键包:
pip install opencv-python rosbag rospy如果遇到cv_bridge报错,需要从源码编译:
git clone https://github.com/ros-perception/vision_opencv.git cd vision_opencv/cv_bridge mkdir build && cd build cmake .. && make sudo make install2.2 核心代码解析
import os import cv2 import numpy as np import rosbag from cv_bridge import CvBridge def extract_images(bag_path, rgb_topic, depth_topic, output_dir): bridge = CvBridge() rgb_dir = os.path.join(output_dir, 'rgb') depth_dir = os.path.join(output_dir, 'depth') os.makedirs(rgb_dir, exist_ok=True) os.makedirs(depth_dir, exist_ok=True) with rosbag.Bag(bag_path, 'r') as bag: for topic, msg, t in bag.read_messages(): if topic == rgb_topic: cv_image = bridge.imgmsg_to_cv2(msg, "bgr8") timestamp = msg.header.stamp.to_nsec() cv2.imwrite(f"{rgb_dir}/{timestamp}.png", cv_image) if topic == depth_topic: # 关键步骤:正确处理深度图 depth_img = bridge.imgmsg_to_cv2(msg, "passthrough") depth_array = np.array(depth_img, dtype=np.float32) # 归一化处理 cv2.normalize(depth_array, depth_array, 0, 1, cv2.NORM_MINMAX) depth_img = (depth_array * 255).astype(np.uint16) timestamp = msg.header.stamp.to_nsec() cv2.imwrite(f"{depth_dir}/{timestamp}.png", depth_img)这段代码有几个关键点:
- 使用
passthrough模式读取深度图,保留原始精度 - 深度值归一化到0-1范围,避免数据截断
- 转换为16位PNG保存,确保深度信息不丢失
2.3 实际应用示例
假设我们用Azure Kinect录制了数据:
extract_images( bag_path='kinect_data.bag', rgb_topic='/rgb/image_raw', depth_topic='/depth_to_rgb/image_raw', output_dir='./extracted_images' )运行后会生成如下目录结构:
extracted_images/ ├── rgb/ │ ├── 1623456789123456789.png │ └── ... └── depth/ ├── 1623456789123456789.png └── ...3. 深度图处理的进阶技巧
3.1 数据对齐与同步
RGB和深度图的时间戳经常有微小差异,我推荐用message_filters进行精确同步:
import message_filters def callback(rgb_msg, depth_msg): # 处理同步后的消息 pass rgb_sub = message_filters.Subscriber('/rgb/image_raw', Image) depth_sub = message_filters.Subscriber('/depth/image_raw', Image) ts = message_filters.ApproximateTimeSynchronizer( [rgb_sub, depth_sub], queue_size=10, slop=0.1) ts.registerCallback(callback)这个方案允许0.1秒的时间容差,在实际测试中效果很好。
3.2 深度图可视化
调试时可以用伪彩色显示深度范围:
def depth_to_colormap(depth_img): depth_colormap = cv2.applyColorMap( cv2.convertScaleAbs(depth_img, alpha=0.03), cv2.COLORMAP_JET ) return depth_colormap3.3 性能优化建议
处理大型bag文件时,我总结出几个提速技巧:
- 使用
--chunksize=1024参数录制bag文件 - 提取时先过滤话题:
bag.read_messages(topics=[rgb_topic, depth_topic]) - 多线程处理图像保存(注意OpenCV的线程安全问题)
4. 常见问题解决方案
4.1 深度图全黑问题
这是新手最常见的问题,根本原因是:
- 深度图原始数据是32位浮点(单位:米)
- OpenCV默认保存为8位图像(0-255)
正确做法是:
depth_img = bridge.imgmsg_to_cv2(msg, "32FC1") depth_meters = depth_img.copy() depth_img = np.clip(depth_img, 0, 5) # 限制在5米范围内 depth_img = (depth_img / 5.0 * 65535).astype(np.uint16) # 转为16位4.2 时间戳对齐
我开发时遇到过一个棘手问题:RGB和深度图的时间戳虽然接近但不同步。解决方案是:
- 提取所有时间戳到数组
- 为每个RGB图寻找最近的深度图
- 最大时间差超过阈值(如50ms)则丢弃
4.3 话题名称差异
不同设备的话题命名规范不同,这里列出常见组合:
| 设备类型 | RGB话题 | 深度话题 |
|---|---|---|
| Azure Kinect | /rgb/image_raw | /depth_to_rgb/image_raw |
| RealSense D435 | /camera/color/image_raw | /camera/aligned_depth/image_raw |
| ROS Bag | /camera/rgb/image_raw | /camera/depth/image_raw |
5. 实战案例:制作SLAM数据集
最近帮实验室搭建了一个SLAM测试系统,需要从bag文件生成TUM格式数据集。关键步骤包括:
- 创建时间戳关联文件associate.txt
rgb_files = sorted(os.listdir('rgb')) depth_files = sorted(os.listdir('depth')) with open('associate.txt', 'w') as f: for rgb, depth in zip(rgb_files, depth_files): f.write(f"{rgb[:-4]} {depth[:-4]}\n")- 转换深度图单位为毫米(TUM格式要求)
depth_img = cv2.imread(depth_path, cv2.IMREAD_UNCHANGED) depth_img = (depth_img * 1000).astype(np.uint16) # 米转毫米- 验证数据一致性
import matplotlib.pyplot as plt plt.figure(figsize=(12,5)) plt.subplot(121) plt.imshow(cv2.cvtColor(rgb_img, cv2.COLOR_BGR2RGB)) plt.subplot(122) plt.imshow(depth_img, cmap='jet') plt.show()这套方案已经稳定运行了半年多,处理过100+小时的实验数据。最关键的收获是:一定要在数据采集阶段就检查bag文件质量,避免后期处理时才发现问题。
