NCLT 数据集转 ROS Bag 实操指南:从数据解析到格式适配

NCLT(Northwestern University Large Scale Tracking)数据集是机器人与自动驾驶领域的经典公开数据集,包含激光雷达、相机、IMU、GPS 等多传感器数据,广泛用于 SLAM、目标跟踪等算法测试。而 ROS(Robot Operating System)的 Bag 格式是机器人开发中数据存储与回放的标准格式,将 NCLT 数据集转换为 ROS Bag,可直接复用 ROS 生态工具链进行算法调试与验证。本文详解 NCLT 数据集转 ROS Bag 的完整流程,包括数据预处理、格式映射、Bag 生成,附关键代码与配置,帮助快速完成转换。

一、核心准备:环境与数据集预处理

1. 依赖环境安装

转换过程依赖 ROS 环境与 Python 数据处理库,需提前配置:

  • ROS 安装:推荐 ROS Noetic(Ubuntu 20.04)或 Melodic(Ubuntu 18.04),参考 ROS 官网 完成安装;
  • Python 依赖:安装数据解析与 ROS 接口库:

pip install numpy pandas pykitti rosbag sensor_msgs cv_bridge

  • 辅助工具:安装激光雷达、图像数据处理工具:

sudo apt-get install ros-noetic-pcl-ros ros-noetic-image-transport

2. NCLT 数据集下载与目录结构

  • 下载地址:NCLT 官网,选择目标序列(如 2012-01-08),下载以下核心数据:
    • laser_data/:Velodyne HDL-64E 激光雷达点云数据(.bin 格式);
    • images/:左右目相机图像数据(.png 格式);
    • imu/:IMU 数据(.txt 格式);
    • gps/:GPS 数据(.txt 格式);
    • timestamp/:各传感器时间戳文件(.txt 格式);
  • 解压后数据集目录结构(简化):

nclt_dataset/

├── 2012-01-08/

│ ├── laser_data/

│ │ ├── 0000000000.bin

│ │ ├── 0000000001.bin

│ │ └── ...

│ ├── images/

│ │ ├── left/

│ │ └── right/

│ ├── imu/imu.txt

│ ├── gps/gps.txt

│ └── timestamp/

│ ├── laser_timestamp.txt

│ ├── image_left_timestamp.txt

│ └── ...

二、转换核心流程:数据解析与 Bag 生成

步骤 1:数据时间戳同步(关键!确保多传感器时序一致)

NCLT 数据集各传感器数据独立存储,需通过时间戳文件对齐,生成同步索引:


import pandas as pd

def load_timestamps(timestamp_path):

"""加载时间戳文件(格式:秒.微秒)"""

timestamps = pd.read_csv(

timestamp_path, header=None, names=["timestamp"]

)["timestamp"].values

# 转换为 ROS 时间戳格式(秒 + 纳秒)

ros_timestamps = []

for ts in timestamps:

sec = int(ts)

nsec = int((ts - sec) * 1e9)

ros_timestamps.append((sec, nsec))

return ros_timestamps

# 加载各传感器时间戳(以激光雷达和左目相机为例)

laser_ts = load_timestamps("nclt_dataset/2012-01-08/timestamp/laser_timestamp.txt")

image_left_ts = load_timestamps("nclt_dataset/2012-01-08/timestamp/image_left_timestamp.txt")

# 时间戳同步(简化:按索引对齐,实际可使用插值法优化)

min_len = min(len(laser_ts), len(image_left_ts))

synced_ts = list(zip(laser_ts[:min_len], image_left_ts[:min_len]))

步骤 2:激光雷达数据解析与 ROS 消息封装

NCLT 激光雷达数据为 .bin 格式(每行 4 个浮点数:x,y,z,intensity),需转换为 ROS PointCloud2 消息:


import numpy as np

import rosbag

from sensor_msgs.msg import PointCloud2, PointField

import sensor_msgs.point_cloud2 as pc2

def load_laser_data(laser_path):

"""加载激光雷达 .bin 文件"""

data = np.fromfile(laser_path, dtype=np.float32).reshape(-1, 4)

return data[:, :3], data[:, 3] # xyz 坐标 + 强度

def create_pointcloud_msg(xyz, intensity, timestamp):

