交通灯识别系统实战:小目标检测与颜色分类的工程化方案

发布时间:2026/10/27 11:19:18
交通灯识别系统实战:小目标检测与颜色分类的工程化方案

1. 项目缘起与整体设计思路拆解

交通灯识别这个题目,每年毕业季都会被大量同学选中,但真正能跑通、能讲清楚、能在答辩现场扛住追问的并不多。我前后帮几个同学看过这类系统的代码和论文,发现一个共性现象:大部分人把精力全砸在"训练一个能出结果的模型"上,却忽略了交通灯识别这个任务本身的特殊性——小目标、强光照变化、类别不均衡、实时性约束,这四个坑任何一个没处理好,模型在演示视频里看着还行,换个场景就原形毕露。这篇内容就把我踩过的坑和验证过的方案完整摊开讲,从任务定义、数据构建、模型选型到训练调优和部署推理,给一套可以直接照着复现的路线。

先说清楚这个系统到底要解决什么问题。交通灯识别在自动驾驶和辅助驾驶的感知链路里属于"语义级目标检测"的一个分支,它不只是要框出画面里有没有交通灯,还要判断这个灯当前是什么颜色状态——红灯、绿灯、黄灯,有些场景还要区分方向箭头灯、倒计时灯。和通用目标检测相比,它的难点集中在几个方面:交通灯在整幅图像里占比极小,远距离时可能只有十几个像素;颜色判断高度依赖光照条件,逆光、夜间、雨雾天都会让颜色特征漂移;真实路况下红灯和绿灯的样本比例严重失衡;车载场景对推理延迟有硬约束,模型不能太重。

所以整个系统的设计思路必须围绕"小目标 + 颜色分类 + 实时性"这三个关键词展开,而不是简单套一个通用检测框架就完事。我在方案选型时遵循一个原则:先保证检测召回率,再优化分类准确率,最后压缩推理耗时。这个顺序很重要,因为交通灯漏检的代价远高于误检,一个没被检测到的红灯可能直接导致决策失误,而多框出几个候选区域后续还能靠分类器过滤。下面这张表是我对比过的几种主流路线,也是我建议你在论文里作为方案论证部分的核心内容。

方案路线检测精度小目标表现推理速度实现难度适用场景
两阶段检测器(Faster R-CNN 类)高较好慢中离线分析、精度优先
单阶段检测器(YOLO 系列)中高一般快低实时检测主流选择
单阶段 + 特征金字塔增强高好较快中本项目推荐
检测 + 独立分类两段式高好中中高颜色判断要求高时

我最终推荐的是"单阶段检测器 + 特征金字塔增强 + 检测头直接输出颜色类别"的一体化方案。理由很直接:两阶段检测器精度虽好但速度撑不住实时场景,而把检测和颜色分类拆成两个模型虽然每部分都能做得更精,但工程复杂度翻倍,答辩时也不好讲清楚端到端的逻辑。一体化方案在检测头直接输出"红灯/绿灯/黄灯/背景"的类别概率,训练和推理都是一条链路,代码量可控,复现门槛低,对毕业设计这个体量来说是最优解。

这里要特别提醒一个认知误区:很多同学一上来就追求 mAP 刷到很高,但交通灯任务里 mAP 这个指标会被大量简单样本稀释,真正该盯的是小目标召回率和颜色分类混淆矩阵。我在实际调试中发现,一个 mAP 0.85 的模型可能在小目标上的召回只有 0.6,而另一个 mAP 0.78 的模型小目标召回能到 0.8,后者在实际演示中明显更稳。所以从项目一开始就要把评估体系建对,别等到最后才发现指标好看但效果不行。

2. 数据集构建与预处理的核心细节

2.1 数据来源与标注策略

交通灯数据集的构建是整个项目里最耗时也最容易被低估的环节。公开数据集里有一些带交通灯标注的街景数据,但类别定义、标注粒度往往和你的需求对不上,直接拿来用会出现"标注框只框了灯体没框灯罩""颜色类别缺失"这类问题。我的建议是公开数据打底 + 自采数据补充的组合策略:先用公开数据把模型的基本检测能力训起来,再针对自己演示场景补充几百张针对性样本做微调。

