国内交通信号灯检测实战:从数据集构建到YOLO部署全解析

发布时间:2026/10/22 7:50:27
国内交通信号灯检测实战:从数据集构建到YOLO部署全解析 先说结论做交通信号检测最大的瓶颈不是模型是数据集去年年中我接手了一个路侧感知的项目核心需求很朴素在十字路口用摄像头识别红绿灯状态辅助车辆决策。模型方案我几乎没有犹豫就选了YOLO——单阶段检测器速度足够跑实时精度经过这么多次迭代早就够用。真正让我耗了两个多月的是数据集。网上能下载的公开数据集基本都是欧美路口的场景红绿灯挂在横杆上、箭头灯排列整齐、背景干净拿到国内实际路口一试问题全冒出来信号灯装在路边立杆上、和树木电线杆重叠、倒计时数字和灯体粘连、黄灯闪起来能连闪好几帧……模型在公开集上mAP再高落到自家路口就是漏检。所以我今天想聊的重点不是怎么调参而是从数据集角度完整拆一遍“国内交通信号检测”这个项目从0到1的落地过程。包括数据是怎么采的、怎么标注才能少返工、YOLO的配置和训练参数怎么定、以及小目标、夜间曝光、灯色混淆这些坑是怎么一个一个踩平的。如果你正准备做类似的视觉检测项目或者手里已有YOLO模型但想换到国内交通场景这篇应该能帮你省下几周试错时间。1. 项目整体设计为什么YOLO国内自建数据集是必须的组合1.1 国内信号灯场景到底特殊在哪先泼一盆冷水现有公开数据集和国内真实路口的差异大到能直接毁掉一个模型。国外常见路口是悬臂式红绿灯灯组整体悬挂在路口正上方视野开阔国内城市路口则是五花八门——灯杆在右侧人行道边、左转灯放在对向位置、灯组周围有广告牌和绿植遮挡更别提有些老旧路口还是老的“横式”排列第一眼根本分不清哪个是左转。这种差异意味着什么数据分布不匹配。你用美国街道上拍的照片训练模型模型学的特征是“大块亮色圆形区域挂在横杆上”到了国内路口灯体小、位置偏、背景杂模型自然判不对。统计学里这叫训练集与测试集分布不一致放到工程上就是线上召回率断崖下跌。所以要解决国内场景就必须有国内道路数据的“地基”。这也是我优先把工程量压在数据侧的原因——模型是算法数据是粮草粮草不对算法再先进也只是在错误的方向上加速。1.2 YOLO在这里解决了什么问题交通信号检测有两个硬指标实时性和准确性。红绿灯状态是连续变化的车辆决策系统需要拿到当前帧的状态判断“能不能走”延迟每增加100毫秒都可能带来隐患。YOLO这类单阶段检测器把目标分类和框回归统一在一个网络里一次前向完成以我用的YOLOv8s为例TensorRT FP16下推理延迟能压到10ms级别完全满足实时要求。当然两阶段检测器比如Faster R-CNN精度可能更高但推理速度通常慢一倍以上。在路侧摄像头这种场景里检测目标数量不多、类别也少红、绿、黄、左转红基本就这几个YOLO的精度劣势已经被数据量和训练技巧补回来了速度优势却是两阶段方案给不了的。这就是选型的核心逻辑在特定任务里工程最佳不一定是学术最优。1.3 项目链路总览梳理一下整个项目的流转路径数据采集行车记录仪视频、路侧摄像头视频、手机手持拍摄覆盖多个时段路段。数据清洗与抽帧去重、去模糊、平衡场景分布。标注使用开源标注工具规范类别和框选规则。预处理与增强统一尺寸、色彩增强、模拟夜间场景。训练与调优划分训练验证集配置YOLO训练参数迭代评估。部署导出ONNX/TensorRT接入实际路口测试。在这个链路里数据采集和标注占了我大约七成的时间。这一点必须开宗明义讲清楚不要指望模型能弥补数据的缺陷前期多花的时间后期都会在精度上赚回来。2. 数据采集与预处理从零攒出一套能用的国内交通信号集2.1 采集渠道怎么找性价比最高数据来源不一定要自己扛着相机去路口蹲一天。我实际用的组合是三个方向并行铺开。第一公开行车记录仪视频。B站、抖音上大量长距离路跑视频城市道路全景多、镜头稳定能一次性覆盖几十个路口。下载后按路段切分作为基础底料。第二自己架设拍摄。找几个典型的十字路口用手机固定在三脚架上分别在工作日早晚高峰、夜间、阴雨天各拍30分钟。手机需要设置成固定焦点和固定曝光避免自动对焦乱跳导致连续帧画面抖动后期还得裁剪。第三第三方开源数据集。一些高校公开的交通目标检测数据集里会附带国内信号灯图片可以作为补充。但要注意两点一是图像版权授权范围不能超出研究用途二是旧数据集的分辨率普遍偏低通常仅640x480而现代YOLO在640输入下需要目标占据足够像素。旧图里的灯体可能只有十几个像素模型很难学到有效特征这类数据只能少量混入不能当主力。我的最终配比大概是视频抽帧60%、自行拍摄30%、旧开源集10%。这个配比不是拍脑袋定的而是根据训练后各类样本的AP值调整过——旧图补进来的那10%主要是为了增加亮度方差让模型对老旧信号灯泛化更好。2.2 抽帧与清洗别把同一帧扔进去训练十次视频抽帧很容易犯一个错误连续帧之间几乎一样模型看到的是同一场景的重复变体。训练集和验证集如果时间上重叠会出现“验证集泄漏”最终指标虚高一上真实路口马上现原形。我的做法是抽帧间隔不小于15帧0.5秒30fps并且把同一段视频的帧全部归入同一数据子集——要么训练集要么验证集不能混。具体操作我写了段小脚本辅助处理import cv2, os, random random.seed(42) def extract_frames(video_path, out_dir, interval15, target_size1280): cap cv2.VideoCapture(video_path) frame_id 0 saved 0 while True: ret, frame cap.read() if not ret: break if frame_id % interval 0: h, w frame.shape[:2] if w target_size: scale target_size / w new_size (target_size, int(h * scale)) frame cv2.resize(frame, new_size, interpolationcv2.INTER_AREA) # 剔除过亮或过暗帧 gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) brightness gray.mean() if 30 brightness 220: cv2.imwrite(os.path.join(out_dir, fframe_{frame_id:06d}.jpg), frame, [cv2.IMWRITE_JPEG_QUALITY, 90]) saved 1 frame_id 1 cap.release() print(fsaved {saved} frames from {video_path})上述脚本做了三件事间隔抽帧、统一长边到1280、剔除亮度异常帧。1280是一个权衡——太大会让标注和训练显存开销倍增太小则小目标信号灯像素不足。1280保证灯体在图像中至少占20-30像素给后续缩放到640训练留出余量。2.3 数据清洗的血泪经验抽完帧之后必须人工过一遍。我统计过原始帧里大概12%是彻底不能用的主要集中在三种情况对焦模糊灯体边缘像棉花一样晕开这种框出来只会教坏模型。遮挡超过50%的灯体比如正好被一辆大货车完全挡住。如果页面上同时有另一个同类灯可见可以保留做难度样本只有一个灯且被完全遮挡直接删。镜头污渍反光画面中心一团光晕盖在灯位上。清洗别嫌枯燥这一步决定了下游标注的人力是否白费。我的做法是写一个快速筛查界面按左右方向键翻页按D删除坏图每天过5000张三天内清完一个基础数据集效率比用图片浏览器高很多。3. 标注规范与增强策略数据质量的小细节才是精度的大变量3.1 类别的设计比你想象得更讲究交通信号检测不是简单标“红、绿、黄”就行得结合下游需求。比如自动驾驶决策需要区分“直行红绿灯”和“左转箭头灯”因为它们的意义不同——直行红灯不能右转除非另有规定左转箭头红灯却允许直行这里不严谨不行。我的项目只做状态识别所以类别这样定义red圆形/整体红灯green圆形/整体绿灯yellow黄灯闪烁或常亮arrow_red红色箭头灯左转/右转arrow_green绿色箭头灯为什么要单独拆出箭头灯因为箭头灯的视觉特征和圆形灯差别很大尤其黄灯亮起时箭头的黄色条纹是斜向的和圆形黄色区域完全不同。如果不拆分模型会认为它们必须长得一样结果两头都学不好。如果你还要做倒计时识别建议单独加一个countdown类别把数字区域也框出来。倒计时的数字是七段码或点阵特征独立需要单独的数据量。3.2 标注框怎么打才标准我们用的是YOLO格式类别编号 归一化中心坐标 归一化宽高。标注工具有很多我试过LabelImg和X-AnyLabeling最后长期用X-AnyLabeling。原因是它支持自动保存、预标注模型辅助、图像增强预览效率比LabelImg高一截。标注框的边界规则是框体紧贴灯体外边缘不要包含灯杆或遮阳板。遮阳板会和灯体粘连框进去会引入大量背景噪声。如果同一根灯杆上有多个灯组每个灯组单独框如果一组灯里既有圆形红灯又有箭头红灯按实际类别分别框。当目标小于10像素时宁可删除也不要硬标。10像素以下的灯在640分辨率输入下只有1-2个像素学习不到任何特征还会成为矛盾梯度来源。一个容易忽略的点如果你用矩形框在红绿灯轮廓是圆形或箭头形的目标上矩形框会包含一些背景区域。这些背景区域里如果有树枝或灯光反射会让模型误以为颜色块应该出现在框内某个区域。所以标注时尽量让框裁剪得紧凑减少不必要的背景。3.3 数据增强模拟恶劣天气的“平行宇宙”国内交通的特点就是天气复杂。雾天、雨天、逆光、夜晚每种场景的视觉特征差异极大。数据增强是补充这些场景性价比最高的办法。YOLO自带的增强里我开了这些hsv_h: 0.015色调扰动小幅度避免色彩偏移。hsv_s: 0.5饱和度扰动模拟褪色信号灯。hsv_v: 0.4明度扰动模拟过曝和欠曝。degrees: 5.0旋转小角度模拟摄像头安装不水平。translate: 0.1平移扰动模拟车辆抖动。scale: 0.5缩放扰动让模型适应不同距离的灯体大小。fliplr: 0.5水平翻转。但我额外加了自己写的一组“夜间增强”策略。做法是从原始夜间帧里单独分出一部分用Gamma校正把图像压暗1.2档然后叠加高斯噪声让信号灯的红色或绿色在图像中的对比更强。这比全局加噪声更符合人眼看到的夜间情景——背景暗但灯体亮。增强不是越多越好。开花开着开着会改变灯体颜色分布黄色灯在强HSV扰动下可能变成橙色或绿色反而造成标签噪声。我最终把hsv_v控制在0.4以内只在白天数据上开高饱和度扰动夜间数据单独做亮度增强。4. 模型选型与训练配置用YOLOv8s在单卡上跑出可用精度4.1 为什么是YOLOv8s训练前需要定模型尺寸。YOLO系列从v5到v8到v11一路迭代各有拥趸。我的判断标准很简单社区生态成熟度、部署易用性、精度与速度平衡。v8s参数量约11M在RTX 3090上训练一个epoch只要三分钟batch16640输入推理速度在TensorRT下能到100 FPS以上。v8官方预训练权重在COCO上做过充分调优迁移到红绿灯场景只需要微调最后几层检测头收敛极快。不用v5的原因不是它不好而是Ultralytics对v5的更新维护已经放慢新出的性能优化基本都在v8/v11上。v11我后来也试过精度略高但官方配套的部署工具链还不算完善导出TensorRT时有一些版本兼容问题。工程上稳定第一所以我留在了v8s。如果你手头只有老款显卡v8n也是一个不错的轻量选择精度少掉两三个点但速度更快。4.2 数据集的目录结构与划分YOLO训练要求固定的目录结构。我记得最清晰的建议是把训练集和验证集放在同一个父目录下并用数据集yaml文件指定路径。datasets/ ├── traffic_signal/ │ ├── images/ │ │ ├── train/ │ │ │ ├── 000001.jpg │ │ │ ├── ... │ │ └── val/ │ │ ├── 100001.jpg │ │ └── ... │ ├── labels/ │ │ ├── train/ │ │ │ ├── 000001.txt │ │ │ └── ... │ │ └── val/ │ │ ├── 100001.txt │ │ └── ... │ └── data.yamldata.yaml内容path: ../datasets/traffic_signal train: images/train val: images/val names: 0: red 1: green 2: yellow 3: arrow_red 4: arrow_green生成label文件时我用了一个小脚本来从标注工具导出的JSON格式转成YOLO txt并在转换时做了归一化和坐标检查def convert(img_w, img_h, box): x_center (box[0] box[2] / 2) / img_w y_center (box[1] box[3] / 2) / img_h width box[2] / img_w height box[3] / img_h if not (0 x_center 1 and 0 y_center 1 and width 0 and height 0): return None # 异常框 return x_center, y_center, width, height注意超过图像边界的框不能直接截断最好是把框中心坐标限定在[0,1]内宽高同理。如果框完全在画外这帧就应该删掉。4.3 训练参数与多显卡注意事项我拿单张RTX 3090 24G显存做训练batch size可以稳妥开16。如果显存小就降batch同时按比例提高学习率不学习率不宜直接十倍增大以追求速度而是应保持相对稳定。以下是我最终用的胶水参数yolo detect train \ modelyolov8s.pt \ datatraffic_signal.yaml \ imgsz640 \ batch16 \ epochs100 \ optimizerAdamW \ lr00.001 \ lrf0.01 \ warmup_epochs3 \ cos_lrTrue \ patience10 \ save_period10 \ seed42 \ projectresults/sign_det \ nameexp1几个参数的解释都是过来人的心酸预训练权重选择yolov8s.pt而不是从零训练。COCO预训练模型已经学会了通用物体边缘和纹理特征用它的权重做初始化红绿灯训练相当于在巨石上雕刻而不是从泥巴里捏人收敛快而且不容易过拟合。warmup_epochs设成3。前期学习率太低如果warmup太短模型参数更新幅度过大容易把预训练学好的特征冲掉。cos_lrTrue配合lrf0.01做余弦退火可以让末期学习率平稳降到初始的1%在细粒度调优阶段帮助稳定收敛。patience10做早停。数据集不大的情况下通常40-60个epoch就已经收敛后面全是过拟合震荡留着等待没有意义。训练过程中需要盯两条曲线val_loss和mAP。如果train_loss持续下降而val_loss在30轮后反弹说明过拟合早停生效如果是mAP0.5一直提升但mAP0.5:0.95停滞这是小目标的问题得回到数据增强或分辨率上找办法单纯的训练跑不下去。4.4 评估指标怎么解读YOLO训练结束后会输出一堆指标重点看这些mAP0.5表示IoU阈值0.5下的平均精度红绿灯任务我要求0.95以上。mAP0.5:0.95IoU从0.5到0.95取均值更能体现框定位质量。交通信号灯一旦框偏移半个灯体状态判断就会错。所以这个指标不能低于0.75。precision/recall平衡状态。信号灯状态判断宁漏检recall低不误检precision低具体要看决策逻辑。我的场景是路侧感知要求尽量不误伤所以precision优先。你还会看到per-class AP列表。如果某个类别明显低于平均线比如arrow_green只有0.8多半是样本量不够或数据增强把它扭曲了。回去给这类多补充些真实图片比盲目加epoch管用。5. 常见问题与排查技巧实录小目标、夜间、颜色混淆的实战解法5.1 小目标信号灯漏检换切片推理比换模型更有用信号灯在路口画面中通常是很小的目标。1280长边图像里灯体大约长20-60像素缩放到640输入后只剩10-30像素。在YOLO的检测头上深层的特征图下采样32倍10像素的目标在最后几层可能只有不到1个像素的特征响应自然容易被漏掉。第一反应是切图推理。把原始1280图像切成四块960x960重叠50像素分别推理再合并框小目标在子图中的相对尺寸可以翻倍。我用SAHI这个框架做过切片配合YOLOv8s夜间远距离的召回率从0.72提到了0.88代价是推理耗时x3。在路侧场景硬件有富余这个代价可以接受。但要注意切片后可能出现同一灯被两个切片各检一半的情况需要使用NMS合并重叠框IOU阈值调到0.3。第二种方案是提高输入分辨率。直援imgsz960YOLO本身在推理时只需修改imgsz但训练时也要用相同尺寸才不损失。我试过960训练显存占用翻翻但精度提升有限。切图效果更直接因为保持了目标自身上下文。5.2 夜间过曝与低光的信号灯检测夜间信号灯检测反而好办——灯体本身是高亮自发光体背景全黑反而是极佳的对比度。真正的问题是镜头反光、路灯干扰、以及暗部噪点被误检成信号灯。我的处理是三步数据集里夜间图片占比尽量到30%。如果拍摄条件不允许就用灰度变换把白天图片压暗模拟夜间。推理端如果摄像头支持开启了HDR模式有效扩大动态范围让灯体轮廓更清晰避免全黑背景里红绿饱和度过高而溢出屏幕。在训练时给夜间图片单独开一组增强只做亮度轻微下调和高斯噪声叠加不做饱和度扰动防止绿色在低照度下被抹成灰色。实测下来夜间检测精度比白天更稳——因为背景干扰更少。真正难的反而是傍晚逆光太阳斜射在镜头上产生光晕灯体被罩住。这种场景通过手动增加“光晕模拟”增强在灯体附近叠加半透明白色光晕块让模型学会在光晕下辨认边缘。5.3 红灯和车尾灯混淆负样本是最有效的药红灯是纯圆形红色发光体汽车尾灯也是。除了位置不同两者在图像上长得非常像。如果训练数据里没有大量尾灯负样本模型就会把红色刹车灯当成红灯。负样本采集有一个野路子从夜间车流视频里截取大量包含车灯但无信号灯的帧直接作为“空标签”图片加入训练集。YOLO允许图片中没有任何目标对应标签它可以从这些反向样本中学习到“这里不该出框”。我在数据集中特意加了8%的无目标图片其中一半是深夜车流、一半是白天有红色广告牌的路口。加入负样本后误检率从每帧3.2个降到0.4个效果极其显著。5.4 倒计时和频闪干扰国内信号灯常伴有倒计时数字数字区域和灯体紧贴。模型有时候会把数字“9”识别成黄色信号灯。这是因为倒计时的数字是黄色LED小尺寸下和黄色圆形信号灯相似。我的策略是检测框紧贴灯体不包含数字区域标注时严格避开数字让模型学到的特征是“圆形区域”而不是“黄色点亮块”。另外在推理端加一个简单规则同一灯位在连续10帧内状态不能突变超过两次红直接跳绿除外用时序滤波清除频闪误判。5.5 模型剪枝与部署注意训练完成后导出TensorRT有一个坑默认fp16精度会导致红色信号灯在暗背景下颜色略微失真。模型在OpenCV和PyTorch里能正确识别到TensorRT中却漏检三个百分点。解决方法是把模型的输入均值方差归一化层融合到网络外部预处理里不要让TensorRT在数学优化时把颜色通道算歪。具体做法是关闭模型的归一化改用预处理代码传入归一化后的float张量。这个细节我在部署调试时花了整整一天才定位写出来给大家避坑。6. 最后说两句实在话这个项目做下来我最深的体会是数据工程不是给算法打下手它本身就是决定项目成败的主战场。YOLO模型现在人人能跑但能跑和能落地之间有巨大的鸿沟鸿沟里填的就是数据采集的脏活累活、标注规范的较真、增强策略的拿捏。如果你正打算启动类似项目我会建议按这个顺序投入精力先花两周把数据集基础版本做扎实类别定义、标注边界和场景覆盖都定义清楚再花一周调YOLO参数基本就能拿到90分效果。剩下的10分靠负样本和夜间增强一点一点抠。顺序反了就麻烦了——模型改来改去最后发现是数据里没有夜间样本纯属白费显卡电费。最后分享一个我后来一直在用的小技巧给每一个真实路口场景单独建一个小文件架里面只放三张典型帧白天、夜间、雨天每轮模型迭代后先用这三张图看输出。模型参数更新的好坏肉眼扫一眼就知道根本不用等完整验证集跑完。这做法就像老厨师尝咸淡一勺就知道整锅味道省事。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询