做红绿灯检测这个项目我前前后后折腾了差不多两周从数据标注到模型训练再到最后的边缘端部署踩了不少坑。标题里写的这套东西——PaddleDetection、SSD_MobileNet、VOC数据集——组合起来看确实是个经典得不能再经典的轻量级目标检测方案但“经典”不代表“没坑”。如果你正准备用这套技术路线做红绿灯识别、路侧交通感知或者辅助驾驶这类场景这篇文章应该能帮你省掉一部分弯路至少可以让你在跑通第一个模型时心态不崩。红绿灯检测到底在检测什么表面上是找“灯”实际上要找的是灯的位置、颜色、方向状态而且在车拍、路拍画面里红绿灯往往只是几十个像素的小目标远处、逆光、夜晚炫光、雨雾遮挡全是麻烦。它适合谁看适合已经熟悉基础目标检测、准备用PaddleDetection落地具体项目的开发者也适合想了解整套技术路线怎么选型、怎么把现成框架调成实用模型的初学者。下面我就把整个项目从思路、数据、训练到部署的全部细节按实操顺序拆给你看。1. 项目核心思路拆解1.1 为什么选用PaddleDetection SSD_MobileNet这一套组合红绿灯检测的应用场景有俩硬约束一是车端或边缘设备的算力有限不能用特别重的模型二是检测要够快最好单帧几十毫秒内出结果留出余量做决策控制。这决定了主干网络必须轻量推理必须实时而SSD_MobileNet恰好是这个定位。SSDSingle Shot MultiBox Detector属于单阶段检测器核心思路是在多个不同尺度的特征图上直接预测默认框default box的类别和偏移量不需要像两阶段检测器那样先提候选框再分类所以速度快。MobileNet作为主干网络用深度可分离卷积替代普通卷积计算量大幅下降尤其适合跑在CPU或者低算力GPU上。PaddleDetection则提供了一个完整的工程链路从训练、评估到导出、部署都有现成工具省去了自己写数据处理和评估脚本的时间。对比一下其他方案用YOLO也可以但PaddleDetection里对VOC数据集的适配做得比较顺手用Faster R-CNN精度会更高可是推理速度对实时任务来说偏紧尤其是要部署到边缘设备时性价比不高。所以“SSD 轻量骨干网络”是精度、速度和工程便利性之间比较均衡的选择。1.2 红绿灯检测任务本身到底在检测什么很多时候我们把“检测红绿灯”当成一个单纯的目标检测问题但其实它包含两个子任务定位灯的位置、判断灯的颜色状态。如果你只训练一个“traffic_light”类别下游拿到框之后还得再做一遍颜色分类流程变长而且颜色分类在框位置不准确时很容易误判。所以我的做法是直接在检测阶段就把颜色维度加进去训练三个类别red、green、yellow。这样做的好处是模型同时学习外观特征和颜色特征端到端输出灯框和灯色。缺点也很明显——数据标注工作量变大黄灯样本通常特别少类别不平衡问题得专门处理。但综合来看比起“先检测后分类”的串联方案这种端到端方案在工程上更省事减少了一次误差传递。红绿灯本身的视觉特征是“高对比度、颜色鲜明、结构相对固定”但放进真实道路场景之后就变得复杂了目标远、尺寸小画面中有大量颜色相近的干扰物夜间灯是自发光的白天天光下的灯又偏暗雨雪天、逆光时对比度下降。这些都对模型鲁棒性提出了要求数据配比也要围绕这些场景来铺。1.3 VOC数据集在红绿灯检测里的特殊地位为什么用VOC格式而不是COCO最直接的理由是PaddleDetection对VOC数据格式的支持很成熟数据准备简单VOC的XML标注看起来也直观拿文本编辑器就能打开检查不需要额外写转格式的脚本。VOC数据集的目录结构是固定的核心包含三块JPEGImages放原图Annotations放XML标注文件ImageSets/Main放train.txt和val.txt两个列表文件。XML里记录了每张图里每个目标的类别名和边界框坐标xmin、ymin、xmax、ymax。理解这个结构之后你就能很快把私有红绿灯数据组织成PaddleDetection能直接消费的数据集。红绿灯场景对标注框的精度要求比普通物体检测要高因为灯本身小如果标注框多几个像素的误差对小目标IoU的影响非常大训练出来的模型框不准。这里也体现出VOC格式直观的好处脚本可以直接读取XML做统计分析很容易发现某个类别标注框太小、太偏或者类别错标的问题。2. 环境准备与数据整理2.1 PaddleDetection环境安装与版本兼容我先把环境装好再聊数据。推荐在干净的Python虚拟环境里做避免和系统其他项目的依赖打架。基础流程是创建虚拟环境安装匹配版本的paddlepaddle然后安装PaddleDetection包最后验证安装。# 创建虚拟环境 python -m venv ppdet_env # 激活环境 # Windows: ppdet_env\Scripts\activate / Linux: source ppdet_env/bin/activate # 安装CPU或GPU版PaddlePaddle # 以GPU为例先装对应CUDA版本的PaddlePaddle pip install paddlepaddle-gpu # 安装PaddleDetection pip install paddledet # 验证 python -c import paddle; print(paddle.__version__) python -c from ppdet.utils.check import check_version; print(ppdet ok)这里最容易出问题的是PaddlePaddle版本和CUDA版本不匹配。装完之后务必先跑一次简单的paddle运算确认GPU能用再跑训练不然就会遇到莫名其妙的“报错半天最后发现是CPU在硬抗”的情况。安装PaddleDetection后注意看它自带了一个configs目录里面预置了ssd_mobilenet_v1_voc.yml这套配置这就是我们要改的模板。不建议自己从零写配置文件用自带的预训练权重加标配配置起步比瞎改参数容易成功得多。2.2 红绿灯数据的采集、筛选与标注规范数据是整个项目里最花功夫的部分也是最影响最终效果的部分。模型再强数据质量不行就是白搭。我在这个项目里用的数据主要来自车载视频和路侧摄像机视频的抽帧按场景分了若干组每组抽几百张再人工筛选出“值得标注”的帧。采集的时候要特别留意场景覆盖度白天、黄昏、夜晚必须都有顺光和逆光都要有晴天、雨天、雾天尽量覆盖市内路口、高速匝道、快速路的路口也要覆盖。红绿灯颜色在不同光照条件下差异很大夜间灯的饱和度极高、白天灯的饱和度低模型必须见过足够多的变化才能泛化。标注用labelimg就行输出格式选PascalVOC。我的标注规范是类别名统一用red、green、yellow不要混用中文或大小写。框要尽可能贴住灯盘不把灯杆包进来带倒计时数字的灯盘尽量框住整个灯盘而不是只框数字。多组箭头灯、圆形灯在同一个灯杆上时每个灯盘单独标一个框。对遮挡超过一半的灯如果还能看出灯色就标注如果完全看不出就不标宁缺毋滥。对远距离小目标也要标因为这类样本正是红绿灯检测里最需要模型学会的。标注完之后一定要做一遍检查用一个脚本统计每个类别的框数量、框尺寸分布看看有没有类别严重缺失的情况。比如你发现yellow只有几十个框而red有几千个就需要手动补标黄灯或者用增强策略补齐否则训练出来的模型对黄灯的召回会非常差。2.3 目录结构、标签映射与配置文件适配数据准备完成后按下面的目录结构组织数据集假设都放在项目根目录的dataset文件夹下dataset/ JPEGImages/ 000001.jpg 000002.jpg ... Annotations/ 000001.xml 000002.xml ... ImageSets/ Main/ train.txt val.txt label_list.txt其中JPEGImages放图片Annotations放对应同名XMLtrain.txt和val.txt里每一行是图片文件名不带后缀用于划分训练集和验证集。为了让PaddleDetection正确读取类别在dataset目录下放一个label_list.txt一行一个类别顺序和模型输出对应例如red green yellow然后开始改配置文件。打开ssd_mobilenet_v1_voc.yml重点改这几个地方num_classes: 这里填“实际类别数1”因为目标检测通常把背景也算一类所以红黄绿三类就是4。数据路径TrainReader和EvalReader里指向你的数据集目录还有label_list.txt、train.txt、val.txt的路径。batch_size和learning_rate根据显存调节一般batch_size 16起步lr从0.001左右开始调。预训练权重可以设成“从官方下载的SSD-MobileNet COCO预训练权重”迁移学习能显著加速收敛。配置文件是最容易漏改的地方尤其是num_classes漏改了多分类任务会直接报shape不匹配报错信息还容易让人一头雾水。改完配置先跑一个空Epoch或者用一两百张图做快速试运行确认数据流没问题再上全量训练。3. 训练、评估与导出部署实战3.1 训练前需要改的5个关键参数我每次从零训练都会反复确认下面几个参数踩了太多次坑num_classes上一节说了类别数加1。忘了这个后面的loss形状一定炸。batch_size由显存决定。以常见的8GB显存为例输入分辨率300x300时batch_size 16可以跑但如果数据增强里开了mosaic内存翻倍可能要降到8。如果不确定先用小batch跑两分钟看显存占用再往上加。learning_ratePaddleDetection里的配置通常是base_lr: 0.001batch_size 16。如果batch_size翻倍学习率也建议翻倍到0.002保持线性的等效学习率。数据加载线程数num_workers在本机训练时设成CPU核心数的一半左右。设太小GPU会吃不饱设太大Windows平台可能出数据加载错误。训练轮数epoch红绿灯数据量不大我实测一轮到50~80个epoch足够设太大容易过拟合设太小精度不够。中间用eval开关去验证方便早停。训练命令我记得可以这样写python tools/train.py -c configs/ssd/ssd_mobilenet_v1_voc.yml --eval加上--eval之后每个epoch结束后会在验证集上算mAP训练日志里能同时看到训练loss和验证精度这是我判断何时停训的主要依据。3.2 训练过程与loss曲线怎么看SSD的loss由两部分组成定位损失smooth L1和分类损失softmax交叉熵。正常训练时loss曲线整体趋势是下降的但不少人看到loss一会儿高一会儿低就慌其实单阶段检测器在早期loss抖动是正常的尤其是开了随机裁剪、翻转这类增强之后。看曲线要看整体趋势不要被局部波动吓到。训练初期如果你发现loss下降极其缓慢或者根本不动优先怀疑学习率太低。如果loss直接变成NaN那大概率是学习率太高或者数据里出现了空标注框。如果用预训练权重起步loss一般会在前几个epoch快速下降然后趋于平缓。我的训练策略是前30个epoch用较大的学习率让模型快速收敛30个epoch后学习率按比例衰减让loss进一步降低。在日志里看到mAP连续几个epoch不涨就停止训练不用硬拉到设定轮数。下面是几个典型的loss信号及其判断现象可能原因处理方法总loss缓慢下降正常运行只是速度慢检查数据量和学习率可以适当加大lr总loss震荡剧烈学习率太高或batch_size太小降低lr适当增大batch_sizeloss突然变成NaN学习率过大、数据有NaN像素、标注异常检查lr、检查输入图和XML坐标是否越界验证mAP停滞可能是模型容量不足或数据增强不够换更大输入分辨率或增加数据增强强度训练loss很低但验证mAP低过拟合减少训练轮数或增加正则/数据增强3.3 模型评估指标解读PaddleDetection默认用VOC的mAP来评估检测器默认IoU阈值0.5。VOC的mAP有两种算分方式VOC07是11点插值APVOC12是积分AP。建议看PaddleDetection输出里同时给出的ap重点不是数字本身而是各类别之间的差距。红绿灯这个小目标场景最需要注意的是“小目标AP”这个维度能不能单独拿出来看以及每个类别的AP是否均衡。如果green很高而red很低说明数据里红灯样本少或者分布不均衡不能只看整体mAP觉得自己已经做好了。另外验证集怎么划分也影响你判断模型好坏。我在这个项目里把早晚白天夜间按一定比例混进验证集保证评估时每个场景都有代表避免验证集都是白天导致模型夜间翻车自己没注意到。评估不只看数字还要把部分预测结果可视化保存成带框的图片自己肉眼扫一遍比mAP数字更能发现问题。3.4 导出静态图模型与部署侧注意事项训练完模型之后真正要部署时不能直接加载训练权重跑训练权重里带着优化器状态和动态图信息部署环境用静态图推理更快更稳。PaddleDetection提供了导出脚本python tools/export_model.py -c configs/ssd/ssd_mobilenet_v1_voc.yml \ -o weightsoutput/ssd_mobilenet_v1_voc/best_model.pdparams导出之后会得到部署目录里面包含推理模型文件和配置文件。部署侧用Paddle Inference或者Paddle Lite加载都行MobileNet这种量化友好的结构比如在边缘端还可以开启INT8量化帧率可以明显提升。部署时有个坑导出模型默认的输入尺寸写的是训练配置里的尺寸。如果你的目标设备上输入的图像分辨率不一样要么在导出时指定要么在部署前做resize。红绿灯检测的输入尺寸不建议用太小的300x300后面调优再细说但部署时务必保证训练和推理的尺寸一致否则模型看到的特征尺度都变了精度会掉一大截。3.5 模型调优思路小目标与夜间场景怎么办第一版模型往往在小目标、远距离灯、夜间场景上效果不佳这不是模型结构的问题而是数据分布和输入尺度的问题。我最开始用300x300输入训练远处的小红绿灯基本漏检后来把输入尺寸提到360或512漏检立刻减少代价是训练和推理都变慢了一点但对红绿灯这种需要远距离识别的任务来说很值得。数据增强里随机裁剪对既有大目标又有小目标的图像非常容易把小目标裁掉导致模型学不到小目标。我的经验是在红绿灯数据集上只开轻度随机裁剪和翻转重点开颜色扰动、亮度扰动、对比度扰动这些增强方式不会破坏目标本身还能模拟白天黑夜不同光照下的视觉差异。再就是针对夜间场景我专门做了一批亮度增强过的副本数据让模型多接触“发光物体”的画面对夜间识别提升很明显。另外还有一招对画面中的ROI做拆分。红绿灯几乎都出现在图像上半部分或者十字路口正前方可以在前处理中裁剪出上半部分或者中心区域再做检测变相提升小目标分辨率。这种“区域检测加全图兜底”的组合方式在很多工程落地里比单纯加大模型更有效。4. 常见问题与排查技巧实录4.1 训练loss不降或者直接NaN第一反应先看学习率学习率过大是小数据集上最常见的NaN来源。把lr降到原来的十分之一先跑几个step看看loss是否恢复正常。如果还是NaN检查数据是不是有图片是全黑的、XML里是不是有负数坐标、有没有类别名和label_list对不上。PaddleDetection读数据时如果遇到损坏的图片有时候不会立刻报错而是可能把NaN带进去所以数据集做一遍完整性校验非常必要。4.2 红绿灯漏检严重漏检分两类远的漏、小的漏、暗的漏。远的漏就是把输入分辨率提高小的漏检查SSD的anchor匹配我实测红绿灯小目标匹配率不高的时候可以在SSD骨干的低层特征图上增加anchor的尺度和数量但这需要改配置文件里anchor生成的参数不建议初次接触的人直接动。更稳妥的做法是先调输入尺寸和增强策略。暗的漏基本就是夜间数据不够补数据比调模型直接。如果某个颜色类别漏检特别严重优先查数据分布。我一度红灯漏检多看了统计才发现红绿灯里的红灯样本只有绿灯的三分之一这种数据层面的类别不平衡得靠重采样或者补样本来解决不能指望模型自己学会。4.3 误检把红色车尾灯、红色招牌当成红绿灯这个在夜间特别明显。红绿灯夜间是强发光体而车尾灯、红色霓虹灯牌也很亮模型单靠纹理特征很难区分。我调试的时候加了几个层面的处理第一个在验证集里加了一批车尾灯红色招牌的负样本图片让模型见过更多“不是红绿灯但长得像”的东西第二个在后处理里加入颜色和形状约束比如检测框内的红色面积占比过低就过滤掉第三个是加了跟踪模块利用时序信息连续几帧都在同一位置出现的红灯才视为有效单帧偶发的红色块会被自然抑制。4.4 显存不够与训练卡死OOM的解法主要是减小batch_size、降低输入分辨率、如果卡在加载阶段把num_workers调低。还有一次卡在“莫名其妙训练一个epoch耗时很久”查了半天发现是Windows系统下PaddleDetection读数据时的文件句柄泄漏最后把num_workers设成0改用主线程加载数据就正常了。数据集大、机器配置一般的同学可以考虑先用较小的数据集做通流程验证没问题之后再上全量数据这样排查问题快很多。4.5 导出与部署时的常见坑导出时最容易犯的错是配置文件路径忘改了导出脚本拿着训练时的数据路径去读取配置解析没问题但数据内容用不到结果模型参数没对齐。导出后第一件事先用一张验证集图片做实测推理确认输出框的位置和原图对得上再上真机跑。量化部署时INT8量化后的模型精度通常会掉1%~3%这是正常的但如果掉得太离谱检查有没有把预处理方式改成和训练时一致尤其是图片归一化的均值和方差不匹配的话量化精度直接崩成乱框。最后分享一点个人经验心得红绿灯检测这类小目标、强颜色、场景多变的任务真正决定项目上限的往往不是模型结构而是数据质量和后处理策略。我在实际做的时候模型选型和训练参数只在前面两天花了很多时间后面大部分精力都花在了补数据、做负样本、写后处理约束和调跟踪上。先保证你的数据集里白天夜晚、远近目标的分布足够均衡再考虑调复杂的网络结构先跑通从训练到部署的最小闭环再回头优化精度这个顺序能帮你省掉无穷无尽的调试时间。如果你想把这个项目继续向下做还可以考虑在检测的基础上接一个轻量分类头做方向状态判断或者引入单目视觉测距给下游决策模块提供距离信息。每一步都在增加系统复杂度但每一步也都离真正的产品形态更近。这套PaddleDetection SSD_MobileNet VOC数据集的组合虽然看着朴素只要把数据这一关过好它足够撑起一个完整的红绿灯感知原型。