
做自动驾驶、视觉SLAM或者3D目标检测的兄弟对KITTI数据集应该都不陌生。我印象太深了当年第一次跑视觉SLAM的评测导师甩给我一句“去把KITTI下载下来跑一遍”我光是在下载和解析上就折腾了两天。不是网站打不开就是下载到一半断了好不容易下完了点云数据、标定文件、标签文件怎么看怎么不对最后对着一个点云文件发半天呆。这篇文章就是把我踩过的坑、梳理过的经验整理出来从KITTI到底包含什么、怎么高效下载到核心文件的解析思路和可复用的Python代码一次性讲清楚。适合刚入门的同学照着操作也适合已经被下载折磨过、想系统搞懂数据格式的老手查漏补缺。1. 先搞清楚KITTI到底有什么很多人一上来就急着下载结果下了一堆不需要的数据占了几十G硬盘最后真正用到的却只有一小部分。KITTI官网整理得其实挺清楚但第一次进去很容易看花眼。这里先把它的家底盘一遍。1.1 四大模块别下错数据KITTI数据集主要由四大模块构成每个模块解决的任务方向完全不同。第一个是Odometry即视觉里程计/ SLAM评测集。这个模块是跑SLAM算法最常用的包含11个训练序列00到10和11个测试序列11到21提供了灰度图、彩色图、Velodyne点云、GPS/IMU的位姿真值。很多SLAM十四讲里的实验用的就是这一套数据。第二个是Object Detection即3D目标检测数据集。这个模块分自行车Cyclist、行人Pedestrian、汽车Car、卡车Truck等类别包含7481张训练图像和7518张测试图像每张图都有对应的Velodyne点云、标定参数和3D框标签。做BEV检测、3D目标检测、鸟瞰图分割的任务基本都在这个模块上做。第三个是Tracking即多目标跟踪数据集。它是在Object Detection基础上增加了时间序列的跟踪标签每个目标都有全局唯一的track id。想研究多目标跟踪、轨迹关联的用这个模块。第四个是Raw Data即原始数据。这个模块没有经过同步和裁剪数据最原始包含大量传感器信息。如果你想自己实现传感器同步、做数据预处理或者需要连续帧的点云用Raw Data最合适。KITTI官方的语义分割、深度补全等扩展任务也是在Raw Data基础上做的。还有一个容易被忽略的KITTI还提供了“Develop Kit”也就是devkit里面是官方用C和Matlab写的读取和评测代码。下载数据之前先把devkit下下来研究一遍你会发现很多格式都能直接找到参考实现。1.2 一套数据的目录规范和命名逻辑KITTI的目录结构非常规整理解它的命名逻辑解析起来就会顺很多。拿Object Detection模块举例下载解压后你会看到这几个文件夹calib每帧图像的相机标定参数包括内参、外参、矫正矩阵等。image_2左侧彩色相机拍摄的图像也就是我们做2D/3D检测时常用的RGB图。label_2左侧相机视角下的3D框标签文件每一行代表一个目标。velodyneVelodyne HDL-64E激光雷达采集的原始点云格式是二进制bin文件。timestamps每帧数据的时间戳这个在部分模块中有。每个文件的命名都是以6位数字编号比如000000.txt、000001.bin。这套编号在image_2、label_2、velodyne、calib之间是一一对应的也就是说000000.bin的点云、000000.png的图像、000000.txt的标签描述的都是同一个时刻的场景。Odometry模块的目录逻辑略有不同它以sequences/00到sequences/10的方式组织每个序列文件夹下有image_0左灰度、image_1右灰度、image_2左彩色、image_3右彩色、velodyne、times.txt和calib.txt。位姿真值则在poses/00.txt到poses/10.txt中每一行是一个3x4的变换矩阵摊平后的12个数字表示该帧相对于序列起始帧的位姿。这套统一的命名逻辑是KITTI能成为通用benchmark的关键。你的程序只需要根据文件名索引就能把多个传感器的数据对齐起来这在真实数据集里是很奢侈的事。2. 下载前的准备与完整下载实操这部分直接关乎你的硬盘和耐心。KITTI官网的下载页面其实是个“手动勾选下载清单”的逻辑很多人卡在这里是因为不熟悉它的操作。2.1 官网怎么定位、下载清单怎么列访问KITTI官网后导航栏里按任务分类Odometry、Object Detection、Tracking、Raw Data等。以Object Detection为例进入对应页面后往下拉到下载区域你会看到一张表格里面有data_object_calib.zip、data_object_label_2.zip、data_object_image_2.zip、data_object_velodyne.zip这些文件。每个文件前面都有一个复选框勾选后页面底部会生成一个下载链接。这里要注意很多教程让你直接点某个文件名的链接官网实际是让你勾选后通过生成的脚本或链接下载。操作方法是勾选你需要的模块文件然后点击页面底部的下载按钮会弹出一个下载请求。你可以复制生成的下载链接也可以直接右键保存。对于Odometry模块下载清单更细data_odometry_calib.zip、data_odometry_poses.zip、data_odometry_gray.zip、data_odometry_color.zip。其中灰度图和彩色图体积都很大按需下载就好如果只跑SLAM灰度图已经够用。2.2 推荐下载姿势与断点续传KITTI的数据文件动辄几个G、几十个G。浏览器直接下载最大的问题不是速度慢而是中断后要从头再来。这个问题我吃过几次亏后来摸索出一个比较省心的组合优先使用支持断点续传的下载工具比如IDMInternet Download Manager或者aria2。复制官网生成的直链后把链接丢给下载工具。aria2在我的使用中更稳定多线程拉满中断后重新执行同样的命令会自动续传。用aria2下载的命令简单记一下aria2c -x 8 -s 8 -c https://s3.eu-central-1.amazonaws.com/avg-kitti/raw_data/2011_09_26_drive_0001/2011_09_26_drive_0001_sync.zip-x 8表示每个服务器最多建立8个连接-s 8表示拆分8个分段下载-c表示支持断点续传。实测在普通网络环境下这个命令的下载速度比浏览器默认下载快很多关键是断点续传能让人安心。如果官网下载实在不稳定KITTI在学术界有非常多的镜像源比如一些高校实验室的FTP或学术网盘。也有很多热心人把文件传到网盘供下载虽然没有官网版本更新及时但胜在稳定。我个人建议还是想办法搞定官网直链因为镜像源可能缺文件或版本不一致后期做实验对比会有麻烦。各模块数据体积大概如下表心里有个底模块主要文件体积参考Object Detectioncalib label_2 image_2 velodyne约20GBOdometrygray color calib poses约22GB含彩色Tracking训练集全部约30GBRaw Data各日期drive原始包按需下载单个压缩包1-5GB不等2.3 文件完整性与解压校验下载完成后不要急着解压先做完整性校验。我遇到过下载完的zip文件打不开或者解压到一半报错的情况基本都是因为下载过程中文件损坏。简单的做法是用命令行工具检验zip完整性Linux下直接unzip -t data_object_velodyne.zip如果输出的最后一行是No errors detected in compressed data of data_object_velodyne.zip说明文件完整。Windows下可以用Bandizip或7-Zip的“测试压缩档”功能。Hash校验更严格官网如果给出了MD5或SHA256建议用校验工具核对一下。解压时注意KITTI的zip包解压后通常会生成新的子目录比如training和testing或者sequences。我建议把数据放在一个固定目录比如/data/kitti/object后续写代码的时候路径统一环境变量里设置好就一劳永逸。3. 核心文件解析calib、label、velodyne下载完成只是第一步解析才是真正的分水岭。KITTI最核心的三类文件分别是标定文件、标签文件和点云文件。很多跑3D检测的初学者拿到数据后对着calib文件里的矩阵一头雾水这是正常的因为这套坐标系转换本身就有好几个层次。下面逐个拆开讲。3.1 calib标定文件的四个矩阵以Object Detection模块的calib文件夹为例打开一个000000.txt内容大致是这样P0: 7.070912e02 0.000000e00 6.018873e02 0.000000e00 0.000000e00 7.070912e02 1.831104e02 0.000000e00 0.000000e00 0.000000e00 1.000000e00 0.000000e00 P1: 7.070912e02 0.000000e00 6.018873e02 -3.798145e02 ... P2: 7.070912e02 0.000000e00 6.018873e02 4.575731e01 ... P3: ... R0_rect: 9.999239e-01 9.837760e-03 -7.445048e-03 -9.869795e-03 9.999421e-01 -4.278459e-03 7.402527e-03 4.351614e-03 9.999631e-01 Tr_velo_to_cam: 7.533745e-03 -9.999714e-01 -6.166020e-04 -4.069766e-03 1.480249e-02 7.280733e-04 -9.998902e-01 -7.631618e-02 9.998621e-01 1.356763e-02 5.201729e-04 -2.717806e-01 Tr_imu_to_velo: 9.999976e-01 7.553071e-04 -2.035826e-03 -8.086759e-01 -7.854027e-04 9.998898e-01 -1.482298e-02 3.195559e-01 2.024156e-03 1.482454e-02 9.998881e-01 -7.997231e-01这些矩阵的含义按用途分P0、P1、P2、P3分别是4个相机的投影矩阵每个都是3x4。P2对应的是左彩色相机也就是我们做检测的image_2。投影矩阵的功能是把相机坐标系下的3D点投影到像素坐标系。R0_rect3x3的矫正旋转矩阵用于将点云从原始相机坐标系转换到矫正后的坐标系。Tr_velo_to_cam3x4的变换矩阵把Velodyne激光雷达坐标系下的3D点转换到相机坐标系下。这个矩阵由旋转部分R和平移部分t组成。Tr_imu_to_veloIMU坐标系到Velodyne坐标系的变换涉及融合定位时才会用到。一个很常见的需求是把点云投影到图像上。投影链路是这样的点云原始坐标(x, y, z)先左乘Tr_velo_to_cam从激光雷达坐标系变换到相机坐标系。再左乘R0_rect做畸变矫正。最后左乘P2投影到像素平面。注意顺序不能乱一旦乱了点云和图像就完全对不上。这个过程的代码实现我后面给这里先记住公式链。3.2 label标签的15个字段标签文件label_2里的每一行代表一个3D检测框总共15个字段。这里以最常见的Car类别为例Car 0.00 0 -1.57 600.97 173.34 663.55 210.64 1.51 1.62 3.90 -2.40 1.96 11.14 -1.55逐字段解释字段示例值含义typeCar物体类别truncated0.00截断程度0到1表示物体被图像边界截断的比例occluded0遮挡状态0表示完全可见1表示部分遮挡2表示大面积遮挡3表示未知alpha-1.57观测角度即物体朝向与相机光轴的夹角bbox_left600.972D框左边界x坐标bbox_top173.342D框上边界y坐标bbox_right663.552D框右边界x坐标bbox_bottom210.642D框下边界y坐标height1.513D框的高度单位是米width1.623D框的宽度单位是米length3.903D框的长度单位是米cam_x-2.40物体中心在相机坐标系下的x坐标单位米cam_y1.96物体中心在相机坐标系下的y坐标单位米cam_z11.14物体中心在相机坐标系下的z坐标单位米rotation_y-1.55物体绕Y轴的旋转角单位弧度这里有两个容易混淆的角度alpha和rotation_y。alpha是物体朝向相对于相机光轴的角度它考虑了物体在图像中的位置rotation_y是物体在相机坐标系中绕Y轴的真实朝向角。如果只想画3D框直接用rotation_y更简单。height、width、length的顺序是固定的尺寸对应关系是height对应物体垂直方向width对应物体左右方向length对应物体前后方向。这个顺序在3D框可视化时很关键写反了框就是歪的。类别的坑也要注意DontCare类别的标签表示“这些区域有物体但没有被标注”在评测时通常被忽略训练时也建议直接滤掉。不同类别在KITTI中数量极不平衡Car最多Pedestrian次之Cyclist最少这也是很多精度不平衡问题的根源。3.3 velodyne点云与图像时间戳对齐velodyne文件夹下是.bin文件每帧点云都来自Velodyne HDL-64E激光雷达。这个雷达一圈扫描约10Hz图像则是10帧每秒左右理论上帧率是同步的但如果做Raw Data级别的数据处理就要小心时间戳对齐问题。官方在Raw Data中提供timestamps.txt文件记录每一帧图像和点云的时间戳。在Object Detection和Odometry模块中数据已经做好了同步文件名编号一致直接按文件名对应即可。点云文件的内部结构是每一帧包含N个点每个点由4个float组成分别是x, y, z, reflectance反射强度值二进制格式按float32小端存储。文件总字节数除以4再除以4就是点数。比如一个000000.bin文件大小是8388608字节那一帧就是8388608 / 4 / 4 524288个点这正是HDL-64E一圈的典型点数。4. Python实操从文件到可视化这里给出我调试过的一套Python解析流程从读取点云到把3D框画到图像上完整可跑。4.1 读取点云与图像读取点云很简单用NumPy的fromfileimport numpy as np import cv2 def load_velodyne_bin(bin_path): point_cloud np.fromfile(bin_path, dtypenp.float32).reshape(-1, 4) return point_cloud # N x 4前三列xyz第四列reflectance def load_image(img_path): img cv2.imread(img_path) return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)注意reshape的顺序读出来的是N x 4不是4 x N。很多人在这一步就搞反了后面所有坐标计算都会出问题。4.2 解析标定和标签解析calib文件时把矩阵存成字典方便后面按名字取def load_calib(calib_path): calib {} with open(calib_path, r) as f: for line in f.readlines(): key, value line.split(:) calib[key] np.array([float(v) for v in value.split()]).reshape(3, -1) return calib解析标签文件相对直接每一行按空格切分就可以def load_label(label_path): objects [] with open(label_path, r) as f: for line in f.readlines(): parts line.strip().split() obj { type: parts[0], truncated: float(parts[1]), occluded: int(parts[2]), alpha: float(parts[3]), bbox: [float(parts[4]), float(parts[5]), float(parts[6]), float(parts[7])], dimensions: [float(parts[8]), float(parts[9]), float(parts[10])], # h, w, l location: [float(parts[11]), float(parts[12]), float(parts[13])], rotation_y: float(parts[14]) } if obj[type] ! DontCare: objects.append(obj) return objects4.3 3D框投影到图像这是整个解析过程中含金量最高的位置。3D框投影的本质是把8个角点在相机坐标系里算出来然后投影到像素平面。角点的计算依赖dimensions和location以及rotation_y。如果物体中心在相机坐标系下的坐标是(x, y, z)尺寸是(h, w, l)绕Y轴旋转角是ry那8个角点在物体局部坐标系中的坐标可以先写出来然后通过旋转和平移变换到相机坐标系def compute_3d_box_corners(dimensions, location, rotation_y): h, w, l dimensions x, y, z location # 物体局部坐标系下的8个角点顺序无所谓但要成对 corners np.array([ [l / 2, 0, w / 2], [l / 2, 0, -w / 2], [-l / 2, 0, -w / 2], [-l / 2, 0, w / 2], [l / 2, -h, w / 2], [l / 2, -h, -w / 2], [-l / 2, -h, -w / 2], [-l / 2, -h, w / 2], ]).T # 3 x 8 # 旋转矩阵 R np.array([ [np.cos(rotation_y), 0, np.sin(rotation_y)], [0, 1, 0], [-np.sin(rotation_y), 0, np.cos(rotation_y)] ]) corners_cam R corners np.array([[x], [y], [z]]) return corners_cam投影到图像用前面说的P2矩阵def project_3d_to_image(corners_cam, calib): P2 calib[P2] # 齐次坐标 corners_h np.vstack([corners_cam, np.ones((1, 8))]) corners_img P2 corners_h # 3 x 8 corners_img corners_img[:2, :] / corners_img[2, :] return corners_img这里要注意P2矩阵本身包含了镜头内参投影出来直接就是像素坐标不需要额外乘内参矩阵。如果是从velodyne点云投影还要先经过Tr_velo_to_cam和R0_rect不能直接用点云的原始坐标乘P2。4.4 BEV视图可视化3D检测里经常要可视化鸟瞰图BEVBirds Eye View。做法是把点云的XOY平面当作俯视图平面只保留X和Z坐标KITTI的相机坐标系下Z是前方向X是右方向然后按分辨率缩放成图像。def point_cloud_to_bev(point_cloud, x_range(-40, 40), z_range(0, 70), resolution0.1): x, z point_cloud[:, 0], point_cloud[:, 2] mask (x x_range[0]) (x x_range[1]) (z z_range[0]) (z z_range[1]) x, z x[mask], z[mask] scale int(1 / resolution) x_img ((x - x_range[0]) * scale).astype(np.int32) z_img ((z - z_range[0]) * scale).astype(np.int32) img np.zeros((int((z_range[1] - z_range[0]) * scale), int((x_range[1] - x_range[0]) * scale)), dtypenp.uint8) img[z_img, x_img] 255 return imgBEV里的坐标轴方向、尺度范围可以根据自己的算法调节。这个可视化对于调试点云预处理非常有用能一眼看出点云是否被正确裁剪、目标是否在范围内。4.5 用Open3D做3D可视化如果觉得2D投影不够直观可以直接用Open3D把点云和3D框画在三维空间里import open3d as o3d def draw_point_cloud_with_boxes(point_cloud, corners_list): pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(point_cloud[:, :3]) geoms [pcd] for corners in corners_list: lines [ [0, 1], [1, 2], [2, 3], [3, 0], [4, 5], [5, 6], [6, 7], [7, 4], [0, 4], [1, 5], [2, 6], [3, 7] ] line_set o3d.geometry.LineSet() line_set.points o3d.utility.Vector3dVector(corners.T) line_set.lines o3d.utility.Vector2iVector(lines) geoms.append(line_set) o3d.visualization.draw_geometries(geoms)这个可视化调试方式非常直观用来检查标签和点云是否对齐最有说服力。5. 常见问题与排查实录这部分是我实际折腾KITTI时踩过的坑每个都值得记一笔。5.1 下载慢、下载中断、文件损坏怎么办官网下载慢是常态不是你的网络问题。我推荐的做法是优先使用支持断点续传的下载工具不要用浏览器裸奔下载大文件。如果公司或学校的网络有限制可以试试切换网络再下或者使用学术机构提供的镜像。下载完成后先用unzip -t测一遍压缩包完整性发现损坏就赶紧补下对应文件不要等解压到一半才反应过来。还有个容易忽略的点KITTI的Object Detection数据分成多个zip文件并不是一个整体包。有人只下了image_2和label_2没有下velodyne后面做点云融合时才发现缺数据。建议动手前先列一个清单把需要的模块全勾上。5.2 点云和图像对不齐投影位置偏移点云投影到图像结果不对80%的原因是坐标变换顺序错了。记住一个原则从激光雷达到像素先外参、再矫正、再投影顺序不能乱。每一步矩阵维度也要检查Tr_velo_to_cam是3x4使用时要补齐一行[0, 0, 0, 1]变成4x4否则会报错或者结果完全不对。另外投影时要注意点云在相机坐标系下的Z值Z小于等于0的点在相机后方投影结果没有意义要提前过滤掉。我见过有人把车后面的点硬投影到图像上结果画出一堆乱线后来发现就是没做Z值过滤。5.3 标签里DontCare和类别不平衡怎么处理训练自己模型时DontCare类别不是用来训练的它只表示“这个区域有东西但不知道是什么”主要用于评测时排除误检。如果在训练时把它当成一个类会引入大量噪声。建议解析时直接忽略。KITTI的类别不平衡很严重Car、Pedestrian、Cyclist三者数量差距巨大训练时如果不做类别重采样或损失函数加权模型会严重偏向Car。实操中我会对Cyclist做上采样或者对Car做下采样再配合focal loss一类的方法效果会好很多。5.4 转YOLO/MMRotate格式时容易踩的坑很多人会把KITTI标签转成YOLO格式训练或者喂给MMRotate等框架。KITTI的标签是相机坐标系YOLO的3D检测格式通常用相机坐标系或激光雷达坐标系转换时最容易出错的有三个地方尺寸顺序KITTI的dimensions顺序是h, w, lYOLO格式经常是l, w, h不转换直接塞进去框会完全错乱。角度定义KITTI的rotation_y是绕相机Y轴旋转有些框架用的是绕Z轴旋转或者定义角度范围[-π, π]需要做相应变换。bbox与3D框不一致KITTI标签里2D bbox和3D框来自不同标注过程两者并不严格对应。做BEV检测时直接用3D信息就行不要试图从2D框反推3D框。还有个细节KITTI图像分辨率是1242x375左右YOLO训练时会自动做letterbox缩放但3D框的相机内参需要跟着图像一起缩放不校正内参的话检测结果会整体偏移。这一点很多教程不会讲转换时务必检查。最后再分享一个小技巧调试数据解析时不要一上来就跑全量数据集。挑几个有代表性的序列比如带行人的、带自行车的、遮挡程度不同的先跑通一套可视化和投影确认数据没问题后再铺开全量处理。KITTI这个数据集虽然老但它的格式设计、基准评测方式至今仍是很多自动驾驶数据集的模板把它的解析流程吃透后面换到nuScenes、Waymo Open Dataset你会发现很多思路是通用的。