简介:面向自动驾驶场景的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/YOLOv3
KITTI数据集在自动驾驶目标检测里算得上老牌选手,很多做检测算法的人第一次接触真实道路场景,都是从它开始的。相比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合并成Car,Pedestrian和Person_sitting合并成Pedestrian,Cyclist单独保留,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到YOLO
KITTI的标签每一行有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) |
必须提醒一下,这个结果和你的输入分辨率、类别合并方案直接相关。如果输入尺寸改成544,cluster出来的anchor会大一圈。所以一定要在确定输入尺寸之后再做聚类,不要随便抄别人的结果。
3.2 修改cfg与输出层参数
以YOLOv3为例,假设最终类别数用3(Car、Pedestrian、Cyclist),那么最后一个卷积层的filters数要改成3*(5+3)=24。这个3是先验框数量,5是(x,y,w,h,objectness)。每个检测层的最后一个conv都改一遍,这是个容易漏掉细节的地方。我用的是darknet官方cfg,分别在yolo层前面改了三个conv的filters,然后把三个yolo层的classes改成3。
YOLOv2的修改相对简单,因为它是单尺度输出,最终特征图是13x13,anchor数量通常设为5。filters改成5*(5+3)=40即可。不过YOLOv2在KITTI上的表现受限于没有特征金字塔,小目标漏检比较严重,我在实际项目中把输入分辨率从416上调到了544,mAP提升比想象中明显,因为远处的行人从20像素变成接近30像素,特征更清晰。
还有一个容易被忽略的配置是ignore_thresh。这个值控制着被忽略的anchor在高IoU时的loss贡献阈值,默认0.7在KITTI上偏高。我把ignore_thresh降到0.5之后,那些和anchor重叠但不够高的情况下不会疯狂去反向传播梯度,训练稳定很多,不然容易在初期就发散。
3.3 训练策略:学习率、数据增强和多尺度
训练修订版YOLO,我的配置是batch size固定64,subdivisions设成8,初始学习率0.001,warmup跑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(只按类别名映射到对应ID),Car类的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降到544,mAP损失不足1个点,但速度能快15%。另外NMS的处理建议放到GPU上做,用torchvision自带的batched_nms,CPU上跑循环NMS在目标数量多的时候会明显拖慢整体推理。
我个人在实际操作中还有一个比较深的体会:修订这类比赛级数据集上的模型,花在数据侧的时间一定要比网络结构多。很多人的第一反应是去改backbone、加注意力机制,但KITTI这种数据量不大、标注规范特殊的场景,数据格式转换、类别语义对齐和anchor重聚类带来的收益,比堆模型参数来得快得多。如果你也想在KITTI上用YOLO系列做实验,记住先把数据侧的事情做扎实,再谈模型优化。
本文还有配套的精品资源,点击获取