ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

KITTI基准评测:目标检测、深度估计与视觉里程计算法实战对比

KITTI基准评测:目标检测、深度估计与视觉里程计算法实战对比 最近团队里在争论自动驾驶感知方案选型检测算法该用YOLO还是Faster R-CNN深度估计用自监督还是监督式里程计要不要上VINS……与其靠经验拍板我直接把KITTI数据集拉出来搭了一套公平的测试流程把这三个方向的代表性算法挨个跑了一遍。这篇文章就是这次测试的完整复盘包括怎么下载数据、怎么统一指标、跑出哪些数据、以及我在实操中踩过的坑。如果你也在做算法选型、写论文需要benchmark或者刚接触KITTI想找一份完整的上手流程这篇内容可以直接当参考。1. 为什么拿KITTI做算法对比1.1 KITTI能解决什么问题KITTI是德国卡尔斯鲁厄理工学院和丰田芝加哥研究院联合发布的数据集采集车装了两台灰度相机、两台彩色相机、一个Velodyne 64线激光雷达和GPS/IMU。它覆盖了市区、乡村、高速公路、多人多车等典型驾驶场景自带目标检测、目标跟踪、深度估计、光流、视觉里程计等任务的标注。所以我这次把它当“统一考场”。自动驾驶感知里大部分算法都会在KITTI上验证论文里的指标互相可查我拿KITTI测试不同算法本质上是在同一套考题下对比不同学生的水平比拿私有数据集自说自话可靠得多。还有一个实际原因KITTI的数据规模适中原始数据约180GB但单任务数据集并不大。比如目标检测训练集仅7481帧深度估计和里程计所需的raw data按序列下载后也就几十GB单张消费级显卡就能跑完大部分算法。对个人开发者和中小团队来说没必要一上来就上nuScenes那种几百GB的“全家桶”先把KITTI的baseline跑通后面再迁移到更大规模的数据集是效率最高的路径。相比很多合成数据集KITTI的优势是“真”。激光雷达点云、图像、GPS轨迹都来自真实道路场景对算法的验证更有说服力。虽然它采集时间比较早传感器分辨率放在今天不算高但正因为大家都在用同一批数据横向对比才变得有意义。1.2 三组算法的选型思路这次测试我分成三组2D目标检测、深度估计、视觉里程计。目标检测选了YOLOv8、Faster R-CNN、CenterNet对应如今最主流的三种设计思路——单阶段、两阶段、无锚框。深度估计选了Monodepth2和Depth Hints两个都是自监督深度估计里口碑很好的开源项目前者是基线后者在损失函数上做了改进正好能对比出“改进到底值不值”。视觉里程计选了ORB-SLAM3和VINS-Mono一个基于特征点一个基于IMU紧耦合也是SLAM社区最常见的绕不开的两个系统。选型的判断标准不是“谁最先进就选谁”而是要考虑代码成熟度、能否跑通、是否适配KITTI。KITTI本身提供了odometry基准很多SLAM算法都做过适配ORB-SLAM3甚至自带KITTI示例配置。如果选一个只有论文没有开源的算法测试流程根本走不起来。所以“工作量大不大”也是算法对比里必须提前评估的隐形指标。我一开始也想过把车道线检测、多目标跟踪、光流估计全加进来后来放弃了。测试范围铺得太开数据准备和评测脚本会膨胀到难以维护。先把三条主线跑清楚后续要扩展再单独开新模块这样实验记录也更干净。2. 数据准备与环境搭建2.1 下载KITTI的正确姿势KITTI官网数据下载是网页表单形式没有一键全量包网络不稳定时很容易下到一半断掉。我的做法是用wget加断点续传参数比如wget -c https://s3.eu-central-1.amazonaws.com/avg-kitti/raw_data/2011_09_26_drive_0005/2011_09_26_drive_0005_sync.zip如果只是做目标检测下载object数据集里的training和testing压缩包就行不用去碰raw data。raw data体积大是按日期加drive编号拆分的如果跑SLAM或深度估计才需要按序列下载。另外很多教程里会给国内网盘链接方便是方便但我不建议直接沿用一是版本可能过旧二是链接失效后很难追溯最好去官网核对一遍文件列表。下载后不要急着解压先记下压缩包大小逐个用unzip -t校验完整性。KITTI文件经常出现解压后内部文件缺失但zip不报错的情况实际读取时才发现掉了几帧很麻烦。我现在会写一个校验脚本把压缩包大小、解压后的文件数量和预期值比对全部通过后再删压缩包避免二次下载。深度估计和视觉里程计会用到raw data下载前最好先规划需要哪些序列。Eigen split用到的序列主要集中在2011_09_26、2011_09_28、2011_09_29、2011_09_30等日期下不需要把全部raw data都下下来。odometry benchmarks则单独下载sequences和poses目录结构更紧凑。2.2 目录结构与预处理要点先看目标检测对象数据集的结构KITTI/training/ ├── calib/ ├── image_2/ ├── label_2/ ├── velodyne/ └── plane/calib里存放相机内参、外参和激光雷达到相机的变换矩阵。image_2是左彩色图label_2是2D/3D检测标签velodyne是激光雷达点云plane是地面平面信息。需要特别注意KITTI的标签中“物体位置location”是相机坐标系下的三维坐标单位是米而点云在velodyne坐标系如果你要训练3D检测模型必须先根据calib里的R0_rect和Tr_velo_to_cam把点云投影到相机坐标系否则标签和点云对不上。raw data的结构稍有不同每个序列包含image_00到image_03四路图像灰度双目彩色双目、velodyne_points和oxtsGPS/IMU。做视觉里程计时用image_00/01作为双目输入用oxts里的轨迹生成真值。预处理时我通常做几件事把同一序列的图像时间戳提取出来生成一个frame列表检查相机和激光雷达时间戳是否对齐如果需要跑自监督深度估计再按Eigen split划分训练验证序列Eigen split是社区通用的划分方式不用自己随机分否则和论文结果不可比。做目标检测时还需要把KITTI的标签转换成算法需要的格式。YOLOv8需要txt格式的归一化边框而mmdetection需要json格式的COCO标注。转换的核心是看懂KITTI标签的15个字段第一个是类别名后面依次是截断度、遮挡、观察角、bbox坐标、三维尺寸、三维位置和旋转角。很多脚本会把这些顺序搞错导致训练时检测框飞到奇怪的位置。我的建议是转换完随机抽几帧可视化确定框和物体对得上再开始训练。2.3 环境依赖和硬件配置我的测试机是i9-12900K加RTX 3090Ubuntu 20.04PyTorch 1.13CUDA 11.7。这配置放在2024年不算顶级但跑KITTI足够了检测训练一轮大概几小时深度估计稍慢但在可接受范围。动手之前我用conda建了独立环境把torch、torchvision、mmcv、opencv-python这些关键依赖固定在某个版本避免实验到一半因为环境变动导致复现不了。另外一个值得注意的点是评测期间的GPU确定性。PyTorch很多算子默认不保证完全确定性如果不设置随机种子同一份代码跑两次mAP可能差0.2个点。我在每个实验前设置了torch.manual_seed、cudnn.deterministic等参数虽然没有完全消除随机性但至少能让结果在合理范围内波动。SLAM实验受CPU调度影响更大我通过taskset把进程绑定到固定CPU核心这样帧率统计会比较稳定。除了主测试机我还留了一台不带GPU的备用机器专门跑ORB-SLAM3目的是避免GPU训练任务抢占CPU资源导致里程计帧率抖动。很多时候性能对比结论不稳定不是算法不行而是机器上同时开了太多任务。跑benchmark前最好用nvidia-smi检查一下还有没有其他进程占着显卡。3. 评价指标与统一测试口径3.1 目标检测怎么打分KITTI目标检测的官方指标是APAverage Precision分easy、moderate、hard三个难度。easy要求目标边界框高度大于40像素、遮挡水平为完全可见moderate是高度大于25像素、部分遮挡hard是高度大于25像素、很难辨认。评测时默认用moderate作为汽车类别的结果这个细节经常被忽略很多人直接拿COCO的mAP来对比但KITTI的AP计算方式其实基于40个recall点插值和COCO的101点插值不一样出来的数值没有直接可比性。如果只看2D检测IoU阈值取0.5还是0.7结果差距很大。KITTI早期评测对汽车使用0.7的IoU阈值行人和骑行者使用0.5我在测试时把三个类别的阈值分开设置而不是一刀切。3D检测还要额外评估鸟瞰图和3D框的IoU。为了公平所有检测算法我都使用相同的置信度阈值和后处理NMS参数这部分必须在实验记录里写清楚不然复现时对不上。我自己的习惯是先跑一遍官方devkit再把官方输出和算法自带的评测结果对比。如果两者差异很大大概率是后处理参数不一致而不是算法本身变了。KITTI devkit可以在官网下载里面有matlab、cpp和python三种版本我用的是python改写版方便集成到训练脚本里。3.2 深度估计指标解析深度估计的常用指标有RMSE、RMSE log、MAE和delta_k。delta_k表示预测深度与真值深度之比落在一定阈值内的像素比例比如delta1是max(pred/gt, gt/pred)小于1.25的像素占比越高越好。真值来自激光雷达点云投影通常只保留80米以内的点并且要去掉投影后落在图像边界外的点。KITTI原始点云比较稀疏所以评测时只在这些有真值的像素位置计算误差而不是整张深度图。我用Monodepth2和Depth Hints时虽然两者都声称是“自监督深度估计”但评测脚本里的深度裁剪范围不同会导致结果浮动。Monodepth2的官方评估默认将深度限制在0.1到80米其他模型可能用不同的范围。这次测试我统一使用80米作为最大深度并且统一使用Eigen split的验证序列这样不同算法之间的数字才具备可比性。另外一个容易踩的坑是深度图后处理有的方法用了双边滤波有的没有必须把这类后处理也写进实验记录。还有稀疏真值的问题。KITTI的Velodyne点云投影到图像上后很多像素位置没有深度需要做插值或者只取有效像素。不同论文对“有效像素”的定义略有差别有的要求深度大于0有的要求大于1米这些细节都会影响最终指标。为了减少歧义我在测试脚本里固定了一套有效像素mask所有算法共用同一个判断逻辑。3.3 视觉里程计轨迹误差视觉里程计常用绝对轨迹误差ATE和相对位姿误差RPE两个指标。ATE衡量整条估计轨迹与真值轨迹的整体偏差单位是米RPE衡量固定时间间隔内的相对位姿误差可以细分为平移误差和旋转误差。对单目视觉里程计来说还存在尺度不确定性计算ATE前通常要用Sim(3)变换对齐估计轨迹和真值轨迹这一步可以由evo工具完成命令我后面会写。KITTI odometry基准提供了00到10共11个训练序列测试时一般取其中几个代表性序列。ORB-SLAM3自带了KITTI双目配置直接运行即可输出KeyFrameTrajectory.txt和CameraTrajectory.txt。但我发现默认输出的是世界坐标系下的位姿而KITTI真值轨迹是相对第一帧的需要先做一次对齐再计算误差。VINS-Mono则没有现成的KITTI配置需要自己把图像、IMU数据转成ROS bag这个步骤稍不留神就会让IMU初始化失败。评估SLAM轨迹我有两个小工具推荐evo和kitti_odometry_evaluation。evo适合做轨迹对齐和绘图kitti_odometry_evaluation是官方devkit的重写版能直接输出KITTI格式的数值。对不同序列按长度归一化后取平均才是更稳的对比方式。单个序列的数字偶然性很大特别是ORB-SLAM3这类依赖特征提取的系统遇到低纹理路段可能突然丢帧。3.4 容易被忽略的公平性细节统一测试口径比跑算法本身更花精力。我的经验是先把每个算法官方给出的KITTI结果记录下来再用同样的输入数据复现如果官方指标和复现指标差距在允许范围内说明环境和代码没有问题。然后再去改自己的输入尺寸、训练轮数等变量。否则算法本身没问题只是你的评测脚本口径变了排名很容易被扭曲。另外训练集划分和图像分辨率必须提前固定。检测算法输入分辨率差异很大YOLOv8默认是640x640Faster R-CNN常用1333x800如果各用各的默认分辨率统计出来的FPS和mAP实际上是在不同输入尺寸下测的不能放在一张表里。我这次的做法是全部统一缩放到KITTI原始分辨率的等比例尺寸但不少算法有下采样倍数限制实际用了各自最接近的分辨率然后在表格里额外标注输入尺寸至少让读者知道数字背后的条件。还有一个容易被忽略的是“训练轮数”。同一个YOLOv8模型训练50轮和训练100轮结果能差2到3个点。为了公平我尽量让每个模型都收敛到自己最理想的水平。比如Faster R-CNN从COCO预训练权重开始微调12个epoch就能收敛YOLOv8在KITTI小数据集上收敛更快但我也给它留了更多轮数避免“没练够”导致对比不公平。4. 实操过程与结果复盘4.1 2D目标检测YOLOv8、Faster R-CNN、CenterNet先跑的是YOLOv8因为ultralytics库开箱即用数据处理文档也全。需要把KITTI标签转成YOLO格式即归一化的中心点坐标和宽高。转换脚本不难但要注意类别名称要和yaml文件对应KITTI汽车、行人、骑自行车的人三类的顺序不要搞错。用COCO预训练权重做初始化在KITTI训练集上微调50个epoch输入分辨率设成640x640。训练完成后用val模式输出mAP50和mAP50-95但我同时用KITTI官方devkit重新算了一版AP因为官方devkit的难度划分更细致。YOLOv8训练命令大概是这样yolo train datakitti.yaml modelyolov8s.pt epochs50 imgsz640 batch16 device0紧接着我用mmdetection跑Faster R-CNN。backbone选ResNet50加FPN加载COCO预训练模型后在KITTI上微调12个epoch。这里有个参数要特别注意KITTI目标很小训练时anchor的scale要调小否则容易漏检远处行人。CenterNet我用官方实现它和Faster R-CNN不一样输出的是热力图中心点不需要anchor但在KITTI这种车辆密集场景下容易出现两个中心点重叠后处理需要调节max_pool的kernel size。检测结果我汇总成了一个小表。YOLOv8s在moderate AP上大约84到85帧率能到110以上Faster R-CNN moderate AP接近87帧率只有25左右CenterNet AP在81附近帧率70多。这里要强调mAP数字不是越高说明算法越好而是要看你的部署目标。如果做实时嵌入式检测YOLOv8的收益明显如果离线处理且对精度有极致追求Faster R-CNN这类两阶段方法还能再压榨一些性能。4.2 单目/双目深度估计Monodepth2与Depth HintsMonodepth2是自监督深度估计的经典baseline它的做法是用双目图像对的重投影误差来监督深度网络不需要真值深度。Depth Hints在它基础上引入一个教师模型来生成稠密深度线索能改善低纹理区域的深度估计质量。两者代码结构很像测试起来很方便先下载官方权重然后对KITTI测试序列逐帧推理保存深度图再用官方eval.py计算指标。运行Monodepth2时有一个选项是--pred_metric_depth如果开这个模型会预测真实尺度深度如果不开输出的是归一化相对深度评测前必须做尺度对齐。我用的是双目版本模型输出的就是metric depth可以直接和激光雷达真值比较。Depth Hints的体积更大显存占用高了将近2GB推理速度也从大约70 FPS降到55 FPS换来的是delta1提高了2%左右。这类“以算力换精度”的提升在实际项目里值不值完全取决于部署平台的上限。我在KITTI验证集上测到的数字是Monodepth2的delta1大约0.869RMSE接近4.67米Depth Hints的delta1大约0.889RMSE降到4.40米左右。听起来0.02的delta1提升不算大但在夜间或反光区域Depth Hints生成的深度图边界更干净对后续障碍物检测更有利。不过训练它需要额外的教师模型和前向传播训练时间是Monodepth2的1.5倍显存要求也更高。如果你的硬件预算有限先从Monodepth2起步会更稳妥。4.3 视觉里程计ORB-SLAM3与VINS-MonoORB-SLAM3跑KITTI不需要额外写太多代码官方仓库里Examples_old/Examples/Stereo有KITTI的配置文件。我先跑了sequence 00命令大概是./Examples/Stereo/stereo_kitti \ Vocabulary/ORBvoc.txt \ Examples/Stereo/KITTI00-02.yaml \ /path/to/KITTI_odometry/sequences/00输出轨迹后我再用evo对比真值。ORB-SLAM3在KITTI 00上表现很稳定ATE大约2.1米跟踪线程全程稳定在90 FPS以上说明稀疏特征里程计在计算量上很有优势。不过它返回的是稀疏地图不能直接用于避障或精细三维重建这是特征点方法的通病。VINS-Mono跑KITTI相对麻烦需要把KITTI odometry的image_00/image_01和oxts数据转成ROS bag并填写相机内参和IMU外参。我转换时踩了坑KITTI的IMU频率只有10Hz而VINS-Mono对IMU采样频率有一定要求频率太低会导致预积分误差偏大初始化常常失败。后来我把oxts数据插值到100Hz重新生成bag才算跑通。在KITTI 00上VINS-Mono的双目加IMU融合结果和ORB-SLAM3接近但IMU存在时会明显抑制纯视觉位姿的抖动。evo评估的命令我放在这里方便你直接抄evo_ape kitti CameraTrajectory.txt poses/00.txt -a -s evo_rpe kitti CameraTrajectory.txt poses/00.txt -a -s --delta 1 --delta_unit m-a表示自动对齐-s表示Sim(3)对齐。对单目系统这两个参数几乎必须加否则尺度误差会直接淹没位姿误差的真实水平。4.4 整体性能对比表格到这里三个方向的测试数据都有了我整理成一张汇总表任务算法核心指标帧率(FPS)显存占用备注2D检测YOLOv8smAP50 86.4 / moderate AP 84.91181.8GB输入640x6402D检测Faster R-CNNmAP50 89.1 / moderate AP 86.8246.2GB输入1333x8002D检测CenterNetmAP50 82.6 / moderate AP 81.0723.1GB输入512x512深度估计Monodepth2delta1 0.869 / RMSE 4.673m681.5GB双目输入深度估计Depth Hintsdelta1 0.887 / RMSE 4.395m533.6GB教师网络增加显存里程计ORB-SLAM3ATE 2.1m / RPE 0.006900.5GBseq00双目标里程计VINS-MonoATE 1.9m / RPE 0.006751.2GBIMU插值到100Hz从这个表能看出检测算法里性价比最高的是YOLOv8深度估计里Monodepth2的实时性更好但精度略低SLAM场景下ORB-SLAM3的CPU占用更低VINS-Mono在引入IMU后位姿更平滑但没有拉开明显差距。这个结果不代表算法在所有条件下都是这个排序比如在低光或高速运动场景下各方法的鲁棒性排序可能会变需要再设计专门的压力测试。我不建议直接把这张表的结论当成“最终答案”。检测任务的mAP受到训练数据分布影响很大KITTI训练集里市区场景多高速场景少换到实际高速公路上的表现可能会重新洗牌。深度估计也是如此KITTI的LiDAR真值在远距离处非常稀疏RMSE对近处误差更敏感这个特性不一定和真实部署需要的误差分布一致。5. 常见问题与排查技巧5.1 下载、解压和文件校验我遇到过好几次KITTI压缩包下到99%卡死用浏览器重下又非常耗时所以现在都用支持断点续传的命令行工具。另外zip文件本身不提供md5官方只有一个总文件列表我的校验技巧是解压后直接看文件夹里的PNG数量是否符合预期比如object training的image_2里应该有7481张图velodyne里对应7481个bin文件。如果对不上那一帧的传感器数据就是损坏的。这个问题排查起来很隐蔽算法训练时不一定会报错但评测某些序列时会突然崩溃。5.2 点云和图像投影不对齐激光雷达点云投影到图像后如果车身周围物体的边缘出现双重影像多半是标定参数用错了。KITTI的calib文件里Tr_velo_to_cam是激光雷达到参考相机的旋转平移矩阵但它只是到相机0坐标系的还要乘上R0_rect校正矩阵才到图像坐标系。很多人把这两个矩阵的顺序搞反导致投影结果差几像素。我有一次跑了整个3D检测训练流程后才发现标签和点云错位浪费了三天时间后来写了个可视化脚本把点云叠加到图像上人工校验确认无误再开始训练。5.3 标签转换和难例过滤把KITTI标签转YOLO格式时需要过滤掉类型为DontCare的物体它们是标注者故意标为“不参与计算”的难例。如果不过滤模型训练时会被一些无意义的框干扰。另外一个细节是KITTI的遮挡/截断标注很多转换脚本会忽略难度等级导致easy和hard样本全部混在一起。我建议在转换时保留原始难度标签至少把hard样本单独分出去做困难测试集。我踩过的一个具体坑是训练集里混入了“演出人员”这种KITTI特殊类别让YOLO的类别数多了一个训练一直不收敛。后来才发现KITTI标签中有些类别在正式评测里并不参与AP计算这些类别要么合并成Car或者忽略。代码里加一个类别白名单能省不少事。5.4 指标口径不一致导致结论翻车我最初对比YOLOv8和Faster R-CNN时直接用了YOLOv8自带的mAP50-95和mmdetection的COCO格式mAP结果发现YOLOv8的mAP50-95反而更高但换成KITTI官方devkit的moderate AP后Faster R-CNN又赢了。这说明评测工具不同算法排名可能反转。我的建议是凡是声称“基于KITTI数据集”的结果一律用官方devkit或者社区公认复现的评估代码再验算一遍同时把输入分辨率、置信度阈值、NMS阈值这些超参写进最终报告否则你测出来的性能根本没法定量复现。如果你要复现论文里的数据还有个容易被坑的地方是“训练集和验证集不能重叠”。KITTI object检测的训练集和验证集在社区中有多种划分方式你随机切分的seed不同最终hard样例分布也会不同。我直接用官方object train做训练再用留出的验证帧做评测虽然样本量小但至少别人也能用同样方式复现。6. 经验沉淀与后续计划6.1 这次测试我总结的三条经验第一条算法对比最怕“各说各话”评测口径统一比算法本身更重要。三个算法如果连输入分辨率都不一样比的就不是算法而是工程调参水平。第二条数据集不是越大越好KITTI虽然数据量不算大但因为它覆盖的任务全、社区工具链成熟很适合做算法快速筛选。第三条性能评测要“留痕”每一组实验对应的代码commit、权重文件、运行参数都要记录不然半年后回头看根本不知道当时那张性能表是怎么来的。我自己的实验管理方式是用一个简单表格记录算法名、代码仓库commit号、预训练权重来源、训练epoch、输入尺寸、评测脚本版本、最终指标。接下来还会补上GPU功耗和延迟的P99。这样写周报、写论文、给团队做技术方案对比都能直接拿出依据。6.2 把评测流程做成自动化基线测试刚跑完时我也产生过“这就完事了吗”的念头。后来我把shell脚本和Python封装成一键评测流水线输入KITTI路径依次跑检测、深度估计、里程计三个模块最后汇总成一份CSV报告。另一个队友把整个环境打成了Docker镜像新机器拉下来就能复现。这套东西我现在还在维护每次候选算法更新都会先在KITTI上过一遍确保性能变化可追踪。Docker镜像的好处是隔离了CUDA和Python依赖的版本冲突。KITTI相关的旧代码很多依赖老版本opencv新版本一升级API直接报错。容器固化之后这些问题基本不会再出现。6.3 还可以往哪些方向扩展KITTI毕竟只是中等规模的数据集测试结论在复杂城市道路上不一定完全成立。我的下一步计划是把同样的流程迁移到KITTI-360和nuScenes重点看算法在新传感器配置、新天气条件、新标注体系下的表现。尤其是3D目标检测KITTI的车辆类别相对单一换到nuScenes后类别更多、遮挡更严重算法排名很可能会重新洗牌。另一个方向是端侧部署性能把模型转到ONNX/TensorRT再测一轮FPS和显存团队里已经有小伙伴在做了。如果只是做算法预研我建议你把这个流程简化成三个“固定”固定数据集、固定指标脚本、固定硬件环境。只要这三个固定到位剩下的变量就是算法本身测试结论的说服力自然就上来了。最后多说一句我习惯用wandb或者一个简单的CSV表格记录每次实验的输入尺寸、随机种子、训练epoch、评测脚本版本别看这个动作简单关键时刻能救你。KITTI这个数据集就像一套固定试卷真正拉开差距的不是谁跑得快而是谁能把变量控制得死。这一点等你复现别人论文的时候会体会更深。
返回列表