标注环节有几个实操要点必须说清楚。第一,标注框的粒度要统一,是框整个灯组还是只框单个灯头,这个必须在标注规范里写死,否则同一个数据集里两种粒度混在一起,模型学出来的框大小会非常混乱。我一般建议只框当前点亮的那个灯头,因为颜色判断针对的就是点亮状态,框整个灯组反而引入无关区域。第二,对于远距离小目标,标注时要把图像放大到足够倍数再框,肉眼在原始分辨率下框小目标误差极大,一个像素的偏差在训练时会被放大。第三,类别定义要预留"未知/遮挡"这一类,实际路况里被树枝、广告牌部分遮挡的交通灯很常见,强行标成红绿黄会污染训练信号。

标注工具用什么都行,关键是导出格式要统一成你训练框架能直接吃的格式。我习惯统一转成 COCO 格式的 json,因为它的字段结构清晰,转 YOLO 格式也就一个脚本的事。这里给一段我常用的格式转换核心逻辑,把 COCO 的 bbox 转成归一化的中心点加宽高:

import json def coco_to_yolo(coco_json_path, output_txt_path, img_w, img_h): with open(coco_json_path, 'r') as f: data = json.load(f) # 建立 image_id 到文件名的映射 img_map = {img['id']: img['file_name'] for img in data['images']} lines = [] for ann in data['annotations']: x, y, w, h = ann['bbox'] # COCO 是左上角坐标,YOLO 需要中心点归一化 cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h nw = w / img_w nh = h / img_h cls_id = ann['category_id'] - 1 # 类别从 0 开始 lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}") with open(output_txt_path, 'w') as f: f.write("\n".join(lines))

这段代码看着简单,但有个坑:COCO 的 bbox 是左上角加宽高,而 YOLO 要的是中心点加宽高再归一化,转换时如果忘了除以图像宽高,训练时框会全部跑到图像外面去。我第一次做的时候就栽在这,loss 一直不降,排查了半天才发现是归一化漏了。

2.2 数据增强的针对性设计

通用数据增强那套随机裁剪、翻转、色彩抖动,在交通灯任务里不能无脑套用。随机裁剪对小目标极不友好,本来交通灯就小,一裁可能直接裁没了;水平翻转对交通灯本身没问题,但如果你的场景里有方向箭头灯,翻转后箭头方向就错了,颜色对但语义错。所以增强策略必须按任务特性定制。

我实际用下来效果最好的几类增强是:亮度与对比度的随机扰动(模拟不同时段光照)、高斯噪声注入(模拟传感器噪声和雨雾)、小范围随机缩放(模拟远近变化)、以及马赛克增强(把四张图拼成一张,天然增加小目标密度)。马赛克增强对小目标检测的提升非常明显,因为它让模型在一张图里同时看到多个尺度的交通灯,相当于免费扩充了多尺度样本。但要注意马赛克增强在训练后期要关掉,否则模型会过度适应拼接图像的分布,最后几个 epoch 用原始图像微调一下效果更稳。

色彩空间的增强要格外小心。交通灯的核心判别特征是颜色,如果你做大幅度的色相偏移,红灯可能被偏移成橙色甚至偏绿,这就把标签和视觉特征搞矛盾了。我的做法是色相偏移幅度控制在很小范围,主要动亮度和饱和度,这样既增加了光照鲁棒性,又不会破坏颜色语义。这个细节在论文里可以作为"针对任务特性的增强设计"来写,是个加分点。

2.3 类别不均衡的处理

真实数据里红灯和绿灯样本多,黄灯样本少得可怜,因为黄灯持续时间短,采集到的概率低。直接训练会导致模型对黄灯的召回极差。处理这个问题有三条路:过采样黄灯样本、在损失函数里给黄灯更高权重、或者用 focal loss 这类能自动调节难易样本权重的损失。我一般组合使用:先对黄灯做适度过采样(复制加轻微增强,别原样复制否则过拟合),再在分类损失里给黄灯 1.5 到 2 倍的权重。

