基于YOLOv8的智慧交通小目标检测与闯红灯识别实践
跑智慧交通项目的人应该都绕不开一个场景路口闯红灯检测。而且绝大多数第一次接触这个需求的人第一反应都是“先训练一个车辆检测模型红灯亮的时候框住越过停止线的车就完事了”。真做完一轮才发现事情完全不是这样。你要处理的不是“有没有车”而是“哪辆车、什么类型、在哪个车道、到达什么位置、对应哪个信号灯相位”更要命的是很多违章车辆在画面里就是几十个像素的小目标肉眼都容易漏模型凭什么不漏。这就是我这次想重点聊的项目基于YOLOv8做智慧交通场景下的车辆闯红灯目标检测重点解决两件事——小目标检测和类别区分。整套东西包括数据集构建、模型选型与训练、业务判定逻辑几个部分。如果你正在做类似的交通监控类项目或者准备用YOLOv8做一个小目标检测任务这篇内容可以给你一个完整的参考路径尤其是那些文档里不会明说、但实操一定会踩的坑。1. 项目整体设计与技术选型思路1.1 闯红灯检测到底在检测什么先纠正一个常见的思路偏差。闯红灯检测这个业务本身并不是一个单纯的目标检测问题而是“目标检测 多目标跟踪 信号灯相位对齐 区域判定”的组合问题。模型负责的核心是检测但整个项目的架构里检测只是第一环。我们最终要给出的判定结论是在信号灯为红灯的时间窗口内某一辆机动车从停止线前越过停止线并且继续通过了路口。这个判定需要几个信息同时满足信号灯当前是红灯车辆出现在停止线前的“待转区域”车辆持续越过停止线且轨迹上没有中断车辆类型属于需要处罚的机动车辆类别所以你看到的那个“目标检测数据集”实际上要承载的任务比想象中复杂。它不只是让你框出“车”而是要把画面里所有参与交通的目标都按类别区分出来轿车、SUV、卡车、公交车、摩托车、电动车、自行车、行人以及可选的信号灯状态目标。只有分类足够细业务规则才能区分“机动车闯红灯需要处罚”和“非机动车闯红灯以警告教育为主”。另外一个容易忽略的维度是场景覆盖。不同路口的摄像头安装角度、高度、焦距差异非常大。有的路口摄像头是枪机固定在横臂杆上俯视角度大有的是球机带变焦角度平一些。同一个模型要部署到多个路口数据集里就必须包含多种视角样本。而且真实路口的交通参与者形态极其复杂摩托车后面坐个人、电动车载货、三轮车混在机动车道里这些边缘情况如果不在数据里出现推理阶段一定会翻车。1.2 为什么锁定YOLOv8而不是其他方案选YOLOv8本质上是综合考虑了精度、速度、生态和迭代成本之后的结果。这里不吹不黑把几个主流方案放在一起看就清楚了。传统的Faster R-CNN在检测精度上并不差尤其在小目标上的表现往往比一阶段模型更稳但推理速度完全不适合做路口级别的实时视频分析。一个路口4个方向8个摄像头每个摄像头每秒25帧以上如果用Faster R-CNN跑至少需要几张高配显卡项目成本直接失控。YOLOv5和YOLOv7在速度上没问题但它们的工程生态相对分散训练和部署的标准化程度不如YOLOv8。YOLOv8最大的优势在于整个框架的设计更现代化C2f结构、anchor-free检测头、内置了丰富的数据增强策略、从训练到TensorRT导出有一条非常顺畅的链路。对一个要快速落地、持续迭代的智慧交通项目来说这意味着团队可以把精力放在数据和业务逻辑上而不是天天折腾框架兼容性。还有一个关键点是YOLOv8对“类别区分”的支持更友好。它天然支持多类别目标检测并且在COCO等大数据集上已经有预训练权重。交通场景里的车辆类别很多基础特征和COCO数据集里的car、truck、bus是接近的在这个基础上做微调收敛速度比从零训练快得多。这一点在项目时间紧的情况下非常重要。当然绝对不能把YOLOv8当成万能药。在后面的章节我会专门说小目标检测的问题——anchor-free设计在极端小目标场景下有它的弱点但我们有对应的工程方案去弥补这个后面展开。1.3 小目标检测在真实路口为什么这么难小目标检测难在“信息量不够”。一个目标如果只有十几个像素它的纹理特征、边缘特征在卷积网络逐层下采样之后几乎被完全抹掉。普通输入分辨率下很多远处车辆的宽度可能只有8到12个像素池化两次之后只剩2到3个像素的响应。这种目标是靠“猜”的不是靠“看”的。交通场景还叠加了更多不利因素远距离目标的尺寸极小、车辆之间互相遮挡、夜间车灯产生光晕、雨天反光、树木和栏杆的阴影干扰。再加上如果是广角摄像头拍摄同一条路上的车辆尺度差异非常大近处一辆卡车能占满半个画面远处一辆轿车只有十几个像素。这种尺度分布极度不均匀的情况对模型的特征表达能力提出了很高要求。更麻烦的是项目里还要求类别区分。很多模型能在尺度上稳住但在类别上会错把远处的小轿车识别成摩托车把公交车识别成卡车把电动车骑手识别成行人。在小目标尺度下类别间的区分性特征本来就模糊强行要求分类准确本质上是在跟信息瓶颈对抗。所以这个项目的技术核心不是“训练一个YOLOv8模型”这么简单而是围绕“小目标细类别”这两个难点做一整套数据、训练、后处理层面的综合优化。2. 数据集构建才是真正的分水岭2.1 采集端的视角选择与覆盖策略做数据集之前先搞清楚一个问题你的摄像头装在什么位置你的模型就只能学到什么视角的规律。校园路口、高速辅路路口、城市主干道路口机位角度完全不一样。我在项目中见过的最典型失误是只用了一个路口的视频做训练模型在这个路口效果还行换个路口就彻底崩了。数据采集建议覆盖以下维度机位高度与角度至少包含高俯视杆件端装枪机、中等俯视路边立杆、平视过街天桥或对向三种视角时间段白天、黄昏、夜间、凌晨重点是夜间占比不能低于30%。很多路口违法行为恰恰集中在夜间车少的时候天气晴天、阴天、雨天、雾天雨天的地面积水反光对检测影响极大必须有针对性的样本路口形态十字路口、T型路口、不规则路口不同路口停止线的位置和车道走向不一样车流密度高峰期的密集排队、平峰期的稀疏车流、无车时段的空场景采集还有一个容易忽视的环节负样本。很多团队只拼命收集“有目标”的帧但真实推理时会遇到大量没有车辆、只有路灯、地面标线、绿化带的画面。如果训练集里缺少这类负样本模型在空场景下容易产生幻觉把阴影、路面裂缝、电线杆误检成车辆。我通常会在数据集里加入15%左右的负样本帧这个比例对降低误检率很有帮助。2.2 类别体系设计怎么定义“类别区分”类别体系直接决定业务规则的表达能力。在闯红灯场景里类别区分不是越细越好也不是越粗越好而是要根据下游处罚逻辑来定。我建议的类别体系是分层的机动车大类car普通小轿车、SUV这两个在视觉上很难区分可合并但业务上通常不细分truck卡车、厢式货车、平板车bus公交车、大中型客车非机动车大类motorcycle摩托车从视觉和速度上都比电动车更快常有机动车属性ebike电动车、电动自行车bicycle自行车行人person行人、推车行人这个划分方式的好处是它在视觉上尽量做到了“内部差异小、类间差异大”同时兼容了业务规则。比如某些城市对摩托车闯红灯按机动车处理那么摩托车就必须能准确从非机动车中分离出来否则处罚依据就不成立。信号灯状态建议单独做目标检测或直接走信号机数据对接不建议跟车辆检测放在同一个模型里。原因很简单信号灯目标太小多为发光状态颜色信息占主导形状特征弱和车辆检测混合训练会互相干扰。我见过有团队硬把red_light、green_light加进车辆检测模型结果车辆检测精度掉了两个点。信号灯状态判断单独成模型或者干脆从信号机取相位都更稳。2.3 标注规范里的小目标处理细节标注的质量决定了训练效果的上限。一个模型在验证集上mAP偏低排查到最后往往发现是标签本身的问题。小目标检测任务里标注尤其需要一套可执行的规范。小目标怎么定义通常有三种口径绝对像素比如32x32以下、相对面积比如边框面积占图片面积0.1%以下、相对尺度边框尺寸小于图片尺寸的10%。在交通路口场景里我习惯用绝对像素加相对尺度的组合判断车辆长边小于图片宽度的5%就归为小目标。这类目标在标注时需要额外注意。标注规范里有几条实操原则小目标必须用外接矩形精确框住完整的目标不能只框可见部分。车辆被树遮挡一部分时只要能看到车头和车身的一部分就按完整车辆矩形标注而不是按可见范围标注多个目标密集重叠时如果目标完全被遮挡可以不标如果能看到一部分必须标出来画面的边缘区域目标截断超过一半的不标截断小于一半的按完整目标标注夜间场景中车灯造成的强反光区不算目标不能为了“看起来对”把一团光晕标成车辆凡是标出来的目标类别必须是人工确认过的不能用脚本自动生成标签然后直接训练另外强烈建议做一次交叉标注质检。两个标注员背对背标同一批图计算边框IoU和类别一致性。任何IoU低于0.5或者类别不一致的样本都要找第三个人仲裁。这个过程很费人力但对于一个要用于违章判罚的项目数据质量就是公信力。不干净的数据训练出来的模型在正式环境里误判一次代价比标注成本大得多。2.4 面向小目标的数据增强策略YOLOv8内置了多种数据增强但默认参数是为COCO这类通用场景设计的用在交通小目标场景上必须调。我最常调整的几个参数mosaic增强YOLOv8默认开启mosaic它把4张图拼成一张对小目标有一个隐藏好处——等于变相缩小了目标尺寸迫使模型适应更小的目标。但如果训练集中小目标本身就足够多mosaic可能会加剧“目标过小”的难度。我的做法是mosaic保持0.5左右不开满随机尺度训练开启多尺度训练比如输入分辨率640在0.5到1.5倍之间随机缩放。这能让模型对不同尺度的目标都有适应性对缓解尺度分布不均很有用翻转增强只在水平方向做垂直翻转对交通场景没有物理意义HSV扰动交通场景中颜色是类别区分的重要线索比如红绿灯、车身颜色HSV扰动幅度要小hsv_h设0.01左右就够调太大容易破坏颜色语义模拟恶劣天气可以加一点高斯噪声、亮度变化、模糊增强模拟夜间和雨天小目标增强还有一个专项技巧过采样小目标样本。统计训练集中各类别、各尺度的分布如果长边小于32像素的目标非常少就把包含这些小目标的图片在训练循环中重复多遍或者通过复制粘贴的方式把一些小目标车辆贴到空白区域。复制粘贴要注意场景语义不能把车贴到天上或者树冠上。有一个相对稳妥的方案是只贴在地面道路区域用mask约束粘贴位置。3. YOLOv8模型训练实操从配置到收敛3.1 数据集组织与配置文件YOLOv8使用标准的目录结构这一步别看简单错一次能浪费半天时间。它的数据加载机制是按yaml文件中的路径去读图片和标签的路径写错、标签文件名对不上都会导致训练时某个类别样本数为0。目录结构如下datasets/ traffic_redlight/ images/ train/ val/ labels/ train/ val/ traffic_redlight.yaml标签文件必须和图片严格同名比如frame_000123.jpg对应frame_000123.txt每一行是class x_center y_center width height坐标全部归一化到0到1。yaml文件内容很简单path: datasets/traffic_redlight train: images/train val: images/val names: 0: car 1: truck 2: bus 3: motorcycle 4: ebike 5: bicycle 6: person有个检查技巧第一次训练前先运行验证数据加载的命令确认每个类别在train和val中都有样本并且各类别数量不能极端失衡。如果car有两万张bicycle只有两百张需要补数据或者做类别加权否则模型会严重偏向高频类别。3.2 模型规格和输入分辨率怎么选YOLOv8有n/s/m/l/x几个规格对应不同的参数量和推理速度。在交通路口这个场景里我的建议是无脑从yolov8m起步。n和s精度太弱小目标场景直接不够用l和x精度高但训练成本和推理成本都翻倍。yolov8m在速度和精度之间比较平衡作为基准模型跑通全流程是合理的。输入分辨率是影响小目标效果最直接的因素。我从640提到960之后小目标车辆mAP普遍提升3到5个点。代价是推理速度直线下降。工程上的做法是离线分析项目两端训练时用960分辨率部署时先用640跑一版看小目标召回是否达标不达标再往上提。如果项目对速度要求极高还有一个选择是直接使用YOLOv8提供的P2输出层版本。P2层保留了更高分辨率的特征图对小目标更敏感但计算量也大。实际测试下来P2版本在交通场景小目标上的收益比单纯加大输入分辨率更明显值得试。3.3 训练命令和关键参数解读YOLOv8训练命令非常简洁但参数的含义值得逐条理解。下面是一份我在项目中常用的配置yolo detect train \ --model yolov8m.pt \ --data traffic_redlight.yaml \ --epochs 200 \ --imgsz 960 \ --batch 16 \ --lr0 0.01 \ --lrf 0.01 \ --weight-decay 0.0005 \ --mosaic 0.5 \ --degrees 0.0 \ --translate 0.1 \ --scale 0.5 \ --fliplr 0.5 \ --patience 30 \ --device 0逐条说明epochs设200次配合patience 30做早停如果验证集mAP连续30个epoch不提升就自动停batch设为16具体看显存运行中显存占用超过85%就减半lr0是初始学习率0.01比较保险。数据量大的时候可以试0.001收敛更稳但慢lrf是最终学习率系数0.01表示最后变为初始学习率的1%配合余弦退火degrees设为0交通场景不需要旋转增强旋转会破坏“车必须在地平面上”的物理约束scale设为0.5允许训练时随机缩放增强尺度鲁棒性还有一个很多人容易忽略的参数是--cache。数据量大时每次epoch重复从磁盘读取图片非常慢推荐显存充足时直接用--cache ram训练速度快非常多。如果显存不够用--cache disk也比不缓存好。3.4 训练过程怎么判断好坏训练不是跑完就完事需要盯几个关键曲线。loss曲线YOLOv8输出box_loss、cls_loss、dfl_loss。只看最终数值意义不大要看走势。正常的训练过程loss曲线在前30个epoch快速下降然后进入缓慢下降阶段最后趋于平坦。如果loss曲线在训练后期出现明显反弹可能是学习率过大或者数据中有脏标签。验证集上的mAP50和mAP50-95是主要指标但一定要分尺度看。YOLOv8训练日志不会直接给出小目标单独指标需要自己写一个评估脚本按真实框的面积分桶小目标小于32x32、中目标32x32到96x96、大目标大于96x96分别统计召回率。我见过整体mAP50有0.85但小目标召回率只有0.4的情况这种模型在路口远端车辆检测时完全不可用。过拟合的判断标准是训练集loss持续下降但验证集指标停滞这种情况通常发生在数据量偏小的场景。优先加数据增强和dropout而不是提前停机。3.5 小目标漏检的专项优化手段如果分尺度评估发现小目标召回率不达标下面几个方法按投入产出比排序提高输入分辨率成本最低只需改imgsz参数效果立竿见影代价是推理变慢增加小目标过采样回去调整数据集重训一轮使用P2版本模型修改模型配置加入更高分辨率特征层推理阶段做图像切分将大图切成重叠块分别推理再把结果融合。这个方案推理成本高但能解决极端小目标问题。对于路口场景可以只对远端区域做切分近端不用整套流程跑下来我的经验是正常的路口场景YOLOv8m配合960分辨率小目标召回率能做到0.55到0.65已经是很好的水平。如果还想要更高光靠调模型不如回去抠数据把漏检的bad case找出来再过一遍标注提升速度往往比换模型快。4. 闯红灯判定逻辑与类别区分落地4.1 从检测框到违规事件为什么还需要跟踪如果你直接把检测框当作最终结果去判断闯红灯误报率会高到你怀疑人生。原因很简单检测是单帧的而闯红灯是一个过程。同一辆车在连续帧里可能因为遮挡检测中断一个人骑着电动车从机动车道穿过前几帧是行人后几帧变成电动车。如果只看单帧根本无法稳定地描述“同一目标在连续时间内越过停止线”。所以检测之后必须接多目标跟踪。用ByteTrack或DeepSORT都是可选的。在路口场景里我更推荐ByteTrack因为它依赖检测框的IoU关联不需要额外训练ReID模型对固定摄像头场景足够稳定。跟踪的作用是给每个目标分配一个唯一ID维护它的轨迹序列。后面的闯红灯判定全部基于轨迹而不是单帧检测结果。4.2 红灯相位确定信号机对接优先红灯相位信息非常重要而且这个信息的准确性直接决定整个系统会不会误报。我强烈建议如果现场有条件对接信号机的灯态数据绝对不要依赖视觉识别红绿灯。原因有两层。第一视觉识别红绿灯成功率即使做到98%还有2%的错误这2%会直接造成闯红灯误判而信号机数据是“上帝视角”正确率接近100%。第二视觉识别红绿灯需要额外训练模型、额外算力而在夜间、逆光、灯珠反光条件下红绿灯识别效果很难保证。信号机对接在工程上通常走标准协议把当前信号灯相位红/绿/黄和剩余时间传到分析服务器检测系统只需要在收到红灯相位后开始做车辆越线判断。如果确实拿不到信号机数据只能用视觉识别红绿灯那必须做严格的状态确认连续N帧识别到红灯才认为进入红灯状态避免单帧闪烁误判。4.3 停止线标定与虚拟线圈设计停止线的位置是闯红灯判定的核心几何参考。部署时需要在画面中人工标定每个车道的停止线位置生成一条或多条虚拟检测线或者用虚拟区域代替。具体做法是在视频画面中画出多边形区域代表“停止线前待转区”和“越过停止线后的路口区域”。判定逻辑是当车辆轨迹从待转区进入越线区域并且持续存在就触发一次越线事件。这里有一个细节越线区域的宽度不要只是一个像素级的线条要留出一定的缓冲区比如20到30像素宽。否则车辆压线稍有抖动就会产生大量无效触发。但缓冲区也不能太宽否则停止线后方的正常停车会被误判为越线。4.4 类别区分如何影响处罚规则这就是标题里“类别区分”的落点。同样是越过停止线的目标类别决定了走哪条业务流程car、truck、bus机动车闯红灯触发违章记录抓拍证据链两张或三张连续位置照片加一段录像motorcycle根据地区规则可能需要按机动车处理但部分场景只警告ebike、bicycle非机动车闯红灯一般走警告或罚款较低档位person不处罚但要记录用于统计行人违章这个规则听起来简单但落到代码里就复杂了。因为跟踪轨迹里目标的类别可能不稳定同一目标前几帧是person后几帧是ebike如果直接按最后一帧的类别走流程业务就会出错。工程上的做法是对一条完整跟踪轨迹做类别投票统计该轨迹在所有帧中出现最多的类别作为最终类别同时要求该类别置信度超过一定阈值。另外对非机动车和行人闯红灯判定条件应该更宽松。因为非机动车体积小、速度慢在停止线附近来回移动很常见。一个行人在红灯时走到停止线边缘又退回去不应该被判为闯红灯。通常的做法是要求轨迹“持续且在时间维度上越过停止线后保持前进”连续多帧都满足才触发。4.5 误判抑制策略实测中误报最多的来源不是检测漏掉而是判定逻辑过于敏感。下面几个抑制策略都是必须的连续帧确认候选越线轨迹必须在超过3帧内持续成立才进入待确认状态置信度阈值分层检测置信度低于0.5的框不参与跟踪低于0.6的框不参与越线判定速度方向过滤闯红灯的轨迹方向必须是从停止线前向路口内前进。如果车辆从路口内往停止线方向逆行不能判定为闯红灯非机动车区域限定在非机动车道区域内检测到的目标即便轨迹形态类似越线也只按非机动车逻辑处理这些策略叠加之后能把误报率压到可接受的水平。我在项目中把这部分叫作“业务防抖”它的重要性不亚于模型本身。5. 常见问题与排查实录5.1 小目标漏检严重从哪下手如果分尺度评估发现小目标召回率不达标按下面的顺序排查先检查数据集里到底有多少小目标。写一个统计脚本把训练集中所有真实框的长边分布画出来如果32像素以下的目标占比不到5%模型根本没有机会学到小目标。这个原因占了我遇到的漏检案例的一大半。解决方案是回去补充数据或者做小目标过采样。再排查标签质量。小目标样本画框不精确、或者干脆漏标了模型就会被“教坏”。交叉质检环节不能省。最后才是调模型。imgsz提高到960换P2版本如果还不行再考虑切图推理。5.2 类别混淆严重摩托车/电动车分不清摩托车和电动车在远距离、夜间场景下确实高度相似。大多数情况下两者的轮廓差异是排气管和油箱但在小目标尺度下这些细节完全丢失。这个问题的根源不在模型而在类别定义本身。我的做法是重新审视类别定义如果业务规则里摩托车和电动车处理方式一样那就合并成一个类别motorbike没必要硬分。如果必须区分就必须在数据集里重点增加“有骑手姿态差异”的样本同时可以引入视频帧序列信息用运动特性辅助区分。摩托车通常速度更快跨帧位移更大这个线索可以帮助修正分类而不是纯粹依赖单帧外观。5.3 训练loss不收敛或者收敛后又反弹loss不收敛先看训练集本身。常见原因是标签文件里出现脏数据类别编号越界、坐标归一化后超过1、或者有的txt是空的。用数据加载脚本把所有标签文件扫一遍任何异常立即处理。loss反弹多半是学习率问题。我有一次用lr00.01训练一个自定义数据集前50个epoch正常之后cls_loss开始反弹验证集mAP也掉。把lr0降到0.001重新训练之后就好了。大数据集上从0.01起步没问题但中等数据集最好从0.001起步。另外一个隐蔽原因是模型预训练权重和自定义数据集的类别数不一致。YOLOv8加载预训练权重时如果类别数不同会自动替换检测头但替换后整体收敛速度会受影响。遇到收敛慢检查一下是否应该改用COCO预训练权重还是从零训练。5.4 部署阶段推理速度不够训练时用960分辨率效果好但部署时全部用960推理边缘设备不一定扛得住。工程上常用的方案是分级分辨率策略近端区域用高分辨率ROI裁剪后单独推理远端区域用较低分辨率的全图推理。也可以把整个画面分流主路口的机动车区域做大分辨率检测非机动车道区域用小模型检测。更推荐的做法是上TensorRT。YOLOv8的模型导出TensorRT非常顺畅FP16精度下推理速度提升显著。如果目标是进一步压缩延迟可以做INT8量化但要在量化前后分别在验证集上对比指标交通场景里INT8对小目标的影响可能不小。部署侧还需要关注跟踪器的性能。ByteTrack本身不重但如果检测帧率上去了跟踪逻辑的处理时间也可能变成瓶颈。把跟踪目标数量上限做约束比如单帧最多跟踪200个目标超出部分直接丢弃低置信度目标可以避免极端车流下的延迟尖刺。写在最后的一点体会这类项目做完整套之后我个人最大的体会有两点。一是模型本身的选择和调参在整个项目的难度排序里只能排第二。真正决定项目成败的是数据集的完整性、标注的严谨性以及业务判定逻辑的可靠性。一个模型不管多强遇到残缺的数据和粗糙的后处理都会在真实路口暴露短板。二是别把所有事情都押在模型上。闯红灯检测里的很多问题比如相位获取、几何标定、轨迹确认根本不需要跟模型死磕用信号机数据、人工标定和合理的状态机就能解决而且解决得更干净。如果你正在做类似的项目我建议把精力优先放在数据采集和标注规范上把业务规则提前定义清楚然后再去训练模型。这套流程理顺了后面无论是换检测模型、加类别、加路口都只是在已有的框架上做增量迭代不会推倒重来。最后再分享一个小技巧训练完模型先别急着上生产环境把模型跑在最近一周的录像上每天自动统计误报漏报哪怕只是肉眼抽检都能帮你提前发现一大堆只在真实场景里才会出现的问题。