简介:面向SLAM、视觉里程计与自动驾驶方向的学习者与研究者,这份文档系统梳理了TUM RGB-D、KITTI与EuRoC三大主流数据集的核心信息,帮助读者快速建立选型与使用认知。资源包共1个docx文件,约1.93MB,以文字说明与结构梳理为主,便于离线查阅和整理笔记。内容覆盖TUM数据集的Kinect采集方式、彩色与深度图格式、深度值5000对应1米的缩放规则、groundtruth四元数位姿格式,以及ATE与RPE两项评价准则的适用差异和点云生成脚本;KITTI部分介绍采集车平台、GPS/IMU与激光雷达等传感器配置、raw data按场景分类的组织方式与标定文件;EuRoC则简述微型无人机多传感器数据构成。已有9633人学习,适合在算法复现、数据预处理与评测方案设计时作为速查参考。
1. 三个数据集摆在一起,先想清楚你要评的是哪一类系统
手里同时躺着 TUM、KITTI、EuRoC 的压缩包,是视觉 SLAM 和里程计方向很常见的一个起点。但很多人第一步就走偏:拿 KITTI 的序列去跑一个纯 RGB-D 的建图算法,或者用 TUM 的 ATE 脚本去评一个双目惯性系统,跑出来的曲线看着挺像回事,结论却是错的。这三个数据集不是同一类东西,它们各自绑定了不同的传感器组合、不同的真值来源、不同的评价口径。TUM RGB-D 是 Kinect 采集的室内彩色加深度序列,真值来自高精度运动捕捉系统,配套 ATE、RPE 评估脚本和点云生成脚本,适合验证 RGB-D SLAM 的轨迹精度和漂移;KITTI 是车载平台,64 线激光雷达加双目灰度、双目彩色、GPS/IMU,面向自动驾驶的检测、跟踪、里程计和深度估计;EuRoC 是四旋翼无人机上的双目加 IMU,真值同样来自运动捕捉系统,是视觉惯性里程计的标准测试床。选错数据集,后面所有参数调优都是白费。这篇笔记按「每个数据集怎么读、怎么转、怎么评、坑在哪」拆开讲,读完你应该能自己判断某个算法该上哪个数据集、指标该怎么对齐。
2. TUM RGB-D:从深度图比例因子到 ATE/RPE 的完整评估链
TUM 这套数据是入门 RGB-D SLAM 最顺手的一批,但它的坑集中在两个地方:深度图的数值语义,以及轨迹评估前的时间戳关联。这两步任何一步做错,ATE 曲线都会给你一个看似合理但完全错误的数字。
2.1 文件结构与深度图的 5000 比例因子
下载解压后,一个典型序列目录里是rgb/、depth/、rgb.txt、depth.txt、groundtruth.txt这几样。彩色图是 640x480 的 8 位 PNG,深度图是 640x480 的 16 位单色 PNG。关键在深度图的数值:像素值 5000 对应真实距离 1 米,也就是说深度单位是 0.2 毫米,换算成米要除以 5000。像素值 0 表示该点无有效深度,不是「距离为零」,这个区别在生成点云和做深度滤波时是致命的。
读一帧深度并转成米制,常见写法是这样:
import cv2 import numpy as np # 读取 16 位单色深度图,必须用 IMREAD_UNCHANGED,否则会被截断成 8 位 depth_raw = cv2.imread('depth/1305031102.175304.png', cv2.IMREAD_UNCHANGED) assert depth_raw.dtype == np.uint16, "深度图必须是 16 位,读错模式会丢精度" # 5000 像素值 = 1 米;0 是无效值,先屏蔽再换算 valid = depth_raw > 0 depth_m = np.zeros_like(depth_raw, dtype=np.float32) depth_m[valid] = depth_raw[valid].astype(np.float32) / 5000.0 print(depth_m[valid].min(), depth_m[valid].max()) # 有效深度范围,单位米IMREAD_UNCHANGED这个参数不能省,默认的IMREAD_COLOR会把 16 位深度图当彩色图读进来再降位,数值全乱。除以 5000 之后得到的是米,后面做反投影生成点云、算深度误差都用这个米制值。valid掩码要一直带着走,无效像素参与任何几何计算都会污染结果。
2.2 轨迹文件格式与时间戳关联
groundtruth.txt每行是timestamp tx ty tz qx qy qz qw,时间戳是 Unix 纪元秒,平移是彩色相机光心相对运动捕捉系统世界原点的位置,旋转是单位四元数。注意四元数顺序是 qx qy qz qw,不是常见的 qw 在前,直接喂给某些库的Quaternion构造函数会静默出错。
评估前必须把估计轨迹和真值按时间戳关联起来。TUM 官方脚本associate.py做的就是这件事,它按时间差阈值配对,常用命令:
python associate.py rgb.txt groundtruth.txt --max_difference 0.02 > associations.txt--max_difference 0.02表示只接受时间差在 20 毫秒以内的配对。这个阈值不是随便定的:Kinect 采集帧率约 30Hz,帧间隔 33 毫秒,阈值设太大可能把相邻帧错配,设太小又配不上足够多的点导致轨迹断裂。室内手持序列一般 0.02 够用,快速运动序列可以放宽到 0.05,但要回头检查配对数量是否合理。
2.3 ATE 与 RPE:两种误差量的是两件事
ATE 直接比较真值轨迹和估计轨迹对应位姿之间的差异。预处理是先按时间戳关联,再用奇异值分解做轨迹对齐(因为估计轨迹的坐标系原点和真值不一定重合),最后统计每对位姿差值的均值、中位数、标准差。它衡量的是整体轨迹的绝对精度,适合评一个完整 SLAM 系统的建图质量。
RPE 量的是相对运动误差,也就是固定时间窗口内两帧之间相对位姿的偏差。官方脚本evaluate_rpe.py默认计算所有时间戳对的相对误差,但轨迹越长对数越多,是二次增长,所以要用-max_pairs下采样,或者用-fixed_delta加-delta和-delta_unit指定固定窗口。RPE 更适合评视觉里程计的漂移率,比如「每秒漂移多少」。
# ATE:输出均值/中位数/标准差,并可画图 python evaluate_ate.py groundtruth.txt estimated.txt --plot ate.png --verbose # RPE:固定 1 秒窗口,单位秒 python evaluate_rpe.py groundtruth.txt estimated.txt --fixed_delta --delta 1.0 --delta_unit s --plot rpe.png--verbose会打印对齐后的详细误差,调参阶段很有用。--plot生成的图能直观看出误差是集中在某一段还是全程均匀,前者往往意味着那一段有跟踪丢失或快速旋转。
2.4 从彩色图加深度图生成 PLY 点云
深度图已经配准到彩色图,像素一一对应,所以反投影很直接。官方给了generate_pointcloud.py,核心逻辑是按相机内参把每个有效像素反投影到相机坐标系,再附上 RGB 颜色写成 PLY。内参在序列的说明里给,freiburg1、freiburg2、freiburg3 三组内参不同,用错内参点云会整体缩放或畸变。生成后用 MeshLab 打开检查:墙面应该是平的,如果出现明显弯曲,多半是内参或深度比例因子用错了。这一步也是验证你前面读深度图是否正确的最快手段。
3. KITTI:raw data 的组织方式与标定文件怎么用
KITTI 的数据量比 TUM 大一个量级,组织方式也更工程化。它的坑不在数值语义,而在「你下载的到底是哪一份数据」以及「标定文件里的矩阵对应哪个坐标系」。
3.1 raw data 的目录结构与同步矫正数据
raw data 按场景分为 Road、City、Residential、Campus、Person 几类,每个序列(如2011_09_26_drive_0005)解压后是image_00到image_03、velodyne_points、calib等目录。image_00/01是双目灰度,image_02/03是双目彩色,都以image_00为同步参考系。每个相机目录下又分data(同步矫正后的图)和data_raw(原始带畸变未同步的图)。绝大多数算法用的是data下的同步矫正数据,data_raw只在你要自己重做矫正流程时才碰。
激光雷达数据在velodyne_points/data下,每帧是.bin格式的 float 矩阵,每点 4 个 float:x、y、z、反射强度。读法:
import numpy as np # 每点 4 个 float32:x y z intensity scan = np.fromfile('velodyne_points/data/0000000000.bin', dtype=np.float32) scan = scan.reshape(-1, 4) points_xyz = scan[:, :3] # 单位米,雷达坐标系 intensity = scan[:, 3] print(points_xyz.shape) # 单帧通常 10 万点以上reshape 成 N×4 这一步不能省,直接按一维数组切片会把 xyz 和强度混在一起。雷达坐标系原点在传感器本身,和相机坐标系之间差一个外参,投影到图像上必须用标定文件里的变换矩阵。
3.2 标定文件:相机内参、相机间外参、雷达到相机
calib目录下几个 txt 各有分工。calib_cam_to_cam.txt里有每个相机的内参矩阵P_rect_0x(3x4,已含矫正后的投影)和相机之间的R_rect、T;calib_velo_to_cam.txt给的是雷达到相机的旋转R和平移T;calib_imu_to_velo.txt给 IMU 到雷达的变换。把雷达点投到彩色图像上的完整链路是:雷达点 → 用 velo_to_cam 变到相机坐标系 → 用 R_rect 做矫正 → 用 P_rect 投影到像素。少任何一步,投影都会偏。
def load_calib(path): data = {} with open(path) as f: for line in f: if ':' in line: key, val = line.split(':', 1) data[key.strip()] = np.array([float(x) for x in val.split()]) return data calib = load_calib('calib/calib_velo_to_cam.txt') R = calib['R'].reshape(3, 3) T = calib['T'].reshape(3, 1) # 雷达点 p_velo -> 相机系 p_cam = R @ p_velo + T注意R在文件里是 9 个数按行展开,reshape(3,3) 的顺序要对,转置错了投影会整体镜像,这种错误在可视化时一眼能看出来但排查起来很费时间。
3.3 object 与 tracking:单帧检测和连续跟踪别混用
object 数据集是给目标检测用的,每个类别下载后分training和testing,训练集带标注,标注包含 2D 框、3D 框和鸟瞰图框三套。它的数据是单帧的,不连续,所以只能评检测不能评跟踪。tracking 数据集结构类似但序列连续,评的是跟踪算法。拿 object 的帧去跑跟踪、或者拿 tracking 的连续序列去算单帧检测的 mAP,都是口径错配。另外 object 的标注里有个truncated和occluded标志,评估时是否过滤这两类样本会显著影响指标,论文里通常要写清楚。
4. EuRoC:双目加 IMU 的坐标系约定与真值对齐
EuRoC 是视觉惯性里程计绕不开的一套数据,它的坑几乎全在坐标系和传感器外参上。这套数据把每个传感器的坐标系、Body 坐标系、世界坐标系分得很清楚,但正因为清楚,用错一个变换就会得到「看起来在跑但精度很差」的结果。
4.1 四个传感器目录与 sensor.yaml
数据集结构里cam0、cam1、imu0、leica0四个文件夹,每个里面配一个sensor.yaml,描述该传感器相对 Body 坐标系的变换和自身内参。Body 坐标系以 IMU 中心为原点,所以 IMU 的 Sensor Frame 就是 Body Frame。相机和 Leica 的sensor.yaml里有T_BS,表示从 Sensor 坐标系到 Body 坐标系的变换。做双目惯性融合时,两个相机的外参、相机到 IMU 的外参都从这里读,不要自己猜。
4.2 旋转与变换的方向别搞反
原文里那句注释值得反复看:R_WC是把 C 坐标系下表示的点的坐标变换成 W 坐标系下的表示。T_BS同理,是把 Sensor 坐标系下的向量变到 Body 坐标系。方向搞反是 EuRoC 上最常见的错误,表现为初始化后轨迹整体翻转或尺度异常。一个自检习惯:拿一个已知在相机前方的点,用你的变换链算到世界系,看结果是否落在合理位置,比盯着代码看快得多。
4.3 真值格式与时间戳对齐
EuRoC 的真值文件是 18 位时间戳加位置和四元数,时间戳单位是纳秒,和图像、IMU 的时间戳单位要统一后再做关联。IMU 频率 200Hz,相机约 20Hz,做预积分时时间戳对齐误差会直接进残差。常见做法是先把所有时间戳转成同一单位(秒),再用插值把真值对齐到估计轨迹的时间戳上,而不是反过来。评估脚本方面,EuRoC 社区常用的是把轨迹转成 TUM 格式再复用 ATE/RPE 脚本,转换时注意四元数顺序和单位。
5. 避坑:三个数据集上最容易翻车的五件事
5.1 深度图读成 8 位,ATE 看着正常但点云全错
现象:点云生成后墙面弯曲、尺度不对,但轨迹评估数字还行。原因:用默认模式读 16 位深度图,数值被截断,比例因子失效。解决:一律IMREAD_UNCHANGED,读入后断言 dtype 是 uint16,再除以 5000。
5.2 时间戳关联阈值拍脑袋,轨迹被错配
现象:ATE 忽大忽小,换一次运行结果就变。原因:--max_difference设得过大,相邻帧被错配,或者设得过小导致配对点太少。解决:按采集帧率反推阈值,室内 30Hz 用 0.02,并打印配对数量核对,配对率低于八成就要查。
5.3 KITTI 标定矩阵转置用错,投影整体镜像
现象:雷达点投到图像上左右颠倒或上下颠倒。原因:R从 txt 读出后 reshape 顺序或转置搞错。解决:固定一个已知点做单点验证,投影结果和图像里的明显目标(如车辆)对得上再往下走。
5.4 拿 object 数据评跟踪,指标没有意义
现象:跟踪指标异常高或异常低。原因:object 是单帧不连续数据,帧间没有真实运动关系。解决:评跟踪必须用 tracking 序列,评检测才用 object,两者不要混。
5.5 EuRoC 变换方向反了,初始化就飘
现象:VIO 跑起来轨迹整体翻转或尺度离谱。原因:T_BS或R_WC的方向理解反了。解决:按注释确认「从哪个系变到哪个系」,用单点自检,别靠肉眼读矩阵。
6. 把三个数据集接进同一套评估流程:一个转换脚本和两条自检
三个数据集格式各异,但最终都要落到「时间戳 + 位置 + 四元数」这一种轨迹格式上,才能用同一套 ATE/RPE 脚本比较。我一般写一个统一转换脚本,把 TUM 的 txt、KITTI 的 pose、EuRoC 的 csv 都转成timestamp tx ty tz qx qy qz qw,四元数统一成 qx qy qz qw 顺序,时间戳统一成秒。转换完先做两条自检:一是轨迹点数和原始帧数对得上,二是把转换后的轨迹画出来和原始可视化对比,形状一致再进评估。
import numpy as np def to_tum_format(ts, xyz, quat_xyzw, out_path): # ts: 秒;xyz: N×3;quat_xyzw: N×4,顺序 qx qy qz qw assert len(ts) == len(xyz) == len(quat_xyzw) data = np.hstack([np.asarray(ts).reshape(-1, 1), xyz, quat_xyzw]) np.savetxt(out_path, data, fmt='%.6f') return data.shape[0] # EuRoC 纳秒转秒,四元数若为 qw 在前需重排为 qx qy qz qw # KITTI 的 pose 是 3x4 矩阵,需转成平移加四元数这个脚本的关键不在代码本身,而在两个约定:时间戳单位和四元数顺序。所有数据集在入口处就统一掉,后面评估脚本就不用改。转换后跑一遍 ATE,如果数字和论文里同序列的量级差一个数量级以上,先怀疑转换而不是算法。
从那以后我每次接新数据集,都强制先跑一遍「单帧点云可视化 + 轨迹形状对比」这两步再谈调参,因为血泪经验告诉我,格式和坐标系的问题永远比算法问题更隐蔽,也更容易被一个看似合理的指标数字掩盖过去。希望帮到你。
本文还有配套的精品资源,点击获取