RetinaNet道路交通多目标检测实战:从原理到工程落地 最近在整理一个做了一段时间的道路交通多目标检测项目也就是标题里那个“_1”版本——基于RetinaNet的道路交通多目标检测与识别系统目前已经完成第一版能在真实道路视频流里同时识别车辆、行人、非机动车和交通设施等多类目标并且在工程落地层面跑通了从数据准备到推理部署的完整链路。这篇文章想把选型逻辑、原理拆解、训练调参和踩坑全过程写清楚给正在做类似目标检测项目的朋友一份可参考的“成长记录”。这类项目的目标很明确给定一段道路监控或车载摄像头画面系统需要同时输出画面里所有目标的类别和位置框而且要在车流密集、行人穿插、目标尺度差异极大的情况下尽量不丢框、不错框。RetinaNet在这个场景里算是一个非常稳妥的起点尤其是它处理“小目标”和“前景背景样本不平衡”这两件事的思路刚好对应道路交通场景里最常见的两个痛点。下面从最初选型开始讲。1. 为什么选RetinaNet做道路交通多目标检测——选型时的一笔账先交代一下背景。第一版的目标不是追求某个比赛级别的精度而是要在可控的开发周期内搭出一个真正可运行、可评估、可继续迭代的多目标检测基线。既然方向锁定在“道路交通”这个场景一开始就要把场景特点拆清楚目标类别多且大小悬殊、密集遮挡频繁、背景复杂且占像素比例极大。这几个特点直接决定了模型选型的方向。当时摆在我面前的无非是几类主流方案两阶段检测器Faster R-CNN系列、单阶段检测器SSD、YOLO系列、以及基于关键点或Transformer的新方案。我最终选了RetinaNet核心原因有三条。第一RetinaNet是单阶段模型推理速度快工程上更容易满足道路场景的实时性要求。道路交通检测往往要部署在边缘设备或车载工控机上算力预算有限两阶段模型精度虽好但速度压力很大。第二RetinaNet的骨干网络采用特征金字塔结构能拿到从高分辨率浅层到低分辨率深层的多尺度特征图。道路场景里远处行人可能只有几十个像素而近处公交车可能占满大半个画面这种多尺度目标同时出现的情况恰好是特征金字塔擅长处理的。第三它的Focal Loss专门针对“前景样本和背景样本数量极端不平衡”的问题。道路交通画面里绝大多数锚框都是没有目标的背景区域如果拿普通的交叉熵去训练模型会花大量精力去学“背景”学出来的检测器对稀疏的目标极度不敏感。Focal Loss给“容易分类的背景样本”不断降权让模型把注意力集中到那些难的、有目标的前景样本上。顺手做个横向对比表方便后来者选型模型类型精度速度小目标表现道路交通场景适配度Faster R-CNN高较慢较好适合离线分析实时性弱SSD中快较弱小目标易漏检密集场景一般YOLO系列中高很快中等依赖版本需要额外做样本平衡处理RetinaNet高快好精度/速度平衡适合第一版落地第一版选择RetinaNet本质上是想先把“能不能稳定检测出东西”这个基本问题答好后续再根据实际数据表现替换骨干或换更强的检测头。2. RetinaNet的骨架与两个子网络——锚点、金字塔、Focal Loss选型定了之后就要真正理解这个模型每个模块在干什么。RetinaNet本身结构并不复杂但它把骨干网、特征金字塔、锚点匹配、分类子网络、回归子网络和Focal Loss串成了一条完整流水线。任何一环理解不到位后面的训练都会出问题。2.1 骨干网络与特征金字塔多尺度特征是怎么来的RetinaNet通常用ResNet系列作为骨干网络我第一版用的是ResNet50主要是考虑显存和训练速度的平衡。图像输入后会经过ResNet逐层下采样产生不同分辨率的特征图。但仅靠骨干网络的最后一层特征图做检测对小的交通目标很不友好因为深层特征图语义信息强但空间分辨率低小目标在图上只剩几个像素很难定位。RetinaNet没有直接拿骨干网络输出做检测而是接了一个特征金字塔网络用自顶向下和横向连接的方式把深层语义信息和浅层细节信息融合生成P3到P7这几层特征图。P3对应原始图像的1/8分辨率适合检测小目标越往后特征图分辨率越低对应越大的目标。特征图层下采样倍数适合检测的目标尺度实际对应场景P38小目标远处行人、车辆尾部P416中小目标路口中间的非机动车P532中目标近处轿车、公交车P664大目标画面主体车辆P7128超大目标极少出现主要用于大目标兜底当时做目标尺度统计时发现项目里三分之一的目标真实框面积小于32×32像素这类目标如果只在P5以上层做检测基本必漏。RetinaNet把P3拉进检测范围算是解决了这个基础问题。2.2 锚点机制与正负样本匹配RetinaNet在每个特征图位置预设一组锚点框。我用的配置是每个位置3个尺度、3个长宽比共9个锚点。不同特征图层锚点的基础尺寸不同比如P3层锚点最小、P7层锚点最大这样每层只负责对应尺度的目标。训练时锚点与真实目标框做IoU匹配IoU大于0.5的锚点被视为正样本IoU小于0.4的锚点被当作负样本处于中间地带的锚点直接忽略。这里有一个常被忽略的细节正负样本匹配策略对道路交通场景非常敏感。比如两个行人挨得很近某个锚点和行人A的IoU是0.45和行人B的IoU也是0.45按默认规则它会被直接忽略。但如果行人B比较远这个框其实代表了一个“模糊但存在”的目标。第一版我没有做复杂处理只按默认规则来结果发现检测器对密集行人的召回率偏低后面才在数据增强和后处理上补救。2.3 分类子网络与回归子网络RetinaNet在每个特征图后分出两个平行子网络一个是分类分支输出每个锚点对应每个类别的概率另一个是回归分支输出锚点和真实目标框之间的四个偏移量。这两个子网络都是全卷积结构参数在所有锚点间共享这样既控制了参数量也让训练更容易收敛。回归分支输出的是归一化偏移值分别是锚点中心坐标和宽高的对数偏移。训练回归分支时用的是Smooth L1损失相比L2损失它对离群点更不敏感不容易出现某个难样本把梯度带偏的情况。2.4 Focal Loss为什么能压住“易分类背景”Focal Loss可以理解为在标准交叉熵上乘了一个调制系数。标准交叉熵里即使一个背景样本被正确分类且置信度很高它依然会产生一个不小的损失值大量高置信度背景样本的损失累加起来会压过少量前景样本的正确梯度。Focal Loss的形式是当某个样本的分类概率越高、也就是“越好分”的时候它的损失会被乘以一个接近0的权重。反之难分样本保留较大的损失贡献。配合类别平衡系数可以在训练过程中动态把注意力导向真正困难的前景样本。道路交通画面里背景锚点数量往往是前景锚点上万倍没有Focal Loss模型训练到最后大概率变成“什么框都输出低置信度”的保守模式。用了Focal Loss之后一个直观感受是训练前几个epoch里分类损失下降更快后面则剩下难例在拉动损失曲线。3. 数据标注的类别体系与增强方案——决定模型上限的环节模型选得再好数据准备不到位也是白搭。道路交通场景的数据比一般目标检测数据集更麻烦因为类别之间有大量相似外观和遮挡关系。第一版在数据上投入的时间几乎占整个项目的一半这部分经历我觉得最值得写。3.1 类别体系设计少而清晰别贪多第一版我只保留了七个类别行人、自行车、摩托车、小汽车、公交车、卡车、交通标志。这是一个刻意做减法的结果。最初添加过“电动车”“三轮车”“行人推车”这些细分类别但标注一致性和模型精度都很难保证因为不同标注员对“电动车”和“摩托车”的边界判断差异很大模型学出来的特征也混在一起。七个类别里真正容易混的是“自行车”和“摩托车”在像素较小或者被遮挡时两者外观确实像。第一版的处理方式是允许这一对误判存在先不在类别体系里强行细分后面再通过增加训练样本来改善。3.2 标注格式与数据划分数据标注格式我统一转成COCO风格JSON因为RetinaNet的常见训练框架对COCO格式支持最成熟。标注结构大概是{ images: [ {id: 1, file_name: img_0001.jpg, width: 1920, height: 1080} ], annotations: [ {id: 1, image_id: 1, category_id: 2, bbox: [x, y, w, h], area: 30456, iscrowd: 0} ], categories: [ {id: 1, name: pedestrian}, {id: 2, name: car} ] }如果你的原始数据是VOC格式的XML建议写个脚本统一转成COCO JSON一次性把字段对齐避免训练时再来回折腾。训练集和验证集划分要特别注意不能按视频帧随机切。我把同一场景的连续帧全部归到同一集合里避免模型“背”下视频帧的相似背景从而在验证集上刷出虚高指标。3.3 数据增强别增强过度也不要漏掉关键变换道路交通数据天然存在光照变化、天气变化、运动模糊等挑战但训练数据往往只来自几个晴天时段这时候增强就是必需品。我用的是随机翻转、多尺度缩放、颜色抖动、随机裁剪组合。多尺度缩放效果最明显。第一版输入尺寸在600到1000像素之间随机变化等效于让模型在不同距离下观察目标小目标检测的鲁棒性明显提升。有一个容易踩的坑是随机裁剪。在做目标检测的随机裁剪时如果裁剪框和真实框重叠不合理会把大量真实框切成残片甚至把目标一半裁出画面外。这个我单独用工具检查过增强后的可视化结果凡是把遮挡率提得太高的增强策略最终训练出来的模型漏检率都会上升所以一切增强都要以“目标仍然能认出是什么东西”为前提。3.4 类别样本数量不平衡的补救类别样本数量不平衡是道路交通数据里必然出现的问题。小汽车样本可能有上万交通标志只有几百如果不做处理交通标志这个类别即使被标出来模型也会倾向于把它忽略。我采用的策略是在采样器里给低频类别更高的采样权重同时用最简单可靠的的Copy-Paste增强把样本数量少的类别目标按真实尺寸粘贴到道路背景图上保证粘贴位置符合透视关系比如交通标志往路边放、行人往人行道放。这种增强虽然土但对小样本类别的召回率提升非常直观。4. 训练策略、超参与损失跟踪——怎么把模型“喂”到位数据准备好了接下来是整个项目里最磨人的阶段训练。RetinaNet的训练过程比想象中更依赖对损失函数的拆解你不能只看一个总损失值就判断模型好坏而是要分开观察分类损失、回归损失以及各类别AP的变化。4.1 优化器与初始超参我用的是带动量的SGD初始学习率设在0.01配合warmup。warmup的前500步学习率从0.001线性升到0.01这个操作非常关键。RetinaNet在训练初期锚点还没适应目标分布如果直接给大学习率容易出现分类损失直接发散、边界框回归方向跑偏的情况。批大小设为8受限于单卡显存。如果想增大批大小可以把输入分辨率调低或者改用ResNet50的小变体。第一版我没有用分布式训练单卡训练完整个流程大概要跑接近两天属于可接受范围。4.2 训练过程中的损失观察训练时我在每个epoch记录四类信息总损失、分类损失、bbox回归损失、正样本数量。很多框架默认只打印总损失但我建议一定要手动记录正样本数量。最终我的经验是正常收敛的情况下分类损失应该呈现快速下降后平缓减小的曲线回归损失则相对平缓。如果分类损失下降缓慢大概率是正样本占比过低如果回归损失振荡大概率是某些困难样本的损失权重太大。下面是我用来跟踪训练状态的一段示意代码你需要根据自己的数据结构调整# 训练日志跟踪示意记录每个epoch的关键指标 def log_training_metrics(epoch, cls_loss, reg_loss, num_pos): avg_cls cls_loss / num_pos avg_reg reg_loss / num_pos print(fepoch{epoch} fcls_loss{avg_cls:.4f} freg_loss{avg_reg:.4f} fpos_samples{num_pos})4.3 遇到损失异常怎么办第一版训练中遇到过两次典型的异常。第一次是训练到第10个epoch时回归损失突然出现一个巨大尖峰之后整个模型输出的框位置全部飘掉。排查发现是某个训练样本的标注框超了图像边界导致回归目标计算出异常值。处理方式是清洗数据时统一把超出边界的框裁回图像范围面积太小的直接删掉。第二次是损失整体不降。检查发现Focal Loss的γ参数设成了0等于退化成了普通交叉熵模型在大量背景锚点的压力下完全学不动。把γ调回2.0之后损失曲线在几十步内就有了明显反应。如果你也遇到“损失不降”的问题第一件事不是调学习率而是确认Loss实现里有没有应用Focal Loss调制系数再看正负样本比例是否严重失衡最后才考虑模型结构。4.4 学习率调度和停止时机第一版采用余弦退火学习率总共训练了20个epoch。从实际效果看验证集mAP在使用数据增强后大约在第8个epoch基本稳定第16到第20个epoch有小幅提升。如果再往后训练就会见到验证集指标开始震荡那是过拟合的信号。5. 从“检测出框”到“系统可用”——推理、后处理与评估训练完成只是第一步检测系统真正能不能用要看推理阶段的执行效率和后处理策略。道路交通场景里车辆和行人往往密集NMS参数的一点差异直接影响最终输出的可用性。5.1 NMS参数调优RetinaNet输出的原始框数量非常多必须经过置信度筛选和NMS去重。第一版里我把置信度阈值设在0.4NMS的IoU阈值设在0.5。这个组合对稀疏道路场景效果不错但在一个公交站附近的小型拥堵场景里检测器会输出大量重叠框NMS的IoU阈值太严就会把同一个目标的多个框全部留下导致重复计数。后来我把NMS IoU阈值提高到0.6重复框明显减少代价是某些紧密挨着的行人可能被合并成一个框。这类参数没有通用最优值最好按场景单独验证一遍。我的调法是把几种典型道路截图各取20张在0.4到0.7之间逐档测试每档看输出框数量和实际目标数量的匹配程度。5.2 推理速度与模型简化第一版最初直接使用训练框架的原始PyTorch模型推理输入1920×1080图像时单帧在GPU上约耗时80毫秒大约是12FPS对多目标检测系统来说偏慢。之后做了几件简化把输入分辨率统一缩放到1280×720同时把P6和P7层的前向计算从完整特征图改为只在候选区域密集时启用相当于一种“动态层跳过”的思路。这样在空旷路段能把平均帧率提升到22FPS左右而密集场景仍然保持全量计算保证精度不塌。如果你需要更进一步的加速可以把模型导出为ONNX再用推理引擎做FP16或INT8量化。不过要注意的是量化后小目标类别比如远处行人的召回率往往会下降必须用验证集去复测各类别的AP而不是只看整体mAP。5.3 评价指标怎么用多目标检测不能只盯一个mAP。道路交通场景里我要同时看三类指标mAP0.5表示宽松定位条件下的整体检测能力mAP0.75表示精确框位置的检测能力这个对交通轨迹分析很重要各类别的AP则用来发现某个被“欺负”的类别。我第一版在验证集上的mAP0.5约为0.78但其中交通标志类别只有0.51小汽车类别达到0.88。如果只报一个mAP这个差距就被掩盖了后续优化方向也会跑偏。建议每一位做同类系统的朋友在评估时把每个类别的AP打印出来做成表格。6. 第一版踩过的坑和可以复用的经验最后这部分我按“问题—原因—解决”的方式把第一版遇到的主要坑整理成一张表再补一些个人心得。现象根因解决方案小目标漏检严重特征金字塔低层特征参与检测不足或小目标样本太少启用P3层检测增加远处目标样本多尺度训练行人和非机动车误检类别外观相似标注边界不统一统一标注规则增加难例样本适当合并相似类别分类损失不降Focal Loss参数无效或正样本过少校验γ、α参数检查正样本锚点匹配比例回归损失尖峰标注框出界或异常目标数据清洗框回归目标做截断推理速度慢输入分辨率过高全层特征图都参与计算动态跳层缩放输入分辨率模型导出量化密集场景重复框NMS阈值设置过严调整NMS IoU阈值引入更精准的框回归目标个人心得方面有几个点我觉得比技术本身更值得记住。模型第一版不需要追求一次性把所有问题解决先把“能稳定检测出常见大目标”这件事做扎实再逐步加难例、加类别迭代节奏会健康很多。数据处理的时间和训练时间不是浪费交通场景的数据集中注意力放在光照、遮挡和多尺度这三件事上能覆盖大部分模型后续的大坑。做类似系统时一定把自动化的评估脚本提前写好每次改完参数立刻跑同一套验证集用指标对比而不是肉眼感觉来判断好坏。肉眼感觉在少数几张图上特别容易被骗只有整个验证集的统计数字是诚实的。第一版项目到了这个阶段已经能连续稳定处理城市道路视频流中的常见目标。下一步我自己的计划是先把交通标志类别的样本数量补上来再针对夜间和雨天场景做一次专门的数据采集然后升级骨干网络验证精度的提升幅度。如果你也在做类似的检测系统希望这篇文章能让你少走一点弯路尤其是把数据、损失、评估这三件事想清楚整个项目推进起来会顺畅很多。