这里有个经验值可以参考:假设你的数据集里红灯 5000 个、绿灯 4500 个、黄灯 800 个,那么黄灯的损失权重可以设成 (5000+4500)/(2*800) 约等于 6,但这个值太激进会让模型过度偏向黄灯导致误检率飙升。实际调的时候我从 2 倍开始试,观察验证集上黄灯召回和整体误检的平衡点,一般 1.5 到 2.5 倍之间就能拿到不错的折中。这个调参过程建议记录在实验日志里,答辩时被问到"你怎么处理类别不均衡"就能拿出具体数据说话。

3. 模型结构与关键模块实现

3.1 骨干网络与特征金字塔的选择

骨干网络的选择要在精度和速度之间找平衡。太重的骨干(比如大深度的残差网络)在嵌入式设备上跑不动,太轻的骨干(比如极简的移动端网络)小目标特征提取能力不足。我实测下来,中等深度的轻量骨干配合一个设计良好的特征金字塔,是性价比最高的组合。特征金字塔的作用是把深层的语义特征和浅层的高分辨率特征融合起来,交通灯这种小目标恰恰需要浅层的高分辨率信息来定位,同时需要深层的语义信息来判断"这是不是交通灯"。

特征金字塔的层数不是越多越好。我试过五层融合,结果参数量上去了,小目标召回只提升了一点点,推理速度却掉了不少。三层融合(对应三个尺度的检测头)是甜点区,分别负责大、中、小目标的检测,交通灯主要落在最小那个尺度上。这里有个关键细节:最小尺度的检测头对应的特征图分辨率要足够高,如果下采样太狠,一个十几像素的交通灯在特征图上就剩一两个像素,根本没法检测。所以输入分辨率不能太低,我一般把输入设成 640 或更高,保证小目标在最小特征图上还有足够的空间信息。

3.2 检测头与颜色分类的融合设计

检测头这块是整个模型的核心。传统做法是检测头只输出"目标/背景"和框回归,颜色分类另起一个分支。我采用的是一体化设计,检测头直接输出每个锚点位置上各类别的概率,类别包括红灯、绿灯、黄灯、背景。这样做的好处是检测和分类共享特征,颜色判断能用上检测阶段提取的语义信息,端到端训练也更简单。

但一体化设计有个隐患:颜色分类对小目标来说很难,因为小目标在特征图上的感受野有限,颜色信息可能被背景稀释。我的解决办法是在检测头前面加一个轻量的注意力模块,让模型学会聚焦在灯体区域而不是周围的灯罩和背景。注意力模块不用搞太复杂,一个通道注意力加一个空间注意力就够了,参数量增加很少但效果提升明显。实测下来,加了注意力之后小目标的颜色分类准确率能提升五到八个百分点,这个提升在答辩演示时是肉眼可见的。

锚点设计也要针对交通灯调整。通用检测器的默认锚点尺寸是按通用目标统计的,交通灯普遍偏小偏瘦长(竖直方向),所以锚点尺寸要往小和瘦长的方向调。我一般用聚类的方法在自己的数据集上统计真实框的宽高分布,取几个代表性的尺寸作为锚点初始值,比用默认值收敛快很多。这个聚类过程用简单的 k-means 就行,把数据集中所有标注框的宽高拿出来聚成六到九类,每类取中心作为锚点。

3.3 损失函数配置

损失函数由三部分组成:框回归损失、目标置信度损失、类别分类损失。框回归我用的是带 IoU 的损失,它对小目标的定位更敏感,因为小目标对位置偏差的容忍度低,普通的位置损失在小目标上梯度信号太弱。置信度损失和分类损失用带焦点机制的交叉熵,能自动降低简单样本的权重,让模型更关注难样本。

这里要强调一个实操细节:三部分损失的权重配比直接影响训练稳定性。框回归损失权重太低会导致定位不准,太高又会让分类学不好。我常用的初始配比是框回归 : 置信度 : 分类 = 5 : 1 : 1,然后根据训练曲线微调。如果发现训练前期框回归 loss 下降很慢而分类 loss 已经很低,说明框回归权重不够,适当调高;反之如果框回归收敛了但分类一直上不去,就调高分类权重。这个调参没有万能公式,得看你的数据分布,但上面这个起点能让你少走弯路。

