电子商务网站建设的问题,京东商城网上购物登录,帝国做网站是选择静态还是伪静态,宣讲家网站 家风建设1. 项目缘起#xff1a;为什么选择TT100K和YOLOv5#xff1f; 大家好#xff0c;我是老张#xff0c;一个在AI和嵌入式视觉领域摸爬滚打了十多年的工程师。今天想和大家分享一个我最近刚做完的实战项目——从零开始#xff0c;构建一个能识别中国交通标志的智能检测系统。…1. 项目缘起为什么选择TT100K和YOLOv5大家好我是老张一个在AI和嵌入式视觉领域摸爬滚打了十多年的工程师。今天想和大家分享一个我最近刚做完的实战项目——从零开始构建一个能识别中国交通标志的智能检测系统。这个项目听起来挺酷对吧但说实话刚开始我也觉得头大毕竟要搞定数据集、模型训练还要做个能用的界面。不过一步步走下来发现只要思路清晰这事儿也没那么难。为什么选TT100K这个数据集呢简单说它够“接地气”。TT100K全称是Tsinghua-Tencent 100K包含了10万张从国内真实街景中采集的图片涵盖了30,000个交通标志实例。最关键的是这些图片是在各种天气、光照和复杂背景下拍摄的比如大晴天、阴雨天、夜晚甚至有些标志被树荫遮挡或者有点褪色。这意味着用这个数据集训练出来的模型更有可能在咱们国内的实际道路上“不掉链子”。我见过不少朋友直接用国外的数据集比如德国的GTSDB结果模型一上路对咱们国内特有的“禁止长时间停车”或者“注意非机动车”这类标志就“傻眼”了。所以要做能用的系统数据源必须对路。至于模型选择我直接用了YOLOv5。你可能听说过YOLO系列它的特点就是一个字快。YOLOv5在保持高精度的同时推理速度非常快这对于需要实时处理视频流的交通标志检测来说简直是量身定做。它不像两阶段检测器比如Faster R-CNN那样先找候选区域再分类而是直接在单次前向传播中就完成定位和分类效率高得多。而且YOLOv5的社区生态非常好从数据准备、模型训练到部署都有成熟的工具链对新手特别友好。最后为什么还要做个PyQt5的界面模型训练好了总不能天天在命令行里敲代码看结果吧一个图形化的界面能让你方便地上传图片、视频或者直接调用摄像头实时看到检测框和类别这才是“系统”该有的样子。PyQt5用Python就能开发出很专业的桌面应用跨平台也没问题非常适合用来包装我们的AI模型做成一个可交互的演示工具或者原型产品。所以这个项目的目标很明确用最接地气的TT100K数据集搭配高效易用的YOLOv5模型最后封装成一个有模有样的PyQt5桌面应用。接下来我就带你一步步走完全程从环境搭建到模型训练再到界面开发把每个环节的细节和踩过的“坑”都讲清楚。2. 第一步搞定TT100K数据集拿到TT100K数据集第一感觉是“真大”但紧接着就是“真乱”。原始数据包解压后文件结构并不是YOLO直接能用的格式我们需要做不少整理和转换工作。这一步虽然繁琐但至关重要数据质量直接决定了模型的天花板。2.1 数据集下载与初探TT100K数据集可以在其官方页面或一些开源数据平台找到。下载后你会看到主要的文件夹是annotations标注文件和train/test图像文件。标注文件是JSON格式里面记录了每张图片中所有交通标志的类别和边界框位置。我们需要做的就是把这些JSON信息转换成YOLO要求的TXT格式每个图像对应一个TXT里面每行是class_id x_center y_center width height并且坐标是归一化后的。我写了一个Python脚本来批量处理这个转换。这里有个关键点TT100K的原始标注里交通标志类别有200多种但很多类别样本数极少。盲目地用所有类别训练模型会严重偏向那些样本多的类。所以我根据实际道路的常见程度筛选出了50个最核心的类别比如限速标志pl5, pl10...pl120、禁令标志p1, p10...、指示标志i2, i4...等。这样既能保证模型实用性又能提升训练效率。import json import os from pathlib import Path # 我们筛选出的50个核心类别列表 selected_classes [pl80, p6, ph, w, pa, p27, i5, p1, il70, p5, pm, p19, ip, p11, p13, p26, i2, pn, p10, p23, pbp, p3, p12, pne, i4, pb, pg, pr,pl5,pl10, pl15,pl20,pl25,pl30,pl35,pl40,pl50,pl60,pl65,pl70,pl90,pl100,pl110, pl120,il50,il60,il80,il90,il100,il110] # 创建类别名到ID的映射 class_to_id {name: idx for idx, name in enumerate(selected_classes)} def convert_tt100k_to_yolo(json_path, img_dir, output_label_dir): with open(json_path, r) as f: data json.load(f) # 遍历所有图像 for img_info in data[imgs].values(): img_id img_info[id] img_width img_info[width] img_height img_info[height] img_path Path(img_dir) / f{img_id}.jpg # 只处理我们筛选过的图像这里假设我们已经根据类别筛选了图像列表 if not img_path.exists(): continue label_lines [] for obj in img_info[objects]: category obj[category] # 只处理我们选中的类别 if category in selected_classes: class_id class_to_id[category] # 获取边界框注意TT100K的bbox格式是 [x_min, y_min, x_max, y_max] bbox obj[bbox] x_min, y_min, x_max, y_max bbox[xmin], bbox[ymin], bbox[xmax], bbox[ymax] # 转换为YOLO格式归一化的中心点坐标和宽高 x_center (x_min x_max) / 2.0 / img_width y_center (y_min y_max) / 2.0 / img_height width (x_max - x_min) / img_width height (y_max - y_min) / img_height # 确保坐标在0-1之间 x_center max(0, min(1, x_center)) y_center max(0, min(1, y_center)) width max(0, min(1, width)) height max(0, min(1, height)) label_lines.append(f{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) # 如果有标注则写入文件 if label_lines: label_path Path(output_label_dir) / f{img_id}.txt with open(label_path, w) as f: f.write(\n.join(label_lines)) # 调用函数处理训练集和测试集 convert_tt100k_to_yolo(annotations/annotations_all.json, train, labels/train) convert_tt100k_to_yolo(annotations/annotations_all.json, test, labels/val)2.2 数据集组织与校验转换完成后我们需要按照YOLOv5要求的目录结构来组织数据。这个结构非常清晰tt100k_dataset/ ├── images/ │ ├── train/ # 存放所有训练图片 │ └── val/ # 存放所有验证图片 └── labels/ ├── train/ # 存放所有训练图片对应的YOLO格式标签文件 └── val/ # 存放所有验证图片对应的标签文件这里有个大坑一定要检查图片和标签文件是否一一对应。我遇到过因为图片损坏或者标注文件缺失导致训练时读不到数据报错信息又很模糊排查了半天。我建议写个简单的校验脚本遍历所有图片确保对应的标签文件存在且格式正确。同时可以用OpenCV随机抽一些图片把标注框画上去看看直观检查转换是否正确。有时候原始标注的边界框会超出图片范围我们的转换脚本里做了max(0, min(1, ...))的裁剪就是为了避免这个问题。最后我们需要创建一个YAML配置文件比如data/tt100k.yaml告诉YOLOv5我们的数据集在哪、有多少类、类名是什么。# data/tt100k.yaml path: /path/to/your/tt100k_dataset # 数据集根目录 train: images/train # 训练集图片相对路径 val: images/val # 验证集图片相对路径 # 类别数量 nc: 50 # 类别名称列表必须和之前筛选、转换时用的顺序一致 names: [pl80, p6, ph, w, pa, p27, i5, p1, il70, p5, pm, p19, ip, p11, p13, p26, i2, pn, p10, p23, pbp, p3, p12, pne, i4, pb, pg, pr,pl5,pl10, pl15,pl20,pl25,pl30,pl35,pl40,pl50,pl60,pl65,pl70,pl90,pl100,pl110, pl120,il50,il60,il80,il90,il100,il110]3. 第二步搭建YOLOv5训练环境环境搭建是项目的第一步也是最容易出问题的一步。很多人在这里就被劝退了其实只要按步骤来非常顺利。我强烈建议使用Conda来管理Python环境它能很好地解决包依赖冲突的问题。3.1 创建并激活Conda环境首先如果你还没安装Anaconda或Miniconda去官网下载安装一个。然后打开终端Windows用Anaconda PromptLinux/Mac用终端执行以下命令# 创建一个名为yolov5的Python3.8环境 conda create -n yolov5 python3.8 # 激活环境 conda activate yolov5为什么用Python 3.8因为这是目前很多深度学习框架和库兼容性最好的版本之一比较稳定。3.2 安装PyTorch与YOLOv5接下来安装PyTorch。这里有个关键选择用CPU还是GPU训练如果你的电脑有NVIDIA显卡并且安装了CUDA那一定要用GPU版本训练速度能快几十倍。可以去PyTorch官网根据你的CUDA版本选择安装命令。比如CUDA 11.3可以这样安装pip install torch1.12.1cu113 torchvision0.13.1cu113 torchaudio0.12.1 --extra-index-url https://download.pytorch.org/whl/cu113如果不确定CUDA版本可以在命令行输入nvidia-smi查看。如果没有GPU就安装CPU版本pip install torch torchvision。安装好PyTorch后克隆YOLOv5的官方仓库并安装依赖git clone https://github.com/ultralytics/yolov5.git cd yolov5 pip install -r requirements.txtrequirements.txt里包含了所有必需的库比如OpenCV、Pandas、Matplotlib等。安装过程如果遇到网络问题可以考虑更换pip源例如清华源。安装完成后可以运行一下官方提供的检测脚本测试环境是否OKpython detect.py --weights yolov5s.pt --source data/images/bus.jpg如果能看到一张带检测结果的bus.jpg图片被保存下来恭喜你环境搭建成功了3.3 准备模型配置文件YOLOv5提供了不同大小的模型s, m, l, x权衡了速度和精度。对于交通标志检测我们不需要特别大的模型因为标志本身特征相对明显但场景可能复杂。我选择了YOLOv5s它是体积最小、速度最快的在保持不错精度的前提下非常适合后续在普通电脑甚至边缘设备上部署。我们需要根据我们的数据集修改模型配置文件。YOLOv5的模型配置文件在models/目录下。我们复制一份yolov5s.yaml改名为yolov5s_tt100k.yaml然后只修改一个关键参数nc类别数从默认的80改为我们的50。# models/yolov5s_tt100k.yaml # Parameters nc: 50 # 修改这里number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple anchors: - [10,13, 16,30, 33,23] # P3/8 - [30,61, 62,45, 59,119] # P4/16 - [116,90, 156,198, 373,326] # P5/32 # 后面的Backbone和Head结构保持原样不用动 # YOLOv5 v6.0 backbone backbone: # [from, number, module, args] [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2 [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 ... ] # YOLOv5 v6.0 head head: [[-1, 1, Conv, [512, 1, 1]], ... ]注意YOLOv5的锚框anchors是预设的适用于COCO数据集。对于交通标志其宽高比和大小分布与通用物体不同。更专业的做法是根据我们的TT100K数据集重新聚类生成锚框。你可以使用YOLOv5提供的utils/autoanchor.py脚本进行分析。不过对于入门来说使用默认锚框也能得到不错的结果我们可以先训练看看效果。4. 第三步训练你的第一个交通标志检测模型万事俱备只欠训练。这是最激动人心也最需要耐心的一步因为训练一个模型可能需要几个小时甚至几天。4.1 启动训练命令在YOLOv5目录下运行训练命令。这里我解释一下关键参数python train.py \ --data data/tt100k.yaml \ # 指向我们之前创建的数据集配置文件 --cfg models/yolov5s_tt100k.yaml \ # 指向我们修改后的模型配置文件 --weights yolov5s.pt \ # 使用预训练权重从COCO数据集上训练的可以加速收敛 --epochs 300 \ # 训练300轮 --batch-size 32 \ # 根据你的GPU显存调整越大训练越快但显存占用越高 --img-size 640 \ # 输入图像尺寸YOLOv5默认是640 --workers 8 \ # 数据加载的进程数可以加快数据读取速度 --name tt100k_exp1 # 本次实验的名称用于保存结果--weights yolov5s.pt强烈建议使用预训练权重。这相当于让模型从一个“见过世面”的状态开始学习比从零开始随机初始化训练要快得多效果也更好。YOLOv5会自动下载这个权重文件。--batch-size这是最容易出问题的地方。如果训练时出现“CUDA out of memory”错误就是显存不够了。可以逐步调小这个值比如32-16-8。我的RTX 308010G显存可以跑到32。--epochs训练轮数。不是越多越好通常看验证集上的精度mAP不再明显提升时就可以停止了。300轮对于这个任务是个不错的起点。--img-sizeYOLOv5训练时会自动将图片缩放到这个尺寸。更大的尺寸可能带来更好的精度但也会显著增加显存消耗和训练时间。训练开始后终端会打印每一轮epoch的损失loss和评估指标precision, recall, mAP。这些信息是监控训练过程的关键。4.2 解读训练过程与监控训练启动后你会看到类似下面的日志输出Epoch gpu_mem box obj cls labels img_size 0/299 7.12G 0.06336 0.01373 0.08809 215 640: 100%|██████████| 625/625 [02:1500:00, 4.61it/s] Class Images Labels P R mAP.5 mAP.5:.95: 100%|██████████| 79/79 [00:2000:00, 3.86it/s] all 4000 12345 0.0199 0.444 0.0238 0.0137gpu_memGPU显存使用情况。box_loss,obj_loss,cls_loss分别是边界框回归损失、目标置信度损失和分类损失。训练初期这些值会比较高随着训练进行它们应该稳步下降并逐渐趋于平缓。如果损失出现剧烈震荡或上升可能是学习率太高或数据有问题。P(Precision),R(Recall),mAP.5,mAP.5:.95这是模型在验证集上的表现。mAP.5当IoU交并比阈值为0.5时的平均精度均值是主要参考指标。我们的目标就是让它尽可能高。mAP.5:.95在IoU阈值从0.5到0.95步长0.05区间内的平均mAP更严格。YOLOv5会在runs/train/tt100k_exp1你指定的--name目录下保存所有训练结果包括weights/best.pt训练过程中在验证集上表现最好的模型权重。weights/last.pt最后一轮的模型权重。各种可视化图表results.png损失和指标曲线、confusion_matrix.png混淆矩阵、F1_curve.png等。重点看results.png它把训练和验证的损失、以及精度、召回率、mAP都画在了一起。一个健康的训练过程应该是训练损失平稳下降验证损失也同步下降并最终趋于稳定验证集的mAP曲线稳步上升。如果训练损失下降但验证损失上升或mAP不升反降那很可能出现了过拟合意味着模型只“记住”了训练集而没学会泛化。这时就需要考虑使用数据增强、减少模型复杂度换更小的模型如yolov5n、或者提前停止训练。4.3 调参技巧与常见问题第一次训练结果不理想很正常我们需要当个“调参侠”。这里分享几个我常用的技巧数据增强YOLOv5默认开启了Mosaic马赛克和MixUp等增强这能极大提升模型鲁棒性。你可以在data/hyps/hyp.scratch-low.yaml等超参数文件中调整增强强度。对于交通标志可以适当增加色彩抖动hsv_h,hsv_s,hsv_v来模拟不同光照增加平移缩放translate,scale来模拟不同距离。学习率这是最重要的超参数之一。如果训练初期损失不降可能是学习率太小如果损失爆炸变成NaN那肯定是学习率太大。YOLOv5使用了带热身的余弦退火学习率调度器通常默认设置就很好。如果你想调整可以修改--lr0初始学习率。类别不平衡TT100K中不同类别的标志数量差异很大。YOLOv5的损失函数中包含了类别权重一定程度上缓解了这个问题。如果某些小类别始终检测不好可以考虑在数据层面进行过采样或者在损失函数中给这些小类别更高的权重这需要修改代码。训练中断与恢复如果训练到一半停了比如断电别慌。你可以用--resume参数从上次保存的权重继续训练python train.py --resume runs/train/tt100k_exp1/weights/last.pt我自己的第一次训练在RTX 3080上跑了大约12个小时300轮。最终在验证集上的mAP.5达到了0.83左右mAP.5:.95也有0.65。对于50个类别的交通标志检测任务这个精度已经相当不错足以应对很多实际场景了。5. 第四步用PyQt5打造一个炫酷的检测界面模型训练好了是时候给它做个“外壳”了。一个图形界面能让你的工作成果瞬间提升好几个档次无论是给老师演示、向客户汇报还是自己日常使用都方便太多。PyQt5是基于Qt的Python绑定功能强大写出来的界面也很专业。5.1 设计界面布局我们先规划一下界面需要哪些功能输入选择可以上传单张图片、选择视频文件、调用摄像头或者输入网络视频流RTSP地址。控制区域开始/停止检测的按钮以及调整检测置信度阈值和NMS IoU阈值的滑动条。显示区域一个大画布用来显示原始视频/图片以及模型画上去的检测框和标签。结果区域一个列表或表格实时显示当前帧检测到的所有目标类别和置信度。我用Qt Designer一个可视化设计工具拖拽生成了主界面的.ui文件然后转换成Python代码。当然你也可以完全手写代码。核心的界面类大概长这样import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox from PyQt5.QtCore import QTimer, Qt, QThread, pyqtSignal from PyQt5.QtGui import QImage, QPixmap import cv2 from ui_mainwindow import Ui_MainWindow # 这是由Qt Designer生成的界面类 from yolov5_detector import YOLOv5Detector # 这是我们即将封装的检测器类 class DetectionThread(QThread): 一个独立线程用于运行检测避免界面卡顿 finished pyqtSignal() # 检测完成信号 result_ready pyqtSignal(np.ndarray, list) # 发送检测结果图像和检测信息 def __init__(self, detector, image): super().__init__() self.detector detector self.image image def run(self): # 在这里调用YOLOv5模型进行检测 result_img, detections self.detector.detect(self.image) self.result_ready.emit(result_img, detections) self.finished.emit() class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super().__init__() self.setupUi(self) # 初始化UI self.detector None # YOLOv5检测器实例 self.cap None # 视频捕获对象 self.timer QTimer() # 定时器用于视频流 self.current_mode None # 当前模式image, video, camera # 连接信号与槽 self.pushButton_load_model.clicked.connect(self.load_model) self.pushButton_open_image.clicked.connect(self.open_image) self.pushButton_open_video.clicked.connect(self.open_video) self.pushButton_open_camera.clicked.connect(self.open_camera) self.pushButton_start.clicked.connect(self.start_detection) self.pushButton_stop.clicked.connect(self.stop_detection) self.horizontalSlider_conf.valueChanged.connect(self.conf_threshold_changed) self.timer.timeout.connect(self.update_frame) # 定时器超时读取下一帧 # 初始化界面状态 self.pushButton_stop.setEnabled(False) def load_model(self): 加载训练好的YOLOv5模型权重 model_path, _ QFileDialog.getOpenFileName(self, 选择模型权重, , PyTorch Model (*.pt)) if model_path: try: # 这里初始化我们的YOLOv5检测器 self.detector YOLOv5Detector(model_path, conf_thres0.5, iou_thres0.45) QMessageBox.information(self, 成功, 模型加载成功) self.pushButton_open_image.setEnabled(True) self.pushButton_open_video.setEnabled(True) self.pushButton_open_camera.setEnabled(True) except Exception as e: QMessageBox.critical(self, 错误, f模型加载失败{str(e)})5.2 封装YOLOv5检测器为了让PyQt5界面能方便地调用模型我们把检测逻辑封装成一个单独的类YOLOv5Detector。这个类负责加载模型、预处理图像、运行推理和后处理画框。import torch import numpy as np import cv2 from models.experimental import attempt_load from utils.general import non_max_suppression, scale_coords from utils.augmentations import letterbox class YOLOv5Detector: def __init__(self, weights_path, conf_thres0.5, iou_thres0.45, devicecuda:0): self.device torch.device(device if torch.cuda.is_available() else cpu) self.model attempt_load(weights_path, deviceself.device) # 加载模型 self.model.eval() # 设置为评估模式 self.conf_thres conf_thres self.iou_thres iou_thres self.names self.model.module.names if hasattr(self.model, module) else self.model.names # 获取类别名 def detect(self, image_bgr): 对输入的BGR图像进行检测 Args: image_bgr: numpy数组BGR格式的图片 Returns: result_image: 画了检测框的BGR图片 detections: 检测结果列表每个元素为 [x1, y1, x2, y2, conf, class_id] # 1. 图像预处理 (Letterbox) img letterbox(image_bgr, new_shape640, strideself.model.stride, autoTrue)[0] # Convert HWC to CHW, BGR to RGB img img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB img np.ascontiguousarray(img) img torch.from_numpy(img).to(self.device) img img.float() / 255.0 # 归一化 0-255 to 0.0-1.0 if img.ndimension() 3: img img.unsqueeze(0) # 增加batch维度 # 2. 推理 with torch.no_grad(): pred self.model(img, augmentFalse)[0] # 3. NMS后处理 pred non_max_suppression(pred, self.conf_thres, self.iou_thres, classesNone, agnosticFalse) detections [] # 4. 处理每一张图片的预测结果 (我们一次只处理一张) for i, det in enumerate(pred): if len(det): # 将框的坐标从预处理后的img尺寸映射回原始image_bgr尺寸 det[:, :4] scale_coords(img.shape[2:], det[:, :4], image_bgr.shape).round() for *xyxy, conf, cls in det: # 每个检测结果: [x1, y1, x2, y2, confidence, class_id] detections.append([int(xyxy[0]), int(xyxy[1]), int(xyxy[2]), int(xyxy[3]), float(conf), int(cls)]) # 5. 在原始图像上画框 result_image image_bgr.copy() for det in detections: x1, y1, x2, y2, conf, cls_id det label f{self.names[cls_id]} {conf:.2f} # 画矩形框 cv2.rectangle(result_image, (x1, y1), (x2, y2), (0, 255, 0), 2) # 计算文本背景大小 (text_width, text_height), baseline cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 2) # 画文本背景 cv2.rectangle(result_image, (x1, y1 - text_height - baseline), (x1 text_width, y1), (0, 255, 0), -1) # 写文本 cv2.putText(result_image, label, (x1, y1 - baseline), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 2) return result_image, detections这个类做了几件关键事letterbox函数保持图像长宽比进行缩放和填充确保输入尺寸固定non_max_suppression过滤掉重叠的冗余框scale_coords把检测框坐标映射回原始图像尺寸最后用OpenCV把框和标签画上去。5.3 实现多线程与实时检测在GUI中直接进行模型推理尤其是视频流会阻塞主线程导致界面“卡死”。所以我们必须用QThread把检测任务放到后台线程去执行。上面代码中的DetectionThread就是干这个的。对于视频或摄像头我们使用QTimer定时触发帧的抓取和检测。def open_camera(self): 打开摄像头 if self.detector is None: QMessageBox.warning(self, 警告, 请先加载模型) return self.current_mode camera self.cap cv2.VideoCapture(0) # 0代表默认摄像头 if not self.cap.isOpened(): QMessageBox.critical(self, 错误, 无法打开摄像头) return self.timer.start(30) # 约33FPS启动定时器 self.pushButton_start.setEnabled(False) self.pushButton_stop.setEnabled(True) def update_frame(self): 定时器槽函数从视频源读取一帧并进行检测 if self.cap is not None and self.cap.isOpened(): ret, frame self.cap.read() if ret: # 将BGR的OpenCV图像转换为RGB用于Qt显示 rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) # 在这里可以调用检测器但为了流畅性可以每N帧检测一次或者放到线程里 # 简单起见这里直接在主线程检测可能会卡 if self.detector and self.pushButton_start.isChecked(): # 假设开始按钮是toggle的 result_img, detections self.detector.detect(frame) rgb_image cv2.cvtColor(result_img, cv2.COLOR_BGR2RGB) self.update_detection_list(detections) # 更新右侧结果列表 # 将图像显示到QLabel上 h, w, ch rgb_image.shape bytes_per_line ch * w qt_img QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.label_display.setPixmap(QPixmap.fromImage(qt_img).scaled( self.label_display.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)) else: self.timer.stop() self.cap.release()5.4 打包与发布整个项目开发完成后你可能会想分享给朋友或者在没有Python环境的电脑上运行。这时候就需要打包成可执行文件.exe。我推荐使用PyInstaller它非常方便。首先安装PyInstallerpip install pyinstaller。然后在项目根目录下创建一个简单的启动脚本run_app.py里面就几行代码import sys from main_window import MainWindow from PyQt5.QtWidgets import QApplication if __name__ __main__: app QApplication(sys.argv) window MainWindow() window.show() sys.exit(app.exec_())接着在命令行中运行打包命令pyinstaller --onefile --windowed --add-data best.pt;. --hidden-importtorch --hidden-importtorchvision run_app.py--onefile打包成单个exe文件。--windowed运行时不显示控制台窗口适合GUI应用。--add-data best.pt;.将你的模型权重文件best.pt一起打包进去。分号前是源文件分号后是打包后在exe中的相对路径.代表根目录。--hidden-import显式告诉PyInstaller包含一些它可能找不到的依赖如PyTorch。打包过程可能会比较慢并且生成的exe文件会比较大因为包含了Python解释器和所有库。完成后在dist文件夹里就能找到你的可执行文件了。把这个exe和模型文件如果没打包进去一起发给别人他们双击就能运行你的交通标志检测系统了6. 踩坑心得与性能优化建议做到这里一个完整的系统就算跑通了。但根据我的经验从“跑通”到“好用”还有一段路要走。下面分享几个我踩过的坑和优化建议希望能帮你少走弯路。坑1数据集类别不平衡导致某些标志永远检不出来。TT100K里“限速5公里”和“限速120公里”的图片数量可能差几十倍。模型自然会偏向学习样本多的类别。我的解决办法是第一在准备数据时可以适当对稀有类别的图片进行复制或数据增强如旋转、轻微变色。第二在YOLOv5的损失函数中分类损失部分本身就有类别权重但你可以尝试修改utils/loss.py中的BCEWithLogitsLoss为不同类别设置不同的权重给样本少的类别更高的权重。坑2模型在真实场景如手机拍摄的视频上表现不佳。这很可能是因为领域差异。TT100K数据集虽然来自街景但和手机摄像头拍摄的角度、分辨率、畸变还是有区别。解决办法是进行模型微调Fine-tuning。用手机在校园或小区里拍几百张包含交通标志的图片手动用LabelImg之类的工具标注一下这个过程叫“数据标注”挺枯燥但很必要。然后用你训练好的best.pt作为预训练权重用这批新数据再训练几十轮。学习率要设得比第一次训练小一个数量级例如--lr0 0.001这样模型就能快速适应新场景效果提升会非常明显。坑3PyQt5界面在检测视频时卡顿。即使用了多线程如果每帧都进行检测对于大模型如YOLOv5l或高分辨率视频界面依然会卡。优化策略降低检测频率不是每一帧都检测比如每3帧检测一次中间帧直接显示结果或使用跟踪算法如ByteTrack来维持框的位置。降低推理分辨率训练时用640x640部署时如果对精度要求不是极致可以尝试用更小的尺寸如480x480进行推理速度会快很多。使用TensorRT或ONNX Runtime加速PyTorch模型转成ONNX格式再用TensorRTNVIDIA GPU或ONNX RuntimeCPU/GPU进行推理速度通常能有数倍提升。YOLOv5官方提供了export.py脚本可以很方便地导出ONNX模型。坑4打包后的exe文件巨大且运行时提示缺少DLL。PyInstaller打包PyTorch时会把整个torch库都包进去导致文件很大可能超过1GB。可以尝试使用pip install torch --index-url https://download.pytorch.org/whl/cu118安装CUDA 11.8版本的PyTorch这个版本对打包更友好一些。对于缺少DLL的问题通常是CUDA相关库没打包进去。一个比较“笨”但有效的方法是找到你CUDA安装目录下的bin文件夹比如C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.8\bin把里面所有.dll文件复制到和你的exe同一个目录下。最后这个项目不仅仅是一个教程它更是一个完整的起点。你可以基于它做很多扩展比如把模型部署到树莓派或Jetson Nano上做成一个车载设备或者增加一个语音提示模块检测到“限速”或“停止”标志时发出警报再或者把检测结果通过网络发送到服务器构建一个简单的车路协同演示系统。深度学习的乐趣就在于把想法变成现实的过程。希望这篇长文能帮你顺利走完从数据到模型再到应用的全流程祝你玩得开心