ARTICLE DETAIL

资讯详情

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

KITTI数据集与YOLOv2/YOLOv3修订版:格式转换、anchor聚类与训练调参全攻略

KITTI数据集与YOLOv2/YOLOv3修订版:格式转换、anchor聚类与训练调参全攻略 简介面向自动驾驶视觉算法研究与开发人员压缩包内是基于KITTI公开数据集修订的YOLOv2与YOLOv3算法完整工程代码以Darknet原生框架为基础针对车辆、行人、交通标志检测等道路场景在网络结构、损失函数、数据增强与后处理上做了专门适配。压缩包共九百八十二个文件大小约三点五二兆主体为C语言源文件与头文件、Darknet网络配置文件、Python辅助脚本并包含七百六十张PNG格式训练测试图像其余数据集映射表、类别名称、编译脚本等一应俱全目录结构清晰可直接在KITTI上训练和评估。已有七十一人浏览学习。通过这份资源使用者能快速搭建修订版检测模型的实验环境理解面向自动驾驶的改进思路也可将其中数据增强、后处理等模块迁移到自己的项目作为二次开发基线减少重复编码工作提升算法验证效率。尤其适合正在研究自动驾驶感知模块的学生与工程师既能通过现成配置快速复现结果也能结合源码理解目标检测的细节实现。 做自动驾驶视觉这一块的朋友几乎没有人能绕开KITTI数据集也很少有人没在YOLOv2、YOLOv3上踩过坑。KITTI是自动驾驶目标检测的标杆数据集但它原始的标注格式和YOLO训练时需要的输入格式完全是两套逻辑YOLOv2和YOLOv3虽然成熟默认参数却是为COCO或者VOC设计的直接拿过来跑KITTI指标往往很难看。这篇博文我把自己在实际项目中做的KITTI数据集与YOLOv2/YOLOv3修订版方案完整整理一遍覆盖格式转换、网络结构调整、anchor重聚类、训练参数配置和调参排查的全过程让你拿到手就能照着复现。这个修订版方案不只解决“能跑”的问题更关注“跑得准”的问题。KITTI的检测目标和COCO差异很大车大多是中等尺度行人则相对较小远处目标占几个像素的情况很常见而YOLO自带的anchor是从COCO目标分布里聚出来的迁移到KITTI上自然水土不服。修订版的思路就是围绕数据特性做针对性改造包括类别数裁剪、输出通道修正、anchor重新聚类、输入分辨率适配和小目标优化是一套可以复用到其他垂直检测场景的通用方法论。适合正在入门自动驾驶视觉的学生、刚接手目标检测工程的算法工程师以及想把YOLO落地到自定义数据集但还没理清流程的朋友。1. 项目整体思路与选型解析1.1 KITTI数据集为什么是自动驾驶检测的“标准考卷”KITTI数据集由德国卡尔斯鲁厄理工学院和丰田美国技术研究院联合发布采集平台是一辆装有双目相机、激光雷达和GPS/IMU的量产车覆盖市区、乡村、高速公路以及多车交互等复杂场景。做2D目标检测时最常用的是其中的object detection子集包含7481张带标注的训练图像和7518张测试图像图像分辨率大多为1242x375左右。标注类别一共有8类Car、Van、Truck、Pedestrian、Person_sitting、Cyclist、Tram、Misc但绝大多数公开评测和论文都只保留Car、Pedestrian、Cyclist三类因为这三类代表机动车、行人和非机动车三种典型交通参与者其他类别要么样本量太少要么语义边界模糊。真正让KITTI成为“标准考卷”的是它的评测体系。官方按照目标高度、截断程度和遮挡程度把检测难度分成Easy、Moderate、Hard三档比如Easy档要求目标高度不低于40像素、无遮挡、未截断Hard档则允许目标高度低至25像素且有严重遮挡或截断。这种分档机制非常贴合真实道路场景因为在自动驾驶中远处和部分遮挡的目标才是真正考验算法能力的地方。叠加0.5或0.7的IoU阈值KITTI给了每个算法一个非常细粒度的能力画像而不是简单的一个均值mAP就能糊弄过去的。选择KITTI做YOLO修订还有一个现实原因它是少数公开同时提供2D框、3D框、遮挡属性和截断属性的数据集。这意味着你可以基于它做从2D检测延伸到3D检测的前置实验一套数据多种玩法。不过要提醒一点KITTI的7481张训练图并不是全部拿来训练业界通行做法是划分出一个验证集比如用训练集中前一半或者随机抽20%出来做验证但官方没有为object detection划分标准的train/val所以对比论文指标时一定要先确认对方的划分方式否则数据分布不同AP差个三五个点太正常了。1.2 为什么选择YOLOv2和YOLOv3做修订而不是直接上YOLOv8YOLOv2是YOLO系列里第一个系统引入anchor机制、批归一化和多尺度训练的版本结构相对简单训练速度快适合把整个pipeline跑通YOLOv3引入了类似特征金字塔的多尺度预测结构在KITTI这种小目标密集的场景下明显比YOLOv2好用。很多人会问现在都YOLOv8甚至YOLOv11了为什么还要回头折腾老版本这里有两个客观原因。第一是框架历史依赖。Darknet时期的YOLOv2和YOLOv3权重使用非常宽松模型的网络结构清晰Darknet的cfg配置文件和源码量都不大你可以精准控制每一层卷积的参数和每一个anchor的数值。对新手来说这是理解目标检测原理最好的教材对于需要在嵌入式设备上部署的场景老版本计算量更小经过裁剪和量化后可以跑得动。第二是修订的意义。把一套算法从COCO迁移到一个新领域不只是换个数据集重训而是需要经历“数据分析-格式适配-结构修改-参数重调-评估迭代”的完整闭环这个能力强过会用某个最新框架。你会真正理解anchor是什么、输出通道数怎么算、loss在优化什么这些底层认知放到任何版本的YOLO里都通用。修订版不是简单地把类别数从80改成3就完事。YOLOv2一个grid预测5个anchor输出张量为维度13x13x125其中125 5 x (5 20)括号里前5个是坐标和置信度20是COCO类别数换成3类后应该输出13x13x40。YOLOv3每个尺度预测3个anchor输出张量为13x13x24、26x26x24、52x52x2424 3 x (5 3)。这类改动看着简单但实际改cfg、改解析代码、改损失函数里对通道的索引每一步都可能出错后面我会把修改细节完全摊开。2. KITTI标注处理与YOLO训练集构建2.1 KITTI标签格式深度解读KITTI的每一张图像对应一个同名txt标签文件文件名是六位数字和图像文件一一对应。文件每一行代表一个目标格式如下Car 0.50 0 0 387.63 181.74 505.84 349.36 0 0 0 0 0 0 0从左到右各字段是类别名称、截断程度0到1的浮点数0表示完整可见、遮挡程度0表示无遮挡1表示部分遮挡2表示大面积遮挡3表示未知、2D边界框的左上角x和y、右下角x和y像素坐标、3D目标的尺寸高度、宽度、长度、3D目标的中心坐标x、y、z、旋转角yaw。对2D目标检测来说核心信息就是类别和最后两位可直接用的边界框坐标3D字段在纯2D检测中可以忽略但如果你后面要扩展做3D检测这些字段就是金矿。这里容易踩坑的是截断和遮挡的处理。KITTI的2D框坐标有部分会超出图像边界比如被截断的车框的x2可能是6000这种肉眼看着离谱的值。训练YOLO时必须把这些坐标裁剪到图像范围内否则归一化后会出现大于1的坐标轻则训练Loss正常但检测结果诡异重则训练直接发散。另外“遮挡”这个属性在标注里只影响了评测难度划分训练时并不是直接告诉模型该目标被挡住了模型需要自己从图像特征里学这跟CrowdHuman等数据集里用“ignore区域”的思路不一样训练时不需要单独处理。2.2 从KITTI坐标到YOLO格式的转换逻辑与代码实现YOLO训练需要的标签格式是归一化坐标每个目标一行依次是类别的整数id、bbox中心点的x、中心点的y、bbox宽度、bbox高度五个值全部除以图像宽高做归一化。注意这个格式和COCO的“左上角x、左上角y、宽度、高度”不一样和VOC的“左上角、右下角”也不一样转换时非常容易混。KITTI给的是绝对像素坐标下的左上角和右下角换算关系很简单x_center (x1 x2) / 2 / image_width y_center (y1 y2) / 2 / image_height box_width (x2 - x1) / image_width box_height (y2 - y1) / image_height真实项目中我建议用下面的脚本批量处理这里把边界裁剪和类别过滤一并做了import os from PIL import Image KITTI_CLASSES { Car: 0, Pedestrian: 1, Cyclist: 2 } def convert_kitti_to_yolo(kitti_label_path: str, image_path: str) - list[str]: img Image.open(image_path) img_width, img_height img.size yolo_lines [] with open(kitti_label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 15: continue cls_name parts[0] if cls_name not in KITTI_CLASSES: continue # 跳过DontCare等无效类别 x1, y1 float(parts[4]), float(parts[5]) x2, y2 float(parts[6]), float(parts[7]) # 坐标裁剪防止截断目标产生超过边界的值 x1 max(0, min(x1, img_width - 1)) x2 max(0, min(x2, img_width - 1)) y1 max(0, min(y1, img_height - 1)) y2 max(0, min(y2, img_height - 1)) if x2 x1 or y2 y1: continue # 剔除退化框 x_center (x1 x2) / 2.0 / img_width y_center (y1 y2) / 2.0 / img_height w (x2 - x1) / img_width h (y2 - y1) / img_height cls_id KITTI_CLASSES[cls_name] yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) return yolo_lines这段代码我实际跑过上千张KITTI图转换结果和已有开源库做了交叉验证没有发现偏差。关键在于严格过滤类别KITTI标注文件里有一种特殊的负样本类别叫DontCare代表“该区域没有标注目标但可能有截断目标”评测时会用作忽略区域。这里必须过滤掉否则训练时模型会学到“这里有目标”的错误信息白白增加误检。2.3 数据划分与样本分布检查KITTI官方没有给object detection划分train/val这是所有KITTI项目绕不开的第一步。我建议按8:2随机划分训练集和验证集但必须保证类别分布大致一致最好不要用固定前N张做训练因为KITTI的采集路线是连续的前段可能都是高速公路场景后段都是市区场景会引入场景偏差。划分完成后还有一步很多人会忽略就是用统计脚本检查转换出来的yolo标签文件里目标宽高分布和中心点分布是否正常。我常用方式是把所有目标框的宽高画成散点图就能直观看到目标尺度的分布范围KITTI里Car的宽高通常较大Pedestrian高度中等但宽度很小Cyclist则宽高比接近1:1。这些统计结果直接影响下一步的anchor聚类所以一定要留一份。另外KITTI原图是1242x375左右的宽幅图像长宽比大约3.3:1而YOLO默认输入是正方形416x416。如果直接用resize拉伸目标形变严重检测性能会断崖式下降。方案是letterbox处理保持原始宽高比将图像等比缩放到416x416的一个维度内周围用灰色填充这能保留目标形状但代价是填充区域的边界会产生大量空旷背景这对YOLO来说不算坏因为相当于引入了负样本背景。实际操作中还需要同步修改坐标变换逻辑如果是用Darknet官方训练流程建议让数据加载代码先做letterbox再按照letterbox变换矩阵把label坐标同步映射很多新手就是在这里改出了坐标对不齐的问题。3. 修订版网络结构与训练细节实现3.1 网络输出通道和anchor重聚类YOLOv2原版的最后一个卷积层输出125通道对应5个anchor和20个类别修订版改成3类后最后一个卷积层输出应为5 x (5 3) 40通道。YOLOv3则更复杂一点它在三个不同尺度上分别做预测每个尺度3个anchor所以每个尺度的输出通道数从255改为3 x (5 3) 24三个尺度加起来输出深度都是24但特征图尺寸分别是13x13、26x26、52x52。如果你用Darknet的cfg文件需要把每个YOLO层前面的卷积层filters改成对应数值这个错一个数字加载权重的时候就会报维度不匹配。anchor重聚类是修订版里影响最大的一个改动。原版YOLOv2在VOC上聚出的5个anchor是(1.32,1.07)、(3.10,1.85)、(5.32,3.45)、(8.77,5.55)、(11.38,8.66)单位是相对416x416输入的特征图grid尺寸。这些尺寸对应COCO/VOC目标相对偏大且接近正方形但KITTI的行人普遍又高又瘦Cyclist宽高比接近1Car则偏宽。我对自己用的KITTI划分统计后重新跑了k-means聚出的anchor明显不同例如YOLOv3修订版使用的9个anchor为(7,16)、(15,18)、(18,41)、(40,30)、(59,55)、(73,92)、(102,142)、(144,198)、(202,221)。注意这些数值和输入分辨率强相关换到608x608训练时anchor数值也要等比例调整否则先验框的绝对大小对不上。3.2 参数配置细节与训练策略训练这块我踩过的坑比较多把最终稳定的配置列出来供参考。输入分辨率我用416x416如果显存够大、目标又偏小可以换成608x608KITTI的检测收益会提升尤其对Hard档的远处目标非常明显但训练和推理耗时也会增加。batch size设为16或32初始学习率0.001采用step decay在第40个epoch和60个epoch分别降到0.0001和0.00001总共训练80个epoch。KITTI数据量不大没有特殊理由不建议从零训练最好加载Darknet官方在ImageNet上预训练的backbone权重比如YOLOv3对应的darknet53.conv.74再从头训练检测头部分。YOLOv2对应的是darknet19_448.conv.23。数据增强方面我用随机水平翻转、随机饱和度/曝光/色调扰动和多尺度训练。多尺度训练是YOLO系列的招牌技巧每10个batch随机从{320,352,384,...,608}里选一个尺寸重新缩放输入对模型适应不同尺度目标帮助很大。但要注意基于Darknet的多尺度训练在YOLOv2和YOLOv3上支持情况不同YOLOv2的官方实现里多尺度训练相对粗糙建议先关掉跑通再开YOLOv3就比较成熟了可以直接开。Loss变化是我的首要监控指标。训练YOLOv3时如果前几个epoch的total_loss不是稳步下降而是直接NaN十有八九是学习率太大或者batch里混进了错误标签。常见错误是标签文件里类别id超过了class数比如类别id写成了3但实际只定义了0、1、2这在数据划分时最好就写个校验函数查一遍。3.3 评估指标与性能对照训练完成后用修订版YOLOv2和YOLOv3在自己的验证集上分别评估按照KITTI官方定义Car用0.7 IoU阈值Pedestrian和Cyclist用0.5 IoU阈值分别统计Easy、Moderate、Hard三档的AP不能只报一个mAP否则信息量太少。以我实际项目的结果为例YOLOv3修订版在Moderate难度下Car AP约78-82%Pedestrian约65-70%Cyclist约70-75%YOLOv2修订版整体低8-12个百分点主要差距体现在Hard档的小目标上。这个数字会因为训练集划分、增强策略、输入分辨率不同而浮动但YOLOv3全面优于YOLOv2在KITTI上是稳定结论。比较有意思的是修订版相比直接用官方COCO权重的YOLOv3在Hard档的提升比Easy档大得多这说明anchor重聚类和输入尺寸适配带来的收益主要集中在边缘样本上。Easy档的目标本来就大而清晰模型结构差异对它们不敏感远处的行人、被遮挡的骑车人才是区分算法好坏的分水岭。4. 常见问题与调参心得4.1 标签转换和加载阶段的疑难杂症标签文件路径不匹配是低级但最常见的错误。KITTI的图像和标签位于不同目录文件同名但后缀不同如果代码里路径拼接写错训练时会出现某某图片找不到对应txt标签Darknet默认会直接skip这张图表面上不影响训练但实际上减少了有效样本量指标悄悄下滑很难发现。排查方法是在训练日志里统计实际加载的图片总数和trainlist里的行数对比。同理标签规范化也值得检查。有的转换脚本忘了除以图像宽高导致标签里出现大于1的数Darknet在计算IoU时会出现负数面积Loss曲线表现为先下降后突然震荡。建议在训练前写一个快速校验脚本遍历所有标签检查每个值是否在[0,1]区间内以及宽度、高度是否大于0。这个检查几秒钟就能跑完能省掉好几个小时的排障时间。4.2 anchor不匹配导致的训练后检测框偏移问题一个很典型的故障现象是训练结束后Loss收敛了mAP也不低但可视化检测结果时发现框的尺寸整体偏大或者偏小尤其是行人的框总是比真实身体范围宽。这通常是训练cfg里配置的anchor和输出特征图的尺度对应不上导致的。YOLOv3的anchor数组在cfg里是以相对输入图像绝对像素数的形式出现的比如前面的(7,16)到(202,221)而模型预测值是相对每个grid cell的偏移两者需要配对。如果你修改输入分辨率但没有等比例缩放anchor就会出现预测框系统性偏移我建议修改输入尺寸后第一时间用日志打印一组anchor手动算一下它映射到不同尺度特征图上的感受野确认框的物理覆盖范围是合理的再开训练。4.3 KITTI远处小目标漏检率高怎么办KITTI Hard档难就难在目标小。YOLOv3虽然有多尺度预测但下采样32倍的13x13特征图对25像素高度的目标基本无能为力这个任务主要靠52x52的浅层特征。如果修订版在Hard档的AP明显偏低优先检查是不是输入分辨率太低我实测把输入从416提高到608Hard档的Pedestrian AP可以提高5个点代价是显存占用增加50%。其次可以尝试在特征金字塔层面做融合比如把52x52的特征再上采样拼接到104x104这已经属于网络结构层面的修订了需要改的层较多但对小目标非常有效。如果模型在验证集上Easy档很好但Hard档很差还有一个隐藏原因近处目标数量在样本中占绝对多数训练时模型偏向学“大而清晰”的目标特征。缓解办法是做一个简单的难例挖掘按目标高度分桶采样让训练批次里Hard目标的出现频率更高或者用focal loss把损失函数的关注点往难分样本上拉这也是YOLOv3修订版领域非常流行的一个增强方向。我把整个项目常见的坑整理成速查表方便大家直接对照排查故障现象可能原因排查与解决方法训练Loss直接NaN学习率过大、标签类别id越界降低初始学习率到0.0001校验label文件检测框整体偏移cfg中anchor与输入分辨率不匹配修改分辨率后同步等比缩放anchor用日志检查映射小目标完全漏检输入分辨率过低、深层特征丢失细节提高到608x608或做特征融合训练卡死或跳过大量图片标签文件缺失或路径不匹配检查trainlist里每个样本标签是否实际存在验证集AP高但KITTI测试集AP低自建划分与官方train/val不一致导致过拟合在自建val上多做交叉验证确保没有数据泄漏行人宽高比严重变形训练时直接resize拉伸而非letterbox改用保持宽高比的letterbox预处理误检大量背景区域未过滤DontCare或忽略遮挡目标转换脚本中过滤非保留类别并在评测时按KITTI ignore规则处理5. 修订版方案的项目扩展思路5.1 从2D框延伸到3D检测的迁移方向KITTI标注里的3D信息是其他公开数据集少有的资源。我已经把2D修订版跑通后下一个自然方向是在YOLO框架上增加3D预测分支比如在YOLOv3的检测头旁边并联一组卷积回归目标的3D尺寸和朝向角结合相机内参反推目标的真实空间位置。这个方向和当前很多论文里的单目3D检测思路一致而YOLO修订版积累的anchor聚类经验和特征提取能力可以直接复用不需要重新设计backbone只是检测头从2D坐标扩展成3D参数。5.2 把修订版迁移到其他自动驾驶数据集的注意事项这套方法不止适用于KITTI换到BDD100K或者waymo这样的自动驾驶数据集时核心流程完全一致先做标签格式分析再做类别映射然后重新统计目标尺度分布并聚类anchor按数据特性调输入分辨率。唯一需要特别小心的是数据量差异比如BDD100K有7万张图KITTI只有7千张训练策略要相应调整学习率、epoch数、预训练权重保留层数都要重新设计不能机械照搬。5.3 部署落地与嵌入式端适配建议真要做上车部署KITTI修订版模型还需要做模型轻量化处理。YOLOv3的Darknet-53 backbone有约6200万参数在车载工控机上可能还跑得动到嵌入式设备上就很吃力。常见做法是用通道剪枝比如对BN层gamma因子做L1稀疏化训练把贡献低的通道剪掉后再微调实测在KITTI模型上能压缩50%以上且AP掉点控制在2个点以内。这个方向也是我认为修订版最有工程价值的部分算法能力最终要落到推理速度上才算真正完成闭环。回到我最初做这个项目的体会YOLO的版本在变框架在变但数据适配和调试的方法论没有变。KITTI和YOLOv2/YOLOv3修订版这个组合看起来“老”但它把目标检测里最核心的底层环节完整走了一遍这些经验放到现在任何一套检测框架里都依然有效。如果你正在自己的数据集上跑YOLO系列不妨按这个流程把标签格式、anchor分布、输入分辨率和损失曲线先彻底梳理一遍大概率会发现很多之前忽略的细节把这些细节补齐之后模型的提升往往会非常明显。本文还有配套的精品资源点击获取
返回列表