4. 训练流程与调优实操

4.1 训练环境与超参数设置

训练环境这块,有独立显卡最好,没有的话用云端算力也行,但要注意数据上传和模型下载的耗时。我建议在本地用小规模数据先把整个训练流程跑通,确认没有代码 bug 再上大规模数据,否则在云端跑几个小时才发现数据路径写错了,时间全浪费了。

超参数设置有几个关键项。学习率我用带预热的余弦退火策略,初始学习率设在千分之一到千分之三之间,预热几个 epoch 让模型先稳定下来再进入正常学习节奏。批量大小受显存限制,能开大就开大,批量太小会导致训练不稳定,如果显存不够可以用梯度累积来模拟大批量。训练轮数一般设一百到两百轮,配合早停机制,验证集指标连续若干轮不提升就停,避免过拟合。

下面这张表是我在某次复现实验里记录的典型超参数配置,可以直接作为起点:

超参数取值说明
初始学习率0.001配合余弦退火
预热轮数3线性升温到初始学习率
批量大小16显存不足时用梯度累积
训练轮数150配合早停
权重衰减0.0005抑制过拟合
输入分辨率640×640兼顾小目标与速度
框回归损失权重5.0定位优先
分类损失权重1.0基础值

4.2 训练过程监控与调优

训练过程中要盯的曲线不只是总 loss,还要分开看框回归 loss、置信度 loss、分类 loss 三条线。如果三条线同步下降然后趋于平稳,说明训练健康;如果某条线震荡剧烈,通常是学习率太大或者批量太小;如果某条线早早平稳而其他还在降,说明该部分的权重设置可能不合理。

验证集的评估要分项做,不能只看一个总指标。我一般会记录:整体 mAP、小目标(面积小于某个阈值)的召回率、各类别的精确率和召回率、颜色分类的混淆矩阵。混淆矩阵特别有用,能直接看出模型是把红灯认成绿灯了还是把黄灯漏了。我遇到过模型整体指标不错但混淆矩阵显示红灯和橙色的路灯经常混,后来加了针对性样本才解决。

还有一个容易被忽略的点:训练集和验证集的场景分布要尽量一致。如果训练集全是白天晴天,验证集里混了一堆夜间雨天,指标会很难看,但这不代表模型不行,而是分布偏移。毕业设计里如果自采数据有限,建议按场景分层划分训练验证集,保证两边场景类型比例接近,这样评估结果才可信。

4.3 迁移学习与微调策略

从零训练一个检测模型在毕业设计的算力条件下不太现实,迁移学习是必选项。用在大规模通用数据集上预训练过的权重做初始化,能大幅缩短收敛时间并提升最终精度。加载预训练权重时要注意:骨干网络部分可以全加载,检测头部分因为类别数和任务不同,一般只加载结构不加载权重,或者加载后冻结几轮再解冻。

微调策略我推荐分阶段:第一阶段冻结骨干只训检测头,让检测头先适应新任务;第二阶段解冻全部一起训,但学习率调低一个量级,避免把预训练学到的通用特征破坏掉。这个两阶段策略比一上来就全量微调稳定得多,尤其在小数据集上效果差异明显。我在一个只有两千张图的小数据集上对比过,两阶段微调的最终精度比直接全量微调高了将近四个点。

5. 推理部署与性能优化

5.1 推理流程与后处理

训练好的模型要落地成能演示的系统,推理流程得单独设计。完整链路是:读入图像或视频帧、预处理(缩放、归一化)、模型前向、后处理(解码框、非极大值抑制、置信度过滤)、输出结果。后处理里的非极大值抑制阈值要针对交通灯调,因为交通灯有时会密集出现(一个路口多个灯),阈值太高会把相邻的灯误抑制掉,太低又会留下大量重复框。我一般从 0.45 开始试,根据实际画面里灯的密集程度微调。

