基于YOLOv8的红绿灯检测与PyQt5可视化系统实战 红绿灯检测这活儿看着简单真做起来挺磨人。画面里远处一个只有几十像素的小灯颜色还受光照、天气影响模型稍微偷懒就给你认成别的颜色。这段时间我正好把一套基于YOLOv8的道路交通信号检测系统从头到尾跑了一遍从数据集整理、模型训练、精度调优到用PyQt5搭了个带UI界面的检测工具最后拿视频实测下来整体流程算是完整跑通了。这篇就把整个项目拆开来讲从技术选型到每个步骤的实操细节再到我踩过的坑都写出来给准备做目标检测、尤其是想用YOLOv8做小目标识别项目的朋友一个参考。1. 项目整体设计与思路拆解1.1 核心需求与功能定位先聊需求。道路交通信号检测说到底就是要在图像或视频里找到红绿灯的位置并判断它是红色、黄色、绿色有些场景还要进一步区分箭头方向。这个功能不是简单“目标检测”四个字能概括的里面有几个难点目标尺寸小。红绿灯在画面中占比很小尤其摄像头装在路侧或者车顶距离信号灯几十米的时候灯体可能只有十几个像素这对检测算法的分辨率和小目标处理能力提出了很高的要求。颜色与亮度多变。同样的红灯白天看是亮红傍晚看是带点橙的暗红到了雨雾天气甚至会被削弱得很厉害模型如果只靠颜色特征很容易翻车。类别不均衡。实际道路场景里红灯、绿灯出现的频率不一定均衡如果数据集没处理好模型很容易偏向样本多的类别。实时性。做路口辅助或自动驾驶场景检测耗时不能太高否则后续决策跟不上。所以这个系统的功能定位不只是“能把图片里的红绿灯框出来”而是要兼顾检测速度、小目标召回率、多类别区分能力最后还要有一个方便操作和演示的窗口。项目本身属于典型的目标检测任务但加上UI界面和完整部署流程之后就更像个能直接拿去展示或者二次开发的小工具而不仅仅是一堆训练日志。1.2 为什么选YOLOv8而不是其他方案这里得聊聊技术选型。当时摆在面前的无非几个方向传统图像处理、两阶段检测器比如Faster R-CNN、单阶段检测器YOLO系列、SSD以及一些基于Transformer的新模型。传统图像处理用颜色阈值加轮廓分析写起来简单但光照一变就崩基本不适合实际场景。Faster R-CNN精度高但速度慢一张图跑几十毫秒以上直接接到视频流根本吃不住。基于Transformer的检测器效果好但训练成本和硬件要求都更高对普通项目来说没有必要。YOLOv8的优势在于它把速度和精度平衡得比较好官方提供了多个尺寸的预训练模型用迁移学习的方式可以快速在自定义数据集上收敛同时它还引入了无锚框检测、C2f模块等结构对小目标的特征提取能力比前代YOLO版本更友好。加上ultralytics这个库封装得不错从训练到导出再到部署都是几行代码的事适合这类需要快速落地的场景。另外选YOLOv8也考虑到了后续集成的问题。因为项目还需要做一个UI界面YOLOv8的官方推理接口很简单输入一张图就能得到检测框、类别、置信度输出格式很规整方便在Python里接界面逻辑。如果是别的框架可能还得自己写一堆预处理和后处理代码维护成本就上来了。1.3 整体技术框架与UI界面选型系统整体分三块数据层、模型层、应用层。数据层负责把公开数据集或者自己采集的图片整理成YOLO格式并做好训练集/验证集划分模型层用YOLOv8训练一个多类别检测模型应用层用Python做UI提供图片、视频、摄像头三种检测入口。UI界面这块我对比过几种方案。最省事的其实是直接用命令行跑推理但这种方案只适合验证模型演示给同事或者非技术的人看就很吃力。用Web页面则要起个后端服务还得处理浏览器兼容问题。最后选了PyQt5理由很简单它可以做原生桌面应用功能控件齐全支持线程而且Python调用方便。界面可以做成左侧控制区、右侧播放区的经典布局检测结果和FPS直接在窗口上刷新观感比较像一个小型专业工具。如果以后要换框架PySide6和PyQt5语法基本一致迁移成本也低。提示项目源码里通常已经包含训练脚本、推理脚本、UI脚本和训练好的权重文件目录结构一般不会太复杂。拿到手先别急着点运行花十分钟看一遍文件结构能少踩很多坑。2. 数据集处理与训练细节2.1 数据集准备与格式转换红绿灯方向的公开数据集其实不少常用的是LISA交通信号数据集和Bosch Small Traffic Lights数据集前者类别丰富后者专门针对小目标。但公开数据集通常不是现成的YOLO格式LISA是CSV标记Bosch是带XML标注的格式拿到手第一件事就是转格式。YOLO训练用的标注文件是txt每张图片对应一个同名txt每行代表一个目标类别序号、归一化后的中心点x、中心点y、宽w、高h。需要特别注意这个归一化是相对图像宽高来的很多人第一次写转换脚本时忘记除以宽高结果所有框的位置全错训练出来的模型效果自然很差。我实际处理数据时还在类别定义上做了取舍。红绿灯最简单的分类是red、yellow、green三类但很多路口有左转箭头灯和右转箭头灯如果把箭头单独建模系统才能应对“红灯可右转”这类现实路况。因此建议按需求保留类别red、yellow、green、left_arrow、right_arrow、forward_arrow不够用再加。类别越多标注成本越高模型难度也越大所以不要盲目加类别。公开数据集里往往有out-of-order状态也就是信号灯熄灭或混乱的异常状态这个类别我也保留了实际路口经常会遇到。2.2 训练配置文件与参数调整逻辑数据准备好之后要建立一个data.yaml内容大概是path: ./datasets/traffic_light train: images/train val: images/val nc: 5 names: [red, yellow, green, left_arrow, right_arrow]这段看起来简单但有一个高频出错点path路径最好用绝对路径或者相对YAML文件位置的相对路径。如果在命令行训练时路径写错ultralytics会直接报ImageNotExist之类的问题排查起来挺浪费时间。训练命令我一般这样写yolo detect train datatraffic_light.yaml modelyolov8n.pt epochs150 imgsz960 batch16 device0这里有几个参数想重点说下。imgsz默认是640但红绿灯是小目标我建议训练时用更大的输入尺寸比如960或者1280。很多人在这一步偷懒结果检测远处的小灯时召回率很低原因就是输入分辨率不够。当然分辨率从640提到1280显存占用会明显增加batch就得相应调小比如原来16改成8甚至4。device0代表使用第一张GPU没有GPU就只能用devicecpu但训练速度会慢很多建议有条件的用CUDA环境。epochs不是越大越好。我一开始跑到300轮发现150轮之后验证集mAP基本就平了继续跑不仅浪费时间还容易过拟合。后来习惯先跑100~150轮看趋势如果loss还在明显下降就延长时间如果验证集指标开始回落就提前停止。2.3 训练过程中的监控与调优训练过程要关注两个输出loss曲线和验证集指标。YOLOv8训练完后会直接给出mAP50、mAP50-95、precision、recall这些值。对红绿灯场景我最看重的是recall因为漏检一个红灯可能比误检一个其他物体更严重。在recall不足时可以先把conf_thres调低一点如果低置信度下能检测出更多目标说明模型本身学得到特征只是置信度阈值卡得太严如果低置信度下全部是误检框那就要从训练数据入手。针对小目标问题除了提升训练分辨率还有一个实用手段是适当裁剪或者拼接图像。比如把1280的输入继续加大或者对包含信号灯的区域做局部放大训练都会让模型对小目标更敏感。YOLOv8内部虽然也有多尺度训练但自己提供输入多样性仍然有明显帮助。另外红绿灯数据往往存在类别不均衡我在实际项目中做过统计green样本数量只有red的一半训练出来的绿灯mAP明显偏低。解决办法有两个一是做数据增强比如对green样本较多的图片做亮度、对比度扰动增加它的出现次数二是用类别权重调整损失但这在ultralytics库里需要改配置不熟悉的人容易出错所以优先推荐第一种。3. UI界面设计与实现细节3.1 界面功能规划与布局UI部分其实决定了这个项目能不能“拿得出手”。我自己设计的界面核心功能有三个打开图片检测、打开视频检测、打开摄像头实时检测外加一个检测结果统计区域和置信度阈值调节滑块。界面布局相对简洁左侧是控制面板放按钮和阈值滑块右侧是显示区域用来展示原始画面和画完检测框的结果画面。底部可以放一个状态栏提示当前FPS和模型推理耗时。为什么这样设计因为检测类工具最核心的交互就是“选一个来源、看到框出来的结果”控制项太多反而让演示者分心。置信度阈值做成滑块是必要的因为不同场景下最优阈值不一样白天光线好0.5可能很干净光线不好的雨夜0.3也许才能召回小目标。如果阈值写死演示时就要改代码很尴尬。3.2 PyQt5布局与线程处理PyQt5的界面代码不复杂但有一个关键问题如果检测逻辑直接写在UI主线程里视频流处理时界面会卡死窗口拖不动按钮点了没反应。所以我一定要把渲染工作放到主线程把推理工作放到子线程通过信号和槽来传递结果。这部分典型的代码逻辑是from PyQt5.QtCore import QThread, pyqtSignal from ultralytics import YOLO class DetectWorker(QThread): result_ready pyqtSignal(object) def __init__(self, model_path, conf_thres0.4): super().__init__() self.model YOLO(model_path) self.conf_thres conf_thres self.running True def run(self): while self.running: frame get_current_frame() results self.model(frame, confself.conf_thres, verboseFalse)[0] self.result_ready.emit(results)界面这边只需要定义好槽函数收到结果后把检测框画上去然后刷新QLabel显示就可以了。画框用cv2.rectangle加cv2.putText就行颜色根据类别映射一下比如红色灯用红色框绿色灯用绿色框这样演示时一眼就能看明白。提示如果省掉线程用循环逐帧跑推理程序表面上还能运行但只要视频分辨率高一点窗口就会变得非常卡。这个坑我见过很多次强烈建议一开始就把线程结构搭好。3.3 模型加载与推理接口封装建议把模型相关的操作封装成一个类不要直接散在界面代码里。接口做得干净后面换模型或者加功能都方便。简单封装大概长这样import numpy as np from ultralytics import YOLO class TrafficLightDetector: def __init__(self, model_path, conf_thres0.4): self.model YOLO(model_path) self.conf_thres conf_thres def detect(self, frame): results self.model(frame, confself.conf_thres, verboseFalse)[0] boxes results.boxes.xyxy.cpu().numpy() classes results.boxes.cls.cpu().numpy().astype(int) confs results.boxes.conf.cpu().numpy() return boxes, classes, confs这样UI只需要调用detect获得三个数组再根据类别映射表画框。模型加载一次就好不要在每帧里重复加载否则速度会慢到没法用。如果机器有GPU可以把模型切到半精度推理速度提升很明显代码里加个halfTrue就行。4. 完整实操流程与核心代码解读4.1 环境安装与数据集验证到了复现环节环境这步最容易出问题。我的建议是用Python 3.8以上版本装ultralytics、PyQt5、opencv-python。常见的坑是opencv和ultralytics自带依赖版本冲突所以优先用虚拟环境隔离一下不要直接往全局环境里塞一堆包。装完先跑一个最简单的命令python -m pip install ultralytics pyqt5 opencv-python然后准备一张测试图片跑一次推理python -m yolo predict modelbest.pt sourcetest.jpg如果连这个都跑不过先不要碰数据集和训练优先排查安装环境。很多同学一上来急着训练环境没弄好就报一堆错最后发现是numpy或者torch版本问题反而浪费时间。数据集验证也有个小技巧训练前先写个脚本把标注框可视化出来随机抽几张图把框画上去看一眼。这一步只要几分钟但能提前发现80%的标注问题比如类别标反、框坐标越界、图片和标注文件名字对不上。我习惯在VOC或者COCO数据转换完之后用下面这种思路快速抽查import cv2 import os image_dir datasets/traffic_light/images/train label_dir datasets/traffic_light/labels/train for name in os.listdir(image_dir)[:10]: img cv2.imread(os.path.join(image_dir, name)) h, w img.shape[:2] txt_name name.rsplit(., 1)[0] .txt with open(os.path.join(label_dir, txt_name), r) as f: lines f.readlines() for line in lines: parts line.strip().split() cls int(parts[0]) cx, cy, bw, bh map(float, parts[1:]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.imshow(check, img) cv2.waitKey(0)框位置错没错一眼就看出来比自己对着坐标算半天快得多。4.2 训练命令与最佳调参顺序我复现这个项目时用的训练流程大概是这样的yolo detect train \ modelyolov8n.pt \ datatraffic_light.yaml \ epochs150 \ imgsz960 \ batch16 \ device0 \ workers4 \ projectruns/traffic_light \ nametrain_v1第一次跑建议用yolov8n预训练权重因为它文件小、训练快流程跑通后再换成yolov8s甚至yolov8m。调参顺序也是固定的先确认数据集没问题然后调imgsz和epochs最后才动学习率。一开始就调学习率等于地基没打好就开始装修出了问题都分不清是数据还是参数导致的。训练完成后在跑UI之前建议先导出best.pt并且用几张没见过的图片测试。红绿灯项目里有一个值得做的改进用亮度增强思路做预处理比如对低光照帧先做直方图均衡化再送进模型。这个改进不一定对所有数据集有效但在夜间场景效果明显代码也就两三行import cv2 frame cv2.cvtColor(frame, cv2.COLOR_BGR2YUV) frame[:, :, 0] cv2.equalizeHist(frame[:, :, 0]) frame cv2.cvtColor(frame, cv2.COLOR_YUV2BGR)4.3 从模型到UI的完整链路模型训练好之后把它接进界面整条链路就成型了。我习惯的流程是启动程序时加载一次模型之后每次检测只是调用推理接口视频和摄像头来源都统一转成OpenCV的BGR帧检测结果统一传回主线程。图片检测相对简单因为一帧跑完就结束视频检测要考虑帧率控制不要一帧接一帧无限制地推理否则忙不过来时画面会变成“慢动作”。可以在每帧推理之间加入适当的延时或者计算一下FPS再动态调整处理节奏。摄像头实时检测是效果最直观的部分。把摄像头ID传入cv2.VideoCapture界面打开后显示实时画面同时叠加检测框。做演示前建议先在户外或者对着窗外的路口测试一下角度和距离很多次演示翻车不是因为模型不行而是摄像头对焦不准或者拍摄角度太偏导致红绿灯根本没出现在画面里。5. 常见问题与避坑指南5.1 环境与依赖问题速查整理一下高频问题。安装ultralytics时如果报依赖冲突可以先升级pip再单独安装ultralytics如果提示RuntimeError找不到CUDA说明torch版本和GPU驱动不匹配要么重装对应版本的torch要么直接用CPU模式先把流程跑通。还有一部分人碰到“AttributeError: module cv2 has no attribute VideoCapture”这通常是因为opencv-python和opencv-contrib-python混装建议卸载全部后只保留一个。界面运行时中文乱码也是常事。PyQt5默认字体对中文支持不好在QLabel或者按钮里显示“打开图片”可能要指定中文字体比如from PyQt5.QtGui import QFont font QFont(Microsoft YaHei, 10)少了这行程序不会报错但界面上全是方框很影响观感。5.2 训练精度相关的问题精度上不去先别急着改网络结构按顺序排查数据、分辨率、阈值、增强。如果val mAP很低优先检查val集里有没有标注错误如果召回率低优先提高imgsz或者增加数据增强如果精确率低把conf_thres适当提高或者增加负样本。红绿灯项目的特殊之处在于目标小且密集同类目标之间容易重叠非极大值抑制NMS的阈值也会影响结果YOLOv8默认的NMS参数一般不用改但如果同一个灯上叠加了好几个框可以考虑适当提高NMS的IoU阈值。还有一个容易忽略的坑训练集和验证集数据泄露。比如同一个路口的连续帧图片如果全都混进训练集和验证集验证集指标会很漂亮但换一个路口的视频就崩了。做数据划分时最好按来源或者时间切分不要随机乱切。5.3 部署与运行性能问题最后一个问题是性能。红绿灯检测如果要跑实时视频建议用有GPU的机器CPU模式下模型推理加上界面刷新会很吃力。YOLOv8提供的导出功能可以把模型转成TensorRT或者ONNX格式前者需要在对应GPU环境编译后者可以用onnxruntime加速速度都有明显提升。如果暂时不想碰这些工程化工具至少做到输入帧裁剪到合理尺寸、不用全分辨率原图推理关闭verbose日志输出用半精度推理。这三个小改动就能让帧率明显提升。UI界面卡顿还要检查一下QPixmap转换。把cv2的BGR帧转成QImage时如果每帧都复制一份大数组内存消耗会很大。正确做法是先转成RGB再用QImage创建时指定Format_RGB888最后只更新QLabel的pixmap避免不必要的深拷贝。我自己的实际体会是红绿灯检测这类项目最后的差距往往不是模型结构而是数据整理和工程化的细节。YOLOv8把算法门槛降得很低真正花时间的反而是把数据处理好、把UI用起来、把推理速度优化到能看的水平。后面如果还想扩展可以在系统里接入跟踪算法做信号灯状态的时序判断或者把模型导出成TensorRT部署到嵌入式设备上。这些都会比单纯训练一个模型更有实际意义。