简介:面向自动驾驶与目标检测领域的研究者、算法工程师及相关专业学生,这份文档系统阐述一种针对交通灯尺度小、环境复杂等难题而提出的多尺度YOLOv5检测算法。内容完整覆盖核心设计思路:采用复合数据增强增加输入信息量,以多尺度训练替代固定尺度训练来均衡模型学习能力,并构建4倍、8倍、16倍、32倍下采样信息融合的多尺度特征融合网络,同时引入远跳链接提升小目标检测能力。文中还给出了数据集构建、消融实验及结果分析,并附有中英文摘要与关键词,便于快速把握研究脉络。资源为1个docx文档,压缩包大小约1.6MB,内容紧凑、结构清晰,适合需要借鉴交通灯检测改进思路或开展相关实验的读者。目前已有194人学习,具备一定参考价值。
1. 为什么交通灯检测要把 YOLOv5 拆成多尺度
交通灯是目标检测里一个非常特殊的小目标场景。城市道路上的红绿灯通常只有几十个像素宽,一辆车在 50 米外看到的交通灯可能只占画面的 0.5%;而同一张图里往往同时出现近处的高清灯头、中距离的模糊灯珠和远处近乎噪声的信号点。单模型、单尺度特征图很难同时覆盖这种目标尺寸分布。多尺度 YOLOv5 的交通灯检测算法,核心就是在 YOLOv5 的 FPN+PAN 结构上,把特征层选择、锚框匹配和训练采样策略重新对齐到交通灯的实际尺度分布。它解决的不是「用 YOLOv5 跑通一个检测」,而是让模型在小目标召回率和误检率之间找到可落地的平衡点。
这篇文章从特征层与锚框的关系讲起,给出一套可复现的多尺度改造方案,覆盖数据增强、训练参数和部署阶段的优化。读者需要具备基础的 YOLOv5 使用经验,我会把参数背后的动机和调法讲清楚,不列无意义的默认值。
2. 特征金字塔的尺度分配:交通灯应该交给哪一层
2.1 P3/P4/P5 与目标尺寸的对应关系
YOLOv5 默认使用 P3、P4、P5 三层特征图做检测,分别对应输入尺寸的 1/8、1/16、1/32。在 640×640 输入下,P3 的每个网格对应原图 8×8 像素区域,适合检测 8 到 32 像素的小目标;P4 对应 16×16 区域,适合 32 到 128 像素的中目标;P5 对应 32×32 区域,适合 128 像素以上的大目标。
交通灯的灯头宽度在城市道路视频里通常落在 12 到 60 像素之间,这正好压在 P3 和 P4 的边界上。直接把模型默认的锚框套上去,会导致大量交通灯被分到偏大的锚框组里,小目标的 IoU 很容易低于正样本阈值而被丢弃。
# YOLOv5 默认锚框(以 COCO 预训练为例) anchors: - [10, 13, 16, 30, 33, 23] # P3 小目标 - [30, 61, 62, 45, 59, 119] # P4 中目标 - [116, 90, 156, 198, 373, 326] # P5 大目标这段配置里 P5 的最小锚框是 116×90,在交通灯场景里几乎不会匹配到任何真实框。多尺度改造的第一步不是加检测头,而是重新检查锚框和特征层的匹配关系。
2.2 用聚类重新生成锚框
YOLOv5 提供了自动计算锚框的脚本,但默认设置下聚类结果仍然偏向 COCO 的尺度分布。常见做法是单独统计交通灯数据集的真实框尺寸,再做一次 K-Means 聚类。
python utils/autoanchor.py --cfg models/yolov5s.yaml --data dataset.yaml运行之后脚本会在终端输出新的锚框,并显示每个锚框与真实框的平均 IoU。如果平均 IoU 低于 0.7,说明数据集中存在大量极端长宽比的目标,需要检查标注框是否包含倒计时数字或 LED 灯珠这类细长区域。
交通灯的标注策略容易踩坑:把整个灯头框进去,还是连黑底外壳一起标注?我一般建议标注发光的灯珠区域,去除灯壳和横杆的背景像素。这样能显著提高小目标的正样本占比,聚类出来的锚框也更集中。聚类结果中如果 P3 层出现 8×8 甚至 6×6 的锚框,不要觉得奇怪,这恰好说明数据里有大量极小目标。
注意:autoanchor 脚本算出的锚框是基于原始图片尺寸统计的,不是基于缩放后的输入尺寸。如果训练时用 640×640 输入但原始图片是 1920×1080,锚框匹配时要保证换算方式一致,否则会出现训练时匹配正常、推理时框偏大的问题。
2.3 多尺度采样的实际效果
光改锚框还不够。YOLOv5 默认启用了多尺度训练,每 10 个 batch 随机从 0.5 到 1.5 倍之间缩放输入尺寸。交通灯数据集的原始分辨率普遍较高,如果缩放系数下限太低,灯头会被压到 4 像素以下,标注框可能小于 1 个像素,成为无法学习的噪声。
| 输入尺寸 | 最小灯头像素 | 能否有效学习 |
|---|---|---|
| 640 | 约 8 | 可以 |
| 480 | 约 6 | 勉强 |
| 320 | 约 4 | 不建议 |
实际训练时我会在 data/hyps/hyp.scratch-low.yaml 中调整 scale 相关参数,同时把随机裁剪的比例限制在 0.8 倍以上,避免交通灯在训练过程中频繁丢失。
# hyp.scratch-low.yaml 部分参数(修改后) scale: 0.9 # 从默认 0.5 提高到 0.9 hsv_h: 0.015 # 交通灯颜色敏感,不宜过度扰动色相 hsv_s: 0.5 # 饱和度扰动保持中等hsv_h 降到 0.015 是为了防止红绿黄三色在增强时互相混淆。城市交通灯的颜色集中在固定色相区间,过强的色相抖动会让红灯变成黄灯,干扰模型对颜色语义的建模。
3. 网络结构调整:在 YOLOv5 骨架上扩展检测层
3.1 增加 P2 层的收益与代价
默认的 P3/P4/P5 层级对极小目标覆盖不足,常见的多尺度改造是在原结构上增加一个 P2 输出层。P2 对应 1/4 特征图,在 640 输入下每个网格代表原图 4×4 像素,可以覆盖 4 到 16 像素的极小目标。
改造模型配置文件的主要步骤包括:
- 复制 YOLOv5 的 backbone,在第 2 个 C3 模块之后引出 P2 特征。
- 在 head 部分增加一个上采样/拼接分支,让 P2 特征经过一次融合后进入检测头。
- 更新 anchors 数组,为 P2 层分配与它分辨率匹配的小尺寸锚框。
- 修改检测头的 nc 类别数和 ch 通道数。
# yolov5s_traffic.yaml 中 head 部分的关键改动 head: - [8, 1, Conv, [256, 3, 1]] - [14, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 6], 1, Concat, [1]] # 拼接 P3 之前的特征 - [C3, [256, False]] # P2 检测头 - [-2, 1, Conv, [128, 1, 1]] - [-1, 1, nn.Upsample, [None, 2, 'nearest']] - [[-1, 2], 1, Concat, [1]] # 拼接 backbone 浅层特征 - [C3, [128, False]] - [17, 1, Detect, [nc, anchors, [64, 128, 256]]]这段配置中[[-1, 2], 1, Concat, [1]]里的2表示从 backbone 的第 2 层引出浅层特征,这个索引值需要根据实际 backbone 结构对应调整。加完 P2 之后,检测层从 3 个变成 4 个,计算量增加约 40%,显存占用也会同步上升。
P2 层对交通灯这类小目标召回提升非常明显,但代价是把大量背景纹理引入检测空间。尤其在树木阴影、车窗反光、广告牌上的红色圆形标志附近,模型容易把类似颜色和形状的像素组合误判为交通灯。缓解手段有两种:一是在损失函数中调整 P2 层的正样本权重,二是依靠后处理 NMS 的置信度阈值过滤。
3.2 浅层特征语义不足的补偿策略
P2 来自网络的浅层,特征图分辨率高但感受野小,缺少上下文语义。交通灯检测极度依赖上下文:灯头本身是圆形、颜色鲜明,但要判断「这是一个交通灯」还是「一个红灯」,需要知道它是否挂在道路上方、是否有横杆支撑。增加 P2 层之后,必须让浅层特征具备一定的语义信息,否则误检很难压住。
常见做法是在 P2 分支的 C3 模块里增加注意力机制。不引入重模块的前提下,我会在 P2 的 C3 后加一个简单的通道注意力层,用全局平均池化生成通道权重,与原始特征相乘。这种轻量改造在 Jetson 等边缘设备上也能满足实时性要求。
class ChannelAttn(nn.Module): def __init__(self, channels, reduction=8): super().__init__() self.avg_pool = nn.AdaptiveAvgPool2d(1) self.fc = nn.Sequential( nn.Conv2d(channels, channels // reduction, 1, bias=False), nn.ReLU(), nn.Conv2d(channels // reduction, channels, 1, bias=False), nn.Sigmoid() ) def forward(self, x): b, c, _, _ = x.size() w = self.avg_pool(x).view(b, c) w = self.fc(w).view(b, c, 1, 1) return x * w.expand_as(x)这个注意力模块插入 P2 分支的 C3 输出之后、检测头之前。8 倍的通道压缩不会带来明显的速度损失,但能帮助模型聚焦发光灯珠区域,忽略大面积深色背景。如果数据集中红灯和绿灯的数量严重不平衡,注意力机制的稳定性会下滑,需要在损失函数中配合类别权重使用。
3.3 损失函数里的小目标权重调整
YOLOv5 的损失由 box、cls、obj 三部分组成。多尺度结构下,小目标在默认设置里贡献的梯度占比偏低,因为小目标产生的正样本锚框数量少。为了让 P2 层真正学到东西,需要在损失计算中按检测层分配权重。
YOLOv5 的实现中通常通过hyp['box']、hyp['cls']、hyp['cls_pw']调节全局权重,但要做到按尺度区分,需要修改损失函数中的权重系数。实践中可以在训练时用 TensorBoard 观察三个检测层的 obj loss,如果 P2 层的 obj loss 明显高于 P3/P4,说明小目标没有被正确学习,此时:
# 训练时开启日志观察各层损失 python train.py --data dataset.yaml --cfg models/yolov5s_traffic.yaml \ --weights yolov5s.pt --batch-size 16 --epochs 100 \ --log-imgs 8 --cos-lr观察到的val/obj_loss是各层平均还是合并值,取决于版本实现。更直接的做法是别在损失函数里做太多定制,先把 P2 的锚框匹配调准。很多场景下 P2 层学不好是因为锚框聚类时的目标尺寸范围没有按层切分干净,而不是损失权重不对。
4. 数据增强与训练配置:小目标场景的专属参数
4.1 Mosaic 增强的负作用
YOLOv5 的 Mosaic 增强把四张图拼接成一张,在常规目标检测中效果显著,但对交通灯场景需要谨慎。四张 1080p 城市道路图缩放到 640×640 后,单张图中交通灯可能已经缩到 3 像素。Mosaic 拼接还会破坏空间上下文:交通灯和道路、车辆的位置关系被切割重排,模型学到的是「一个亮色圆点」,而不是「道路上方悬挂的信号灯」。
我把 Mosaic 的概率从默认的 1.0 降到 0.5 左右,同时保留 MixUp 的轻微扰动。这个改动在小规模交通灯数据集上通常能降低 2-3 个百分点的误检率。
# hyp.scratch-low.yaml mosaic: 0.5 # 默认 1.0,降低后保留真实布局比例 mixup: 0.2 # 轻微混合增强 copy_paste: 0.3 # 如果类别包括倒计时数字牌,可以启用4.2 针对灯色的 HSV 增强边界
交通灯检测中,颜色是比形状更强的判别特征,每个交通灯类别几乎绑定一个固定颜色。典型的处理方式是极度压缩 HSV 色相扰动范围,但也不能完全关闭,因为不同城市、不同厂商的灯珠色温存在肉眼可见的差异。
一对合理的数值组合是 hsv_h 0.01、hsv_s 0.4、hsv_v 0.4。这个组合允许亮度和饱和度在合理范围内波动,基本保持色相不变。如果把 hsv_h 设为 0,模型会对日落后灯光泛白的场景非常脆弱;设得过高,绿色灯在训练集中可能被增强成灰色,造成类别混淆。
训练短帧视频时,需要额外加入亮度抖动。城市道路上有大量逆光、树影、夜间远光灯干扰,固定亮度的训练集在白天模型上部署夜间场景,召回率会有断崖式下降。
4.3 多尺度训练的尺度范围修正
在前面的小节中已经涉及 scale 参数的调整,训练配置里还有两个直接影响小目标效果的参数:anchor_t和iou_t。anchor_t控制锚框与真实框的长宽比阈值,默认是 4.0;iou_t是正样本匹配的 IoU 阈值,默认 0.2。
小目标本身的面积很小,即使锚框尺寸接近,由于特征图分辨率有限,IoU 天然偏低。YOLOv5 正样本分配时还依赖gr参数(格点比例),它决定真实框中心附近的网格有多少参与预测。对小目标场景,我会把anchor_t从 4.0 放宽到 5.0,增加正样本锚框数量。
# train.py 中可选的参数覆盖示例 parser.add_argument('--anchor-t', type=float, default=5.0, help='anchor matching threshold, higher = more matches')放宽anchor_t后,负样本数量相对减少,模型收敛速度会变快,但也容易把背景中的圆形物体纳入正样本,导致框输出不稳定。建议配合更严格的置信度阈值做推理,或训练结束后再观察各类别的 PR 曲线决定是否回退。
注意:anchor_t放宽后,如果训练 loss 曲线震荡严重,首先检查是不是真实框标注有大量超出边界的矩形。交通灯数据集中很多标注框接近图片上边缘,裁剪时容易丢失,YOLOv5 对这类样本会直接丢弃标注,导致有效正样本数不足。
4.4 类别不平衡的采样处理
交通灯数据集中,红灯和绿灯往往远多于黄灯和左转箭头灯。黄灯因为亮灯时间短,在视频抽帧中天然稀少;箭头灯又常被框进同一个标注框里,不单独标注。
处理这个问题的落地顺序是:先收集数据,不行再做重采样,最后才改损失权重。对静态图片数据,可以按类别数量进行加权采样,保证每个 epoch 中黄灯样本的重复出现概率更高;对视频帧序列,常见做法是定时抽帧而非连续抽帧,避免相邻帧标注几乎相同导致模型对角度和遮挡鲁棒性不足。
5. 部署阶段的推理优化:输入尺寸、NMS 与 TensorRT 加速
5.1 推理尺寸的选择逻辑
训练时用 640×640 输入,不代表部署推理时也用同样尺寸。交通灯场景需要同时检测远距离小目标和近距离大目标,固定输入尺寸对两种目标都不友好。我一般会保留训练时的多尺度能力,但在推理时按图像分辨率做一次预处理判断:高分辨率输入(1080p 以上)采用 832×832 或 960×960 推理;720p 以下采用 640×640。
def select_infer_size(img_size, target_ratio=0.8): """根据输入图像分辨率选择推理尺寸""" h, w = img_size max_dim = max(h, w) if max_dim >= 1920: return 960 elif max_dim >= 1280: return 832 else: return 640把输入从 640 提高到 960 后,小目标的特征像素数量约增加 2 倍,P2 层的有效检测能力被显著释放。代价是推理耗时非线性上涨,因为特征图尺寸增大后,FPN/PAN 中的上采样和拼接操作都有额外开销。
5.2 NMS 参数对不同尺度的差异化调整
YOLOv5 推理时的 NMS 有两个核心参数:conf_thres和iou_thres。在交通灯场景里,这两个参数需要针对多尺度带来的误检做特殊处理。
当模型包含 P2 层后,小目标检测框数量大幅增加,高置信度的背景误检也会变多。常见做法是把conf_thres从默认的 0.25 提高到 0.35 或 0.4;但如果你部署的算力比较紧张,可以换一个思路,保持 0.3 的置信度阈值,把iou_thres从 0.45 调低到 0.4。
# 使用 detect.py 推理时传入参数 python detect.py --weights runs/train/exp/weights/best.pt \ --source test_videos/ --conf-thres 0.35 --iou-thres 0.40 \ --img 960 --device 0iou_thres降低会让重叠框更容易被合并消除,但假设同一路口有两个距离很近的交通灯同时存在,过低的 IoU 阈值会把其中一个框合并掉,导致漏检。多尺度模型的输出框往往比单尺度模型更发散,所以这里要按实际视频结果反复调整,没有一套通用最优值。
5.3 TensorRT 部署时的尺度抖动处理
YOLOv5 官方提供了export.py导出的 TensorRT 引擎,但在多尺度模型上直接导出容易遇到一个隐蔽问题:固定输入尺寸的引擎在推理时对长宽比不是 1:1 的图像会做 letterbox 填充,填充区域过大时,原本的小目标会进一步缩小。
解决这个问题的方法是把推理尺寸固定为 1280×736 这类与常见视频分辨率等比缩放后的尺寸,而不是直接用 640×640 或 960×960。这样 letterbox 的填充面积最小,小目标信息损失也最小。
# 导出为固定宽高比的 TensorRT 引擎 python export.py --weights runs/train/exp/weights/best.pt \ --img 1280 736 --batch-size 1 --device 0 \ --include engine --half导出后建议用同一段视频对比 PyTorch 模型和 TensorRT 引擎的检测框差异。由于 TensorRT 的 FP16 精度可能在低像素区域产生数值误差,我通常会在 Tiny 目标上做一次置信度分布对比;如果 P2 层输出的低置信度检测框差异超过 10%,考虑改回 FP32。
6. 验证多尺度改造成果的 3 个维度
多尺度模型的最终效果不能只看 mAP,要同时关注小目标召回率、误检率和部署稳定性这三个维度。一个实际可行的方法是先做单层消融实验,把 P2 分支单独开关对比训练结果,确认性能提升确实来自新尺度特征,而不是因为增加了参数量。
具体操作时,我一般会从模型测试集中按标注框大小切出三个子集:小于 16 像素、16 到 48 像素、大于 48 像素,分别计算 AP。如果小于 16 像素的 AP 没有明显提升,先怀疑数据增强中的随机裁剪把太多小目标切掉了;如果 16 到 48 像素区间的 AP 反而下降了,需要检查 P3 层锚框分配是否受到了 P2 层挤压。
推理阶段还有一个快速自检办法:对比模型对同一段路况视频里「红灯变绿灯」瞬间的响应帧数。好的多尺度模型能在 2 到 3 帧内稳定切换检测结果,而弱模型往往在目标亮度变化时丢失 5 到 10 帧。这是因为交通灯在切换瞬间存在短暂暗屏期,多尺度模型可以利用更大感受野的上下文特征维持输出稳定。
最后一件事值得记住:多尺度改造不是只加一个 P2 层就结束了。锚框聚类、数据增强、推理尺寸和 NMS 参数是互相绑定的,改了一个就必须重新审视另外三个。交通灯检测的难点从来不是模型表达能力不足,而是小目标的信号强度与环境噪声太接近。多尺度结构提供的不只是更多像素,而是让模型有机会把「这个亮色圆点」和「这条路口的信号灯」两件事分开来判断。
本文还有配套的精品资源,点击获取