置信度阈值也不能一刀切。红灯的置信度阈值可以设高一点(比如 0.5),因为红灯误检代价大;黄灯可以设低一点(比如 0.3),宁可多报也别漏报。这种按类别设置不同阈值的做法在实际演示中效果更好,也体现了你对业务场景的理解,答辩时是个亮点。

5.2 推理加速手段

如果演示环境是普通笔记本没有独立显卡,推理速度可能是个问题。几个实用的加速手段:把模型导出成推理专用的格式,能获得明显的速度提升;输入分辨率适当降低(但要保证小目标还能检测到,这个要实测找平衡点);用半精度推理,速度能快不少且精度损失很小。我实测过同一模型在不同配置下的帧率差异,导出优化加半精度之后,帧率能翻倍,而检测结果肉眼几乎看不出差别。

还有一个工程上的小技巧:视频推理时不用每帧都跑完整模型,可以隔帧检测加简单的跟踪算法补间,这样平均帧率能再提一截。交通灯状态在短时间内变化不大,隔帧检测完全够用。这个思路在论文里可以作为"工程优化"部分来写,显得你有实际落地意识。

6. 常见问题与排查实录

做这类项目,遇到的问题五花八门,我把高频出现的整理成速查表,方便你对号入座。

现象可能原因排查与解决
loss 不下降或震荡学习率过大、数据标签错误、归一化遗漏降学习率、抽查标注、检查坐标归一化
小目标召回极低输入分辨率太低、锚点尺寸不匹配、增强裁剪过狠提高分辨率、重聚类锚点、关闭随机裁剪
颜色分类混淆严重色相增强过度、类别不均衡、注意力缺失收敛色相增强、调类别权重、加注意力模块
验证集指标远低于训练集过拟合、场景分布偏移加正则、分层划分数据、补充多样场景
推理帧率不达标模型过重、未做推理优化、分辨率过高换轻量骨干、导出优化格式、降分辨率
相邻交通灯被误抑制非极大值抑制阈值过高调低阈值并实测密集场景

除了表里的,我再补充几个亲身踩过的坑。有一次模型训练一切正常,但推理时框全部偏移,查了半天发现是预处理阶段的归一化均值和训练时用的不一致,训练用了一组均值方差,推理代码里手写成了另一组,这种低级错误特别隐蔽,建议把预处理参数写成配置文件,训练和推理共用同一份。还有一次黄灯召回怎么调都上不去,最后发现是标注时把一些偏橙色的灯标成了红灯,标签本身就有噪声,清洗标注后问题自然解决。所以遇到指标异常,先怀疑数据,再怀疑代码,最后才怀疑模型结构,这个排查顺序能帮你省大量时间。

7. 答辩演示与论文写作的实操建议

技术做完了,怎么讲清楚同样重要。答辩演示我建议准备两套素材:一套是静态图片的检测结果对比(原图、检测框、类别标签),另一套是视频实时检测的录屏。静态图用来展示精度细节,视频用来展示实时性和稳定性。演示时故意放几个难例——逆光、夜间、远距离小目标——然后展示模型依然能检出,比只放简单场景有说服力得多。

论文写作上,方案论证部分要把"为什么选这个方案"讲透,用对比表格呈现不同路线的取舍,体现你的思考过程而不是简单罗列。实验部分要有消融实验,比如去掉注意力模块指标掉多少、去掉马赛克增强掉多少,这种对比能直接证明每个设计决策的价值。指标分析不要只报好数字,把失败案例也放上去分析原因,反而显得你做得扎实。

我个人在实际操作中的体会是,这类项目的成败往往不在模型本身,而在数据质量和评估体系。模型结构网上有大量成熟方案可以借鉴,但针对交通灯这个具体任务的数据处理和评估设计,才是真正拉开差距的地方。把数据清洗干净、把评估指标建对、把难例分析做透,哪怕模型结构朴素一点,最终效果和答辩表现都不会差。后续如果还想扩展,可以往多帧时序融合方向做,利用交通灯状态的时间连续性来进一步降低误检,这是个很有价值的延伸点。