"""创建 ROS PointCloud2 消息"""

fields = [

PointField(name="x", offset=0, datatype=PointField.FLOAT32, count=1),

PointField(name="y", offset=4, datatype=PointField.FLOAT32, count=1),

PointField(name="z", offset=8, datatype=PointField.FLOAT32, count=1),

PointField(name="intensity", offset=12, datatype=PointField.FLOAT32, count=1),

]

points = np.hstack([xyz, intensity.reshape(-1, 1)])

msg = pc2.create_cloud_from_array(timestamp, fields, points)

msg.header.frame_id = "velodyne" # 坐标系名称(需与 ROS 配置一致)

return msg

# 示例:生成激光雷达 ROS 消息

laser_data_path = "nclt_dataset/2012-01-08/laser_data/0000000000.bin"

xyz, intensity = load_laser_data(laser_data_path)

laser_msg = create_pointcloud_msg(xyz, intensity, ros_timestamps[0])

步骤 3:图像数据解析与 ROS 消息封装

相机图像为 .png 格式,需转换为 ROS Image 消息,通过 cv_bridge 实现格式转换:


import cv2

from sensor_msgs.msg import Image

from cv_bridge import CvBridge

def load_image(image_path):

"""加载图像文件"""

return cv2.imread(image_path)

def create_image_msg(cv_image, timestamp):

"""创建 ROS Image 消息"""

bridge = CvBridge()

msg = bridge.cv2_to_imgmsg(cv_image, encoding="bgr8")

msg.header.stamp.secs = timestamp[0]

msg.header.stamp.nsecs = timestamp[1]

msg.header.frame_id = "camera_left" # 左目相机坐标系

return msg

# 示例:生成左目相机 ROS 消息

image_path = "nclt_dataset/2012-01-08/images/left/0000000000.png"

cv_img = load_image(image_path)

image_msg = create_image_msg(cv_img, image_left_ts[0])

步骤 4:多传感器数据整合与 Bag 生成

将激光雷达、相机、IMU 等消息按时间戳写入 ROS Bag:


def nclt_to_bag(nclt_root, bag_output_path, sequence_name):

"""NCLT 数据集转 ROS Bag 主函数"""

# 1. 加载时间戳与数据路径

laser_ts = load_timestamps(f"{nclt_root}/{sequence_name}/timestamp/laser_timestamp.txt")

image_left_ts = load_timestamps(f"{nclt_root}/{sequence_name}/timestamp/image_left_timestamp.txt")

laser_paths = [f"{nclt_root}/{sequence_name}/laser_data/{i:010d}.bin"

for i in range(len(laser_ts))]

image_left_paths = [f"{nclt_root}/{sequence_name}/images/left/{i:010d}.png"

for i in range(len(image_left_ts))]

# 2. 创建 ROS Bag

bag = rosbag.Bag(bag_output_path, "w")

try:

# 3. 按时间戳写入数据(简化:按索引遍历,实际可按时间戳对齐)

for i in range(min_len):

# 激光雷达消息

xyz, intensity = load_laser_data(laser_paths[i])

laser_msg = create_pointcloud_msg(xyz, intensity, laser_ts[i])

bag.write("/velodyne_points", laser_msg, laser_msg.header.stamp)

# 左目相机消息

cv_img = load_image(image_left_paths[i])

image_msg = create_image_msg(cv_img, image_left_ts[i])

bag.write("/camera_left/image_raw", image_msg, image_msg.header.stamp)

# (可选)IMU、GPS 消息封装(类似逻辑,按对应格式解析)

# imu_msg = create_imu_msg(imu_data[i], imu_ts[i])

# bag.write("/imu/data", imu_msg, imu_msg.header.stamp)

print(f"已写入 {i+1}/{min_len} 组数据")

finally:

bag.close()

print(f"Bag 生成完成:{bag_output_path}")

# 执行转换(示例:转换 2012-01-08 序列)

if __name__ == "__main__":

nclt_to_bag(

nclt_root="nclt_dataset",

bag_output_path="nclt_20120108.bag",

sequence_name="2012-01-08"

)

步骤 5:Bag 验证与回放

转换完成后,通过 ROS 工具验证 Bag 完整性:


