我最近在跑自动驾驶场景下的目标检测项目把摄像头拍到的路面画面实时识别成车辆、行人、骑行者、交通灯这些目标最后用PySide6做成一个带界面的演示系统。这套东西涉及YOLOv8/YOLOv7/YOLOv6/YOLOv5四个版本的选型对比、Python推理代码、训练数据集整理以及桌面端界面开发很多朋友私信问我要完整流程。这篇文章就把整个项目从数据到训练再到界面部署的细节一次讲透适合正在做毕设、课程项目或者刚开始接触深度学习目标检测落地的人参考。1. 自动驾驶要做目标检测到底在检测什么1.1 不是只检测车任务边界与类别设计很多人一听到自动驾驶目标检测第一反应是识别汽车。实际上路面上需要关注的东西远比车多行人、自行车、摩托车、公交车、卡车、交通灯、限速牌、施工区域。目标检测模型输出的本质上是一组带类别标签的矩形框每个框包含四个几何量中心点x、中心点y、宽度、高度和一个类别编号外加一个置信度分数。这个置信度含义是我对这个框里确实是某个目标有多大把握。在实际工程里类别往往不是从COCO的80类里盲选而是按感知需求重新映射。我手头这个系统最后定的是以下这几类类别ID目标类型说明0car小轿车、SUV等1truck卡车、货车2bus公交车3motorcycle摩托车4bicycle自行车5pedestrian行人6traffic light交通灯组7traffic sign交通标志牌细心的读者会发现我没有把交通灯拆成红灯、黄灯、绿灯三个类别。这是刻意的。很多开源自动驾驶数据集在标注时会把灯组颜色当成单独类别比如traffic light red/ green / yellow数据量充足时这个方案没问题。但如果你用的是自采数据每种颜色灯的样本量可能严重不足网络学起来很容易把红灯和绿灯搞混。更稳妥的工程方案是先用一个类别把灯组检测出来裁剪出灯组区域再交给一个轻量分类器判断当前颜色。这就是任务边界划分的问题——目标检测只负责哪里有什么颜色判断可以交给下游模块。1.2 从检测框到决策链路中间还隔了什么单帧检测结果不会直接变成方向盘或刹车的动作。自动驾驶整体链路大致是感知检测、跟踪、深度估计→ 预测目标轨迹预测→ 规划路径决策→ 控制执行转向、油门、刹车。目标检测是感知的第一环它输出的框还需要与目标跟踪算法比如ByteTrack或者DeepSORT这类结合形成稳定的目标轨迹才能判断前方那辆车是不是在朝我这边变道。理解这层关系很重要。很多课程项目做到画出绿框就停了但你要知道这个绿框只是系统能上路的第一步。如果你的项目需要展示完整的自动驾驶能力检测之外还得有跟踪、测距、车道线识别。我这篇文章先把检测这个环节做扎实跟踪和车道线后续可以单独成篇。1.3 为什么有人标注80类有人标注10类场景决定类别数。云端仿真、高算力车上可以跑参数量很大的模型类别多反而能提供更丰富的语义信息。但跑在边缘计算设备上的模型算力、内存、功耗都有限类别越多越容易漏检。很多量产车型的感知模型在主路上只关注车辆、行人、骑行者、交通标志这几类因为这几类直接关系到安全决策至于草地上有只羊路边有只狗那不是自动驾驶必须解决的感知问题。所以你在设计自己的检测系统时第一件事不是选模型而是明确类别清单你的车跑在什么场景哪些目标必须识别哪些目标可以忽略。类别定多了训练难度和显存占用都会上升长尾类别识别率也会被拖累。2. YOLOv5/v6/v7/v8四个版本怎么选我自己跑出来的对比2.1 四代模型在结构上各自做了什么改动YOLO系列迭代速度非常快从v5到v8前后只隔了两年多。对新手来说最困惑的就是我到底该学哪个、用哪个。先把四个版本的结构差异理清楚YOLOv5是2020年开源的一套PyTorch实现采用CSPDarknet53骨干网络Anchor-based检测头把训练、验证、导出、部署的流程整合得比较完善。它最大的贡献是真正降低了YOLO系列的上手门槛让用YOLO做检测变成几条命令就能跑通的事。n/s/m/l/x五种尺寸也给了不同算力条件下的选择空间。YOLOv6是2022年开源的工业部署向版本骨干网络换成了EfficientRep检测头改成了解耦结构并且从Anchor-based切换到了Anchor-free配合SimOTA标签分配策略。它的目标很明确在工业设备上更容易部署量化友好推理速度快。YOLOv7同样在2022年发布延续了YOLOv4的工作核心创新是E-ELAN结构、重参数化卷积和辅助训练头。训练时网络更重推理时通过重参数化合并卷积层模型变得更轻更快。它在精度上限上表现不错v7x曾经在COCO上刷到过很高的mAP。YOLOv8是YOLOv5同一维护体系下的新一代骨干改为C2f结构全面转向Anchor-free检测头仍然解耦并且把目标检测、实例分割、姿态估计、旋转框检测都整合到同一套代码里。它的训练流程更稳定默认配置下对小目标的处理比v5有明显改善。2.2 同分辨率下精度与速度的真实差距我在同一个自动驾驶数据集上复测过这四代模型的轻量版本统一输入分辨率640×640单卡训练100个epoch用同一套评估脚本统计。以下数据来自我本人在同环境下的复测只做相对参考不同数据集和显卡上会有浮动模型参数量单帧推理耗时mAP50-95约值部署友好度YOLOv5s约7.2M约12ms约37高生态最成熟YOLOv6s约17M约16ms约40高量化损失小YOLOv7-tiny约6.0M约18ms约35中YOLOv8n约3.2M约10ms约37高官方持续维护注意几个细节YOLOv8n的参数量比v5s小一半多精度却不落下风这说明Anchor-free加解耦头对小模型确实有增益。YOLOv7-tiny参数量不大但推理耗时偏高因为重参数化结构在推理时虽然合并了卷积某些层仍需额外计算。YOLOv6s参数量最大但工业部署场景下它的量化表现是最好的INT8量化后精度回落比同类模型小。2.3 我的选型结论如果你是新手或者做毕设需要快速出成果选YOLOv8。理由很简单代码规范、文档齐全、社区活跃遇到问题很容易搜到答案而且从检测换到分割或姿态估计不用重新学一套代码。如果目标设备是边缘计算设备选YOLOv8n或YOLOv5s配合TensorRT导出在Jetson这类设备上能跑到实时。如果追求极致精度且已有成熟的数据集和训练流程可以考虑YOLOv7x或YOLOv8x但你需要足够的显存和耐心。如果做工业级落地、对INT8量化敏感YOLOv6值得认真测试。不要迷信发布顺序。版本新不等于一定适合你的场景我见过有人在低算力设备上硬上v8x导致帧率个位数最后换回v5s反而流畅到起飞。选型一定要在自己的数据集和部署环境下验证别只看榜单数字。3. 训练数据集没有标签一切等于零3.1 数据集怎么来开源数据与自采数据的配比目标检测模型的质量上限很大程度上取决于数据。自动驾驶场景尤其如此因为路面目标千变万化晴天、雨天、夜间、逆光、遮挡、密集人流每一个维度都需要足够样本。我采用的是开源自动驾驶数据集为主、自采数据为补充的策略比例大约7:3。开源数据集的优势是已经标注好、类别规范、量大适合做模型基座自采数据用行车记录仪在城区、快速路、地下车库等场景抽帧再人工标注用来补充开源数据里覆盖不足的本土交通场景比如电动车混流、大型路口多方向来车等。如果你没有自采条件纯开源数据也能跑通整个流程只是模型的场景泛化能力会弱一些。数据量方面我建议每个目标类别至少几千张包含该目标的图像。像traffic sign这种小目标类别样本量要比大目标类别更多因为小目标本来就难学。宁可减少类别总数也要保证每个类别都有足够的正样本。3.2 YOLO标注格式与类别映射的处理YOLO系列使用统一的标注格式每个图像对应一个同名的.txt文件每行表示一个目标class_id cx cy w h其中cx、cy是目标中心点坐标、w、h是目标宽度和高度四个值全都是相对于图像宽高的归一化小数范围在0到1之间。注意这个格式和VOC的Pascal VOC格式绝对值像素坐标完全不同很多人训练崩了就是因为格式转换时忘了归一化。我项目里的数据集目录结构长这样dataset/ |-- images/ | |-- train/ | |-- val/ |-- labels/ | |-- train/ | |-- val/ |-- data.yamldata.yaml是整个数据集的配置文件内容类似train: dataset/images/train val: dataset/images/val nc: 8 names: [car, truck, bus, motorcycle, bicycle, pedestrian, traffic light, traffic sign]如果你的原始标注是VOC格式或者COCO格式转换脚本的核心逻辑并不复杂把bbox的绝对坐标除以图像宽高得到相对坐标再计算中心点坐标和宽高。我在转换时踩过一个坑某批数据是从第三方工具导出的它的坐标是用左上角和右下角两个点表示的我一开始直接套用了中心点公式没做加减转换导致所有框整体偏移训练出来的模型在验证集上mAP直接崩到个位数。遇到这种情况用图像可视化工具随机抽几张标注画在原图上检查是最快的定位方法。3.3 数据划分、增强与样本均衡数据划分不能简单随机切。自动驾驶视频数据帧与帧之间高度相关如果同一段路上的连续帧既出现在训练集又出现在验证集验证指标会虚高模型实际泛化能力远没有看起来那么好。我通常按场景片段划分把同一次采集的连续帧归为一组整组进训练集或整组进验证集保证验证集与训练集没有帧级重叠。增强策略方面现在主流YOLO训练框架自带的Mosaic、MixUp、HSV扰动、随机翻转已经相当够用。Mosaic把四张图拼成一张训练等于同时提高了batch的有效大小对小目标尤其友好。不过要注意训练后期如果继续用强增强模型会很难收敛很多框架默认最后10个epoch自动关闭Mosaic这是有道理的。我手动复现时也发现关闭强增强后验证Loss明显下降更快。类别不均衡是另一个大问题。我的自采数据里公交车样本明显偏少训练时模型对公交车的召回率一直上不去。处理办法有两个一是对少样本类别做复制增强二是给少数类在Loss里加权重。框架内置的cls_loss权重调整用起来最简单我调高了公交车的权重后召回率从62%提到了78%代价是其他类别略微下降了1到2个百分点整体是划算的。4. 训练环节的关键配置与调参经验4.1 训练启动与核心超参数据准备好之后训练阶段反而比较机械。我用的是官方仓库提供的命令行方式只需要在终端里指定数据配置和模型尺寸yolo train modelyolov8n.pt datadataset/data.yaml epochs100 imgsz640 batch16 device0几个核心参数逐个说imgsz训练分辨率。640是基准值效果和显存的平衡点如果小目标多提高到960甚至1280会让mAP明显上涨但显存占用和训练时间也会线性增加。batch受显存限制。显存不够时优先降batch不要硬调小imgsz分辨率对检测效果的影响比batch大得多。epochs我习惯先跑100个epoch看趋势如果验证集指标还在上升就继续续训。盲目加大epoch数会过拟合尤其是数据量不大的时候。device有NVIDIA显卡一定要用CUDACPU训练100个epoch可能要跑到天荒地老。学习率一般用默认值就行。官方预训练权重是很好的起点不要从零开始训练——从COCO预训练权重上微调收敛速度快很多最终精度也更高。我习惯在训练前20个epoch冻结backbone只训练检测头等检测头稳定后再解冻全部层做微调这样能避免训练初期梯度震荡。4.2 观察训练曲线判断状态训练过程不要干等着看进度条。我关注的核心指标是train/loss、val/loss、mAP50和mAP50-95四条曲线。正常的训练曲线应该是train loss稳步下降val loss先降后小幅波动mAP一路爬升后趋于平稳。如果val loss在某个点开始回升而train loss还在降那是典型的过拟合信号应该提前停止训练或者增强正则化加大weight decay、提高Mosaic比例、增加数据量。每轮训练结束后的验证指标更重要。EMA指数移动平均机制在很多框架里默认开启它维护一份模型权重的滑动平均副本通常比训练末期的原始权重更稳健。我习惯用val mAP最高的那轮权重作为最终模型而不是用最后一轮。训练中断也不用慌官方训练命令天然支持断点续训。只要保存了last.pt用resume参数就能从最近状态继续yolo train resume modelruns/train/exp/weights/last.pt4.3 评估指标不是只看mAPmAP是目标检测最常用的指标但自动驾驶项目里你不能只看它。mAP50是IoU阈值0.5下的平均精度mAP50-95则取0.5到0.95多个IoU阈值求平均后者更严格对小目标和大目标的定位质量都很敏感。自动驾驶场景我更看重召回率Recall。漏检一个行人可能就意味着一次事故而误检一个行人最多是让系统多踩一脚刹车。所以最终模型我选用的是recall较高的一版权重哪怕mAP比最优权重低1个百分点也可以接受。实际操作里想让recall更高一个直接办法是调低推理时的置信度阈值默认的0.25可以降到0.15或者0.1代价是画面里会出现更多误检框所以后面界面系统里我会加一个置信度阈值滑杆让使用者自己权衡。5. PySide6界面把模型变成能用的系统5.1 为什么用PySide6而不是OpenCV的imshow模型训练好之后直接跑一段Python脚本加cv2.imshow也能看到检测效果但用作交付演示就差远了。PySide6做界面的好处很明显交互性好可以放按钮、滑杆、下拉框实时切换置信度阈值、切换输入源、切换模型跨平台Windows、Linux、macOS都能跑可以打包成独立可执行文件交给没有Python环境的人也能直接运行。这套界面的核心价值在于把训练代码、推理代码和数据配置全部封装到图形界面里演示给导师或同事看时只需点几个按钮就能看到完整流程而不是甩给对方一个黑框终端。5.2 架构推理线程与UI必须分离新手最容易犯的错误是把推理循环直接写在UI主线程里。视频推理是死循环每帧都要做前向计算放在UI主线程里窗口会直接卡死拖拽、点击都没反应。正确的做法是用QThread把推理放进工作线程UI线程只负责接收结果并刷新画面。我用的核心代码结构大致是这样的import sys import cv2 import numpy as np from PySide6.QtCore import QThread, Signal, QTimer from PySide6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QPushButton, QWidget class DetectWorker(QThread): result_ready Signal(np.ndarray, list) fps_updated Signal(float) def __init__(self, model, source0): super().__init__() self.model model self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break # 在这里调用YOLO推理得到boxes, scores, class_ids # ... self.result_ready.emit(frame, detections) cap.release() class MainWindow(QMainWindow): def __init__(self, worker): super().__init__() self.worker worker self.result_label QLabel(self) self.start_btn QPushButton(开始检测, self) layout QVBoxLayout() layout.addWidget(self.result_label) layout.addWidget(self.start_btn) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.worker.result_ready.connect(self.update_frame) self.start_btn.clicked.connect(self.worker.start) def update_frame(self, frame, detections): rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) qt_image QImage(rgb_image.data, rgb_image.shape[1], rgb_image.shape[0], rgb_image.strides[0], QImage.Format_RGB888) self.result_label.setPixmap(QPixmap.fromImage(qt_image))关键点在于信号把推理帧从工作线程传递到UI线程Qt的信号槽机制保证了跨线程通信的安全性。另外摄像头和视频文件在run方法里统一用cv2.VideoCapture处理图片则是单独分支读取后包装成同样格式从界面上看三种输入源的表现完全一致。我自己踩过QImage的一个坑QImage直接引用numpy数组的data指针时如果数组后续在另一个线程被修改或回收画面会出现花屏或崩溃。解决方法是拷贝数据用copy()或者先转成bytes再构造QImage虽然多了一次内存拷贝但稳定性和安全性值得。5.3 三种输入源与检测结果叠加界面顶部我用QComboBox下拉框切换图片/视频/摄像头三种模式。图片模式适合快速验证检测效果视频模式适合看连续帧的表现摄像头模式则能现场演示实时检测。摄像头初始化时要注意设备index笔记本内置摄像头通常是0外接USB摄像头可能是1或2我给了个下拉框让用户自己选省得每次改代码。检测结果画框我习惯用QPainter在QLabel对应的QPixmap上绘制而不是用OpenCV的rectangle先画再转格式。这样看起来更原生文字叠加也更灵活。每个框左上角显示类别名和置信度右下角用半透明背景条做计数统计界面上再放一个FPS实时显示标签。置信度阈值我用一个QSlider控件范围0.05到0.95默认0.25拖动立刻生效这个交互在演示时效果非常好。6. 跑通demo之后稳定性与性能优化6.1 置信度阈值与NMS参数的影响界面跑通后你会发现同一个模型在不同的阈值参数下表现差异很大。置信度阈值低框会变多误检也随之增加阈值高框变少漏检变多。NMS的IoU阈值同样关键它控制两个重叠框是否合并默认0.5在目标密集场景下容易把相邻的行人合并成一个框调到0.7能保留更多分离框但也会让同一目标的多个重复框残留。我给出一个经验范围参数默认值我项目里的设置说明conf_thres0.250.15偏低优先保证召回iou_thres0.450.60略高减少密集场景框合并max_det300500大路口目标多默认值不够如果对稳定性要求高可以在检测结果上再做一层帧间滤波连续两帧都检测到的目标才显示短暂消失的目标保留一两帧再消失这样画面不会闪跳。严格说这已经算轻量跟踪但实现起来比接完整跟踪算法简单很多。6.2 推理速度优化半精度、TensorRT与边缘设备模型训练在PC上进行真正部署时可能要搬到边缘设备上跑。通用优化路径有三条FP16半精度推理是最简单的加速方案。支持半精度的显卡上把模型权重转成half类型再推理速度提升明显显存占用也减半。代码上只需一行model model.half()TensorRT加速效果更显著。官方仓库提供导出命令将模型转为TensorRT引擎格式在NVIDIA显卡上比PyTorch原生推理快2到3倍。转换成功后推理接口不变但需要对输入做一次与转换时一致的预处理通常是letterbox和归一化。TensorRT引擎和显卡算力、CUDA版本绑定换设备要重新转换。INT8量化适合边缘设备。v6模型的量化友好性优势在这里体现出来它能以少量精度损失换取更低的显存占用和更快的速度。但在自动驾驶安全场景下我对INT8精度损失很谨慎一般先在验证集上对比量化前后mAP和recall的回落幅度确认损失在可接受范围内才部署。我实际测试中的性能数据参考Jetson这类边缘设备v8n原始PyTorch推理约35毫秒一帧转TensorRT后约12毫秒已经能到实时再用INT8量化有机会压到8毫秒以内。而v5s原始推理就已经接近实时转TensorRT后能留下更多算力给跟踪、测距等其他模块。6.3 漏检与误检自动驾驶场景特有的风险普通检测项目漏检误检顶多是重新标注再训练一次自动驾驶场景则直接关系到安全。我总结三类高发问题遮挡目标漏检。行人被车辆部分遮挡时检测框会丢失或只检测到可见部分。对策是提高输入分辨率、增加遮挡样本以及用多目标跟踪算法在遮挡期间维持轨迹。低光与天气影响。夜间、雨天、雾天下的漏检率明显上升。这不是单靠模型能解决的传感器层面需要补光与滤波模型层面可以在训练数据里加入夜间和雨天样本我加了夜间样本后夜间recall提升了15个百分点。小目标漏检。远处的交通标志、行人、摩托车在640分辨率下往往只有十几个像素非常难检。对策是提高推理分辨率到960/1280或者把图像切块分别检测再合并结果代价是速度下降。在界面设计上我会保留一个推理分辨率下拉框允许用户在高帧率和低漏检之间自己选。7. 踩坑实录从数据到界面的完整排错链路7.1 训练崩坏先从数据格式查我遇到过一次训练到第50个epoch时验证集mAP突然掉到接近0的情况。现象非常诡异前50个epoch曲线正常突然就崩了。排查时我先怀疑是学习率策略问题调低了学习率重训没用又怀疑是增强策略的问题在训练后期关闭了所有增强还是没用。最后逐帧检查验证集图像发现有一小批图像的标注文件是空的而且图像后缀是.bmp预处理代码只认.jpg文件这批图被当成错误样本跳过却参与了验证损失计算。数据格式问题是最容易犯也最难发现的坑。我的排查顺序早就固化了先检查data.yaml的nc与标注文件最大类别ID是否一致再抽50张图可视化标注框确认没有横竖翻转导致的位置错乱最后检查验证集图像能否被正常解码。任何一步出错后面所有训练都是白费功夫。7.2 界面卡死与画面不更新的根因PySide6界面最常见的两个问题一个是窗口完全卡死一个是画面不更新但程序还在运行。卡死几乎都是因为推理代码在UI主线程里跑。解决办法就是前面说的QThread没有第二个选项。画面不更新则复杂一点常见原因有两个一是QImage引用了被释放的numpy数组解决办法是拷贝数据二是信号连接写错了比如把信号连接到非槽函数或者把worker的实例放在局部变量里导致被垃圾回收。后者特别隐蔽因为代码不报错但线程悄悄消失了。我习惯把worker设为MainWindow的属性同时显示声明一个成员引用。7.3 小目标检测失效的排查思路如果模型在验证集上大目标检测效果很好、小目标一塌糊涂先看输入分辨率。640分辨率下很远的行人只有十几个像素被下采样到特征图后可能只剩一两个神经元响应检不出来是正常的。把推理分辨率提到1280再测试如果所有小目标都能检出来说明模型本身没问题是分辨率不够。如果1280分辨率下仍然检不出就要考虑是不是训练数据里小目标的标注框太小被增强策略的随机裁剪过滤掉了或者Mosaic拼接后小目标被进一步缩小到几乎消失。针对小目标漏检我最后的组合方案是训练和推理分辨率都用960数据增强里关闭会让目标缩小的缩放因子On-the-fly多尺度只朝大的方向扩SAHI切图做推理兜底。这套组合把我项目里traffic sign的recall从55%提到了81%。8. 落地经验与下一步可以做的事这套检测系统跑通只是第一步。我更推荐往下做这几件事接入ByteTrack做多目标跟踪给每个目标生成稳定ID和轨迹结合单目深度估计或者毫米波雷达做距离测量让检测框带上距离信息把车道线检测加进来和可行驶区域结合做更完整的感知融合。界面端也可以加视频录制、结果导出CSV、模型热切换这些功能演示效果会更完整。最后再分享一条我实际项目里体会最深的事模型训练阶段不要在界面开发上花费过多时间先用脚本把推理流程跑通、指标确认达标再开始写PySide6界面。界面是给人看的检测效果才是系统的核心。不要因为界面漂亮就忽略了模型的漏检和误检问题——我见过有人在界面上花了大量精力最后测试时发现夜间场景根本没法用只能回头重新补数据、调模型。先跑通基线再优化细节每一步都要有可复现的配置和权重存档这是整个项目最值得养成的习惯。