KITTI数据集YOLOv2/YOLOv3目标检测修订实战:从数据预处理到训练调优

发布时间:2026/9/8 9:43:49
KITTI数据集YOLOv2/YOLOv3目标检测修订实战:从数据预处理到训练调优 简介面向自动驾驶场景的KITTI数据集YOLOv2/YOLOv3修订版资源主要供计算机视觉研究者、自动驾驶算法工程师及有YOLO基础的学习者使用。修订版在Darknet框架基础上针对KITTI中车辆、行人、交通标志等复杂交通目标改进了网络结构与损失函数并引入适用于光线变化和天气扰动的数据增强与后处理策略以提升真实道路条件下的检测稳健性。压缩包共982个文件大小3.52MB以760张png图像样本和68个c源文件、48个h头文件为主体同时包含40个cfg配置文件、10个cu与10个py脚本以及names、data、sh等辅助文件基本覆盖Darknet工程编译、模型配置、数据加载与训练调用等环节。对希望复现修订版YOLO在KITTI上训练流程、排查自定义数据集格式问题或研究检测头/损失函数改进的开发者这些代码和脚本能提供直接参考。目前已有71人学习下载适合作为自动驾驶目标检测方向的项目参照。1. 这个修订项目到底在改什么1.1 为什么偏偏是KITTI和YOLOv2/YOLOv3KITTI数据集在自动驾驶目标检测里算得上老牌选手很多做检测算法的人第一次接触真实道路场景都是从它开始的。相比COCO那种通用物体数据集KITTI的数据采集车装备着摄像头和激光雷达拍摄的是德国卡尔斯鲁厄市区的真实道路包含了市区、乡村、高速公路等多种场景图像里车、人、骑行者互相遮挡的情况非常常见光照变化也大。这种数据带来的问题很直接通用模型在这上面跑精度往往不如在COCO上那么漂亮。而YOLOv2和YOLOv3这两代模型说实话已经不算新东西了。YOLOv2当年用batch normalization、anchor box和高分辨率输入把一阶段检测做成了真正能跑的实时方案YOLOv3进一步引入多尺度特征图和logistic分类在当时是速度和精度的最佳平衡点。我见过很多人直接用官方权重在KITTI上跑结果检测出来的框要么漏掉远处的行人要么把路边的车框给树。不是模型垃圾是数据分布和标注规范完全不匹配。这个“Revised version of YOLOv2 and YOLOv3”项目要解决的就是这个问题在不改动模型核心推理逻辑的前提下通过数据侧和训练策略侧做系统性修订让这两代YOLO在KITTI数据集上真正发挥出应有的效果。适合谁参考想做自动驾驶检测但不想上太重模型的开发者、做毕设需要KITTI加YOLO组合的学生、以及想理解数据如何反过来影响模型训练结果的算法工程师。1.2 修订的核心思路不是改网络而是改数据观很多人拿到KITTI第一反应是换网络结构把backbone换成ResNet或者加上注意力模块。我的经验恰恰相反KITTI上YOLOv2和YOLOv3表现差主要问题不在于网络不够强而在于数据侧没有被认真对待。KITTI的标注格式和VOC、COCO都不一样。它单张图像里目标数量少但稀疏分布类别只有Car、Van、Truck、Pedestrian、Person_sitting、Cyclist、Tram这八类再加上一个特殊的DontCare标记。直接套用COCO预训练模型输出的类别映射等于强行让模型在完全不同的语义空间里做推理。修订的第一步是重新定义类别体系。我最终把Car、Van、Truck合并成CarPedestrian和Person_sitting合并成PedestrianCyclist单独保留Tram和DontCare直接忽略。这样模型从原本需要区分八类降到了三类难度大大降低召回率和精确率反而上去了。另一个关键点是anchor的重新聚类。YOLO系列默认anchor是在COCO上算的COCO目标普遍较大较居中而KITTI里车辆和行人往往出现在画面两侧尺度差异大远处的汽车可能只有二三十像素宽。用COCO的anchor去匹配这种分布结果就是很多目标没有合适的先验框匹配到训练时负责检测该物体的格子没有激活自然就漏检了。2. KITTI数据集处理与标签转换的必修课2.1 数据集下载与目录结构说明KITTI数据下载这块经常有人卡住其实官网cvlibs.net/datasets/kitti里找到object detection benchmark然后分别下载left color images、camera calibration matrices、training labels这几个包加起来大概就十几个G。下载的时候注意一定把calibration也拿上后面做3D可视化或者生成BEV投影图都要用到相机内参。解压完整后的目录结构长这样KITTI/ ├── training/ │ ├── image_2/ # 左相机彩色图PNG格式1242x375左右 │ ├── label_2/ # 2D/3D标签文件TXT格式 │ ├── calib/ # 相机标定文件TXT格式 │ └── velodyne/ # 激光雷达点云数据非必要可不下 ├── testing/ │ ├── image_2/ │ └── calib/训练集一共7481张图每张图对应一个同名的TXT标签文件。这里有个小坑官方划分的train和val分别有3712和3769张但val图片在训练包里面并没有单独分开需要自己按官方划分文档来切。网上很多代码直接随机划分比例和官方不一致会导致评估指标没有可比性提交论文的话容易被人质疑。2.2 标签格式转换从KITTI到YOLOKITTI的标签每一行有15个字段最有用的是类别、截断程度、遮挡程度、2D框坐标、3D尺寸、3D位置和朝向角。YOLO训练时只需要txt格式的class、x_center、y_center、width、height而且必须归一化到0到1之间。所以无论你用的是darknet原版还是PyTorch复现版第一步都是做格式转换。我写的转换脚本核心逻辑是这样的import os import numpy as np def kitti_to_yolo(kitti_label_path, img_width, img_height, class_map): yolo_lines [] with open(kitti_label_path, r) as f: for line in f.readlines(): parts line.strip().split() cls_name parts[0] if cls_name not in class_map: continue # 跳过DontCare和不需要的类 # KITTI bbox是浮点数left top right bottom left, top, right, bottom map(float, parts[4:8]) if right left or bottom top: continue w right - left h bottom - top x_center left w / 2 y_center top h / 2 # 归一化 x_center / img_width y_center / img_height w / img_width h / img_height cls_id class_map[cls_name] yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return yolo_lines这段有几个值得注意的细节类别编号必须从0开始和YOLO的class id对齐DontCare不能简单删掉好的做法是把它们转成一个特殊的ignore标记或者干脆在训练时算loss把那些区域的预测排除掉否则模型在训练时看到同一位置又要有框又不要有框loss会来回震荡另外KITTI少数图片的标注框有越过图像边界的情况转换时最好做clip避免生成宽高为0或者负数的框。3. 网络输出修改与训练调优3.1 anchors重新聚类让人头疼但必须做拿到修订版项目第一步不是急着改配置文件而是重新统计数据集中标注框的分布。KITTI的图片统一是1242x375比例接近2:1和COCO的方形图差很多。原始YOLOv3的anchors是在416x416输入下设计的放到这种横向构图上很多框在宽度维度的匹配会出问题。我用的聚类方法是最常见的k-means距离度量改成了IoU距离。注意不能直接拿欧氏距离去聚不然得出的anchor受大框影响太大对小目标不友好。公式很简单d 1 - IoU(box, centroid)。聚类完后按面积从小到大排列再把它拆给三个检测层。YOLOv3总共需要9个anchor大尺度特征图13x13对应最大的3个anchor中尺度26x26用中等的小尺度52x52用最小的那组。我在KITTI上聚类完得到一组和官方默认差异很大的值小目标的anchor面积甚至不到COCO默认的十分之一。这里给一个比较有代表性的结果供参考检测层输入特征图尺寸Anchor尺寸宽x高大目标层13x13(32,48)、(42,73)、(68,101)中目标层26x26(16,22)、(24,38)、(36,52)小目标层52x52(6,12)、(10,18)、(14,38)必须提醒一下这个结果和你的输入分辨率、类别合并方案直接相关。如果输入尺寸改成544cluster出来的anchor会大一圈。所以一定要在确定输入尺寸之后再做聚类不要随便抄别人的结果。3.2 修改cfg与输出层参数以YOLOv3为例假设最终类别数用3Car、Pedestrian、Cyclist那么最后一个卷积层的filters数要改成3*(53)24。这个3是先验框数量5是(x,y,w,h,objectness)。每个检测层的最后一个conv都改一遍这是个容易漏掉细节的地方。我用的是darknet官方cfg分别在yolo层前面改了三个conv的filters然后把三个yolo层的classes改成3。YOLOv2的修改相对简单因为它是单尺度输出最终特征图是13x13anchor数量通常设为5。filters改成5*(53)40即可。不过YOLOv2在KITTI上的表现受限于没有特征金字塔小目标漏检比较严重我在实际项目中把输入分辨率从416上调到了544mAP提升比想象中明显因为远处的行人从20像素变成接近30像素特征更清晰。还有一个容易被忽略的配置是ignore_thresh。这个值控制着被忽略的anchor在高IoU时的loss贡献阈值默认0.7在KITTI上偏高。我把ignore_thresh降到0.5之后那些和anchor重叠但不够高的情况下不会疯狂去反向传播梯度训练稳定很多不然容易在初期就发散。3.3 训练策略学习率、数据增强和多尺度训练修订版YOLO我的配置是batch size固定64subdivisions设成8初始学习率0.001warmup跑500个batch到第40000次迭代降到0.0001第45000降到0.00001。KITTI训练集只有不到7500张图按YOLO这种每张图会随机裁剪增强的方式跑个五六万iteration就够收敛了没必要像COCO那样跑几十万次。增强策略上我做了三件事。第一随机翻转概率0.5注意KITTI里车辆左右不对称的问题不明显翻转对检测没有坏处。第二jitter取0.3做了随机缩放和裁剪。第三多尺度训练打开random设成1每10个batch随机取320到608之间的尺寸。这里有个经验KITTI原始图片不是正方形等比resize容易引入黑边darknet在训练时会直接把图片变形成输入尺寸导致目标被拉宽。我的做法是在输入尺寸上把宽高设成不一致比如固定宽608高256这样更贴近KITTI原始比例。实测下来比硬拉成416x416的mAP要高出两三个点。4. 模型评估与效果对比4.1 评估指标和KITTI官方差异KITTI的评估和COCO不太一样。官方mAP计算时是把Car、Pedestrian、Cyclist分别算AP再平均而且分Easy、Moderate、Hard三个难度等级区分标准是目标高度和遮挡程度。Easy指框高超过40像素、完全可见的Moderate是框高超过25像素、部分遮挡的Hard是框高超过25像素、难以辨认的。如果你的模型只在Easy上刷点说明泛化能力不够。在算mAP的时候建议直接用KITTI官方提供的devkit脚本或者参照它的规则自己写评估器。网上很多GitHub代码把训练集自己划分成train和val但val的难度分布和官方不一样算出来的数字没法横向比较。我自己用的做法是在YOLO推理出来的结果按KITTI格式写回txt放到官方评估工程里跑这样最稳。注意YOLO输出归一化框要乘回图片宽高再转成KITTI的left top right bottom格式。4.2 修订前后对比到底提升了什么在我自己的实验里原版YOLOv3官方weights不做任何修改直接跑KITTI val只按类别名映射到对应IDCar类的AP大约在61%左右Pedestrian只有惨不忍睹的23%Cyclist由于类别语义完全不匹配基本就等于没检测出来。修订版本类别合并anchor重聚尺寸匹配在相同val集上Car能到86%Pedestrian到62%Cyclist到70%左右。YOLOv2提升幅度也类似但整体比v3低几个点毕竟模型结构和特征融合差了一代。提升最明显的是Pedestrian。原版模型对遮挡严重的行人几乎全部漏检修订后因为anchor更小、更密遮挡行人的召回率大幅提升。但代价是误检变多了路牌、树干这种竖条状物容易被当成行人。为了压制误检我在后处理上把NMS的置信度阈值提高到0.4并且针对Pedestrian类别单独加了一个面积过滤规则小于15x30像素的检测框直接丢掉因为KITTI训练集里真实行人的最小标注都不低于这个尺度。5. 常见问题与排查技巧实录5.1 训练loss异常先说最常见的一个坑修改cfg之后训练loss直接nan。遇到这种情况八成是最后一层filters改错了。YOLOv3的3个yolo层对应的conv filters必须精确匹配改漏一个都会导致tensor维度和anchors数量对不上loss计算时索引越界。另外检查一下anchors有没有按面积从小到大排列darknet要求anchors必须从最小排到最大不然训练会在中途崩溃。还有一个我踩过的坑KITTI的部分标签文件是空文件。一张图里没有任何目标的情况很少但确实存在。darknet跑训练时遇到空标注的文件会跳过但如果你自己写PyTorch版本空数组直接进loss计算就会报错。解决方案是读取标签时过滤空文件不能简单地丢一个全负的target进去。5.2 收敛慢和漏检严重如果训练了上万次迭代loss还是不降首先检查输入尺寸和anchors是否匹配。我之前试过把输入固定成416x416但用了根据608尺寸聚类出来的anchors结果目标框的位置回归一直不稳定因为anchor的尺度相对输入图来说太小了正样本数量严重不足。重新聚类并统一尺寸后问题立刻消失。漏检严重还有一个常见原因是类别编号错位。很多人把KITTI的类别id直接设为原标签里的数字但YOLO的class id索引从0开始。如果Car原来是第1类你忘了减1模型训练时看到的是类别2推理时又按类别0来取那AP曲线根本拉不起来。5.3 DontCare区域到底怎么处理这个可能是KITTI最特殊的地方。DontCare标记的是那些没有被标注的车辆或行人出现在目标密集的区域。如果直接把DontCare的框删掉模型会把那个位置当作背景去学习导致附近真实目标被抑制。我建议在数据增强和loss计算时把这些区域映射成忽略掩码不参与损失计算。darknet本身没有直接支持这种操作需要改数据加载器在读取标签时把DontCare框存成ignore列表。实测下来处理好DontCare能稳定提升1到2个点的mAP。5.4 推理阶段的性能优化修订版模型在1080Ti上跑KITTI测试集YOLOv3大约能到40FPS左右YOLOv2能上60FPS对于需要实时处理的应用来说都够了。如果硬要压帧率可以把输入宽度从608降到544mAP损失不足1个点但速度能快15%。另外NMS的处理建议放到GPU上做用torchvision自带的batched_nmsCPU上跑循环NMS在目标数量多的时候会明显拖慢整体推理。我个人在实际操作中还有一个比较深的体会修订这类比赛级数据集上的模型花在数据侧的时间一定要比网络结构多。很多人的第一反应是去改backbone、加注意力机制但KITTI这种数据量不大、标注规范特殊的场景数据格式转换、类别语义对齐和anchor重聚类带来的收益比堆模型参数来得快得多。如果你也想在KITTI上用YOLO系列做实验记住先把数据侧的事情做扎实再谈模型优化。本文还有配套的精品资源点击获取