# 查看 Bag 信息(话题、消息数量、时长)

rosbag info nclt_20120108.bag

# 回放 Bag(需先启动 ROS 核心)

roscore

# 新终端执行回放

rosbag play nclt_20120108.bag

# 可视化查看(安装 RViz 后)

rviz

# 在 RViz 中添加 PointCloud2(话题 /velodyne_points)和 Image(话题 /camera_left/image_raw)

三、关键优化与格式适配说明

1. 多传感器时间戳精准对齐

  • 上述示例采用索引对齐(简化方案),实际推荐使用「线性插值法」:

# 示例:基于激光雷达时间戳插值相机时间戳

from scipy.interpolate import interp1d

laser_ts_sec = [t[0] + t[1]/1e9 for t in laser_ts]

image_ts_sec = [t[0] + t[1]/1e9 for t in image_left_ts]

interp_func = interp1d(image_ts_sec, image_left_paths, kind="linear", fill_value="extrapolate")

synced_image_paths = interp_func(laser_ts_sec)

  • 确保各传感器消息时间戳误差小于 10ms,避免算法处理时时序错乱。

2. 坐标系与消息格式规范

  • 坐标系:NCLT 数据集坐标系需与 ROS 标准坐标系对齐(右手定则),激光雷达默认 velodyne frame,相机默认 camera_left/camera_right frame,可通过 static_transform_publisher 发布坐标系转换关系;
  • 消息类型
    • 激光雷达:sensor_msgs/PointCloud2(需指定字段顺序:x,y,z,intensity);
    • 相机:sensor_msgs/Image(编码格式 bgr8 或 rgb8,需与图像加载方式一致);
    • IMU:sensor_msgs/Imu(需解析角速度、线加速度,注意单位转换:deg→rad)。

3. 大数据量处理优化

  • NCLT 单序列数据量较大(如激光雷达数据约 10GB),转换时需注意:
    • 分块处理:按时间段拆分数据集,避免内存溢出;
    • 多线程加速:使用 multiprocessing 并行解析不同传感器数据;
    • 格式压缩:生成 Bag 时启用压缩(rosbag.Bag(output_path, "w", compression="lz4")),减少存储占用。

四、常见问题解决方案

问题现象

原因分析

解决方案

Bag 回放无图像显示

cv_bridge 格式不匹配或话题名称错误

确认 cv2_to_imgmsg 编码与图像格式一致,RViz 中话题名称与 bag.write 一致

点云显示错乱

点云字段顺序错误或坐标系不对

确保 PointField 顺序为 x,y,z,intensity,RViz 中 frame_id 与消息一致

时间戳同步误差大

索引对齐方式简单粗暴

改用插值法或 message_filters 进行时间同步

转换过程内存溢出

一次性加载全量数据

分块读取数据,每处理一批释放内存

ROS Bag 无法打开

生成过程中断或格式错误

确保 bag.close() 正常执行,重新运行转换脚本

五、扩展功能建议

  • IMU/GPS 数据封装:参考激光雷达 / 相机逻辑,解析 NCLT 的 imu.txt 和 gps.txt,封装为 sensor_msgs/Imu 和 sensor_msgs/NavSatFix 消息;
  • 批量转换脚本:编写循环遍历 NCLT 所有序列,自动生成对应 Bag 文件;
  • 格式裁剪:根据需求筛选特定传感器数据(如仅保留激光雷达 + IMU),减小 Bag 体积;
  • ROS 2 适配:若使用 ROS 2,需将消息类型替换为 ROS 2 对应格式(如 sensor_msgs/msg/PointCloud2),使用 ros2 bag 工具生成。

总结

NCLT 数据集转 ROS Bag 的核心是 “数据解析→格式映射→时间戳同步→Bag 生成”,关键在于确保传感器消息格式符合 ROS 标准、时序一致。通过本文提供的代码示例,可快速实现激光雷达、相机等核心数据的转换,适配 ROS 生态工具进行算法开发与测试。实际使用时,需根据具体需求优化时间戳同步策略、扩展传感器类型,并注意内存与存储优化。转换后的 ROS Bag 可直接用于 SLAM 算法调试、多传感器融合验证等场景,大幅降低数据集适配成本。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