简介:PyQt5与YOLOv5结合的多目标检测图形界面项目,面向刚接触PyQt5开发及YOLO算法的初学者,以可直接运行的完整项目演示界面设计与后端逻辑分离的开发思路,覆盖常用控件、模型加载、检测结果展示等环节,适合作为毕业设计或课程练手模板,帮助读者从零搭建自己的检测工具。资源包共112个文件,压缩后83.46MB,内含26个Python源码、25个YAML配置、33个编译缓存文件、3个模型权重以及测试视频、图片和界面文件,其中源码对应界面与业务逻辑,配置与权重支撑模型调用,视频图片便于运行效果验证,整体结构层次清晰,便于按需定位。目前已有8873人学习下载,通过项目可掌握PyQt5信号槽与布局管理、理解YOLOv5检测流程与源码组织,并学习拆分界面与逻辑层的方法,让代码更易维护和扩展。建议搭配PyTorch与PyQt5开发书籍研读,理论与实践结合,快速将算法封装为图形工具,是目标检测方向入门不可多得的实战素材。
1. pyqt5+yolov5+python:命令行的检测模型,怎么变成你说得清的界面系统
很多人把 YOLOv5 跑通之后就卡住了:模型在终端里能出框、能打印坐标,但真正要交给别人用、要演示效果、要录一段画面出来,命令行那套东西拿不出手。pyqt5+yolov5+python 这个组合,本质上就是给检测模型包一层能落地、能交付的图形界面。我拆过不少这类项目,最常见的需求是:打开软件、选一张图或一段视频、点一下按钮看检测结果,再把结果导出来。看起来简单,真正做完你才知道,界面线程和推理线程的协调、图像格式来回转换、模型路径和权重加载这几件事,随便一个细节没处理好,整套东西就翻车。
这套组合适合三类人:做毕设需要展示系统的学生,手头有检测需求但不想用现成 Web 服务的开发者,以及想把内部算法封装给非技术同事用的工程师。下面按我从环境搭建到打包发布的一整套实操顺序来讲,每一步都给你能直接复现的命令和代码。
2. 环境搭建:Python 版本、PyQt5 与 YOLOv5 的依赖三角关系
2.1 虚拟环境隔离:Python 3.8 与 torch 的版本匹配
YOLOv5 官方仓库对 Python 版本的要求不算苛刻,但实践下来最稳的是 Python 3.8 或 3.9。Python 3.10 以上也能跑,但部分依赖编译容易出问题,尤其是 Windows 上没有预编译轮子的时候,报错能把你绕晕。PyQt5 本身对 Python 版本没有太多限制,3.8 到 3.12 都有对应轮子,所以我把版本锚定在 3.8,主要是迁就 torch 和 torchvision 的选择空间。
conda create -n yolo_gui python=3.8 -y conda activate yolo_gui pip install torch==1.13.1 torchvision==0.14.1 --index-url https://download.pytorch.org/whl/cu117这里我选的是 CUDA 11.7 对应的 torch 1.13.1,这个组合在 YOLOv5 官方 requirements.txt 标注的兼容范围内,和 PyQt5 5.15 系列也共处得很好。如果你机器没装 CUDA,把--index-url换成 CPU 版的安装源就行,推理速度会慢一些,但整个流程不受影响。安装完先确认一下 torch 能不能正常调用 GPU:
python -c "import torch; print(torch.cuda.is_available())"这一步输出True再往后走。我见过有人装完 torch 没验证就一路往下,最后训练时发现用的是 CPU,白白浪费几天时间。
2.2 PyQt5 安装与验证:pip 源、designer 工具链
PyQt5 的安装比想象中容易踩坑。直接pip install pyqt5在国内网络环境下经常卡住,解决方法是换 pip 国内镜像源。我一般习惯在虚拟环境里把全局源配好,免得每次安装都折腾。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyqt5==5.15.9 pyqt5-toolspyqt5-tools这个包里带了 Qt Designer,就是可视化拖拽界面的设计器。清华源上 5.15.9 这个版本比较稳,其他版本在部分 Windows 环境下会出现QT_QPA_PLATFORM_PLUGIN相关报错。装完验证一下能不能正常创建 QApplication:
import sys from PyQt5.QtWidgets import QApplication, QLabel app = QApplication(sys.argv) label = QLabel("PyQt5 OK") label.show() app.exec_()如果这段能弹出一个窗口,说明 PyQt5 基础环境没问题。这里的QApplication是所有 Qt 界面程序必须创建的对象,它管理整个事件循环;exec_()是进入事件循环的入口,窗口显示之后程序不会立刻退出,而是等待用户交互。
2.3 YOLOv5 依赖清单:requirements 里藏着哪些坑
YOLOv5 仓库根目录下有个requirements.txt,里面列了它运行时需要的全部依赖。但你直接pip install -r requirements.txt会遇到一个现实问题:它会再次安装 torch 和 torchvision,把刚才装好的 GPU 版本覆盖掉。因为 requirements.txt 里对 torch 的约束是torch>=1.7.0,pip 会自动装最新版,这就破坏了版本一致性。
sed -i '/^torch/d;/^torchvision/d' requirements.txt pip install -r requirements.txt我的做法是先装好 GPU 版 torch,然后删掉 requirements.txt 里跟 torch 相关的行再安装其余依赖。这个过程会遇到几个经典问题:pyyaml版本过低导致yaml.load报错,numpy版本不兼容导致np.int属性缺失,matplotlib版本太新导致某些绘图函数变更。后两个是升级依赖时最常见的翻车点,解决办法是把 numpy 锁在 1.23 左右。
pip install numpy==1.23.5 matplotlib==3.7.1 pyyaml==5.4.1这一套下来,检测部分需要的环境就齐了。YOLOv5 本身是个 Python 包,它的核心检测逻辑在detect.py和models目录下,不依赖额外服务,所以环境配好就等于基础就绪。验证环境是否完整的办法是跑一次官方的预训练推理:
python detect.py --weights yolov5s.pt --source data/images/bus.jpg如果能在runs/detect/exp下生成带框的图片,说明模型推理链路是通的。
3. 数据与训练:从 labelme 标注到 YOLOv5 可用的数据集
3.1 标注工具选型与格式转换脚本
训练自己的数据集之前,标注是绕不开的一步。YOLO 系列用的标注格式是 txt 文件,每行一个目标:类别id cx cy w h,坐标都是归一化到 0 到 1 的比值。labelme 默认输出的是 JSON 格式,记录的是多边形顶点的绝对坐标,不能直接丢给 YOLOv5 训练,需要转一道。
我用 labelme 标注后用脚本统一转换,格式转换这一步是整个数据集准备过程中最容易出错的地方,坐标计算错了模型训练出来完全乱套。转换脚本如下:
import json import os def convert_labelme_json(json_path, class_names, output_dir): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) img_w, img_h = data['imageWidth'], data['imageHeight'] txt_name = os.path.splitext(os.path.basename(json_path))[0] + '.txt' txt_path = os.path.join(output_dir, txt_name) with open(txt_path, 'w') as out: for shape in data['shapes']: label = shape['label'] if label not in class_names: continue class_id = class_names.index(label) points = shape['points'] # 取多边形外接矩形 xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) cx = (x_min + x_max) / 2 / img_w cy = (y_min + y_max) / 2 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h out.write(f"{class_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n") class_names = ['person', 'car', 'bicycle'] # 按你的类别顺序定义逻辑说明:先读取 labelme JSON 里的图片宽高和每个标注形状,对每个多边形取外接矩形的四角坐标,再把矩形中心点坐标和宽高分别除以图片宽高做归一化。类别顺序class_names必须和训练时的 data.yaml 保持一致,这里定义的是person、car、bicycle,假如你后面训练时把顺序改了,之前转换的所有 txt 文件全部作废,这个我有过血泪经验。
3.2 data.yaml 与训练超参数
YOLOv5 训练前需要整理数据目录结构,标准做法是一个根目录下分images/train、images/val、labels/train、labels/val四个子目录,图片放 images 下的对应子目录,txt 标签放 labels 下对应子目录,两个子目录里同名文件一一对应。
# data.yaml train: ./dataset/images/train val: ./dataset/images/val nc: 3 names: ['person', 'car', 'bicycle']data.yaml 里面train和val指向数据集路径,nc是类别总数,names是类别名列表。路径可以用绝对路径,也可以用相对路径,但相对路径是相对于你执行训练命令时所在目录,建议直接用绝对路径,省得换目录后莫名其妙找不到图片。
训练启动命令:
python train.py --img 640 --batch 16 --epochs 100 --data dataset/data.yaml --weights yolov5s.pt --cache参数说明:--img是训练输入图像的缩放尺寸,640 是速度和精度的常规平衡点;--batch是批大小,受显存限制,16 对应大约 8GB 显存,显存小就降到 8;--epochs是训练轮数,100 轮对中小数据集基本够;--weights表示用预训练权重yolov5s.pt做迁移学习;--cache把图片预加载到内存,能明显加快训练速度,代价是多吃内存。
超参数文件data/hyps/hyp.scratch-low.yaml里有一堆训练参数,我用过默认值也能有不错的收敛效果,但有两个需要手动调整:
| 超参数 | 默认值 | 建议 | 调整依据 |
|---|---|---|---|
| lr0 | 0.01 | 0.005~0.01 | 数据集小时调低,防震荡 |
| mosaic | 1.0 | 0.5~1.0 | 目标极小场景建议保持 1.0,大幅提升泛化 |
| batch_size 在命令行处修改 | 16 | 8~32 | 以显存不溢出为准 |
lr0是初始学习率,学习率太高会出现 loss 曲线前期暴涨、后期震荡不收敛;mosaic是马赛克增强概率,它把四张图拼成一张训练,对检测小目标和密集场景特别有效,但训练耗时约增加两成。
3.3 训练恢复、早停与模型导出
训练到一半中断是家常便饭,断电、显存溢出、手误关了终端都会发生。YOLOv5 的--resume参数就是后悔药:
python train.py --resume runs/train/exp/weights/last.pt它会读取训练状态,接着上次的 epoch 继续跑。我用这个参数多次,训练轮数、学习率调度、best 和 last 权重的选择都会从 checkpoint 里恢复,不存在断点续训丢进度的问题。训练完成后的模型导出,YOLOv5 提供了 export.py:
python export.py --weights runs/train/exp/weights/best.pt --include torchscript onnx导出为 TorchScript 的好处是部署阶段不需要源模型文件结构,一个 .pt 文件就能加载推理;导出 ONNX 格式则是为了后来转成其他推理引擎。我这里一般导出 TorchScript,因为 PyQt5 里加载最省事,不需要额外装 onnxruntime。
4. PyQt5 界面集成:视频实时检测与结果展示
4.1 界面布局与信号槽设计
界面布局我用 Qt Designer 拖出来再转成 .py 文件,也有人喜欢纯代码写界面。对检测工具类的软件,核心控件就三样:一个用来显示画面的 QLabel,一个选择文件或开启摄像头的按钮,一个显示检测统计信息的文本区。下面这段代码演示了如何用纯代码搭一个最小可用的窗口:
import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog class DetectorWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("YOLOv5 检测") self.setGeometry(100, 100, 800, 600) self.btn_open = QPushButton("选择图片") self.btn_open.clicked.connect(self.open_image) self.label_display = QLabel("图片显示区域") self.label_info = QLabel("检测结果:") layout = QVBoxLayout() layout.addWidget(self.btn_open) layout.addWidget(self.label_display) layout.addWidget(self.label_info) self.setLayout(layout) def open_image(self): file_path, _ = QFileDialog.getOpenFileName( self, "选择图片", "", "图片文件 (*.jpg *.png *.bmp)" ) if file_path: self.label_info.setText(f"已选择:{file_path}") if __name__ == "__main__": app = QApplication(sys.argv) window = DetectorWindow() window.show() sys.exit(app.exec_())clicked.connect是 Qt 的信号槽机制,按钮点击后自动调用open_image函数;QFileDialog.getOpenFileName弹出文件选择窗口,返回用户选中的路径。这里self.setLayout(layout)把纵向布局装到窗口上,控件会随着窗口缩放自动调整。
4.2 QThread 推理线程与防卡死
界面和推理在同一个线程是最常见的翻车点。YOLOv5 加载模型要几秒钟,单张图推理也要几百毫秒,如果直接在主线程里跑,界面会卡死——窗口无响应、按钮点了没反应,严重时直接被系统标成「未响应」。解决这个问题的标准方案是开一个专门做推理的 QThread 子线程:
import cv2 import torch from PyQt5.QtCore import QThread, pyqtSignal class DetectThread(QThread): result_ready = pyqtSignal(object, object) # 原图, 检测结果 def __init__(self, weights_path, image_path): super().__init__() self.image_path = image_path self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path, force_reload=False) def run(self): img = cv2.imread(self.image_path) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results = self.model(img_rgb, size=640) self.result_ready.emit(img, results)这里pyqtSignal(object, object)定义了带两个参数的自定义信号,run()方法在start()调用后在子线程中执行。模型在__init__里加载,只加载一次;每次run只做推理。torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path)是加载本地训练好的权重文件,size=640指定推理时输入尺寸。
推理完成后用emit把结果发回主线程,主线程里连接这个信号更新界面。这样界面不会卡,因为耗时操作全在子线程完成。
4.3 图像显示组件与结果保存
检测结果要显示到 QLabel 里,需要经历 OpenCV 图像 → QImage → QPixmap 的类型转换,这一步写错会出现图片倒置、颜色变蓝、显示不出来的问题:
from PyQt5.QtGui import QImage, QPixmap def show_result_on_label(self, cv_img, results, label_widget): # 把检测框画在图上 img = results.render()[0] # YOLOv5 自带画框方法,返回 numpy 数组 h, w, ch = img.shape bytes_per_line = ch * w qimage = QImage(img.data, w, h, bytes_per_line, QImage.Format_RGB888) pixmap = QPixmap.fromImage(qimage) scaled_pixmap = pixmap.scaled(label_widget.width(), label_widget.height()) label_widget.setPixmap(scaled_pixmap)results.render()[0]是 YOLOv5 推理结果对象自带的方法,直接在原图上画好框和标签,返回 numpy 格式的图片,省去手动调 cv2.rectangle 的繁琐。QImage.Format_RGB888对应三通道彩色图,如果你传的是 BGR 图,显示出来颜色就全反了,必须在前面用cv2.cvtColor转成 RGB。pixmap.scaled把图片缩放到 QLabel 的尺寸,不改这一行的话大图会直接把界面撑爆。
结果保存按检测到的目标数量走不同的路径,静态图片直接cv2.imwrite保存画框后的图;视频则用cv2.VideoWriter逐帧写入,帧率保持原来视频的帧率。
5. 常见坑位排查:从安装到推理的六条血泪记录
5.1 PyQt5 安装后启动报could not load the Qt platform plugin "windows"
现象:装了 PyQt5 之后跑最简单的窗口程序,直接弹出could not load the Qt platform plugin "windows",程序崩溃。
原因:pip 装 PyQt5 时 platform plugin 的路径没有被正确识别,常见于多版本 Python 混装环境。
解决:先确认 PyQt5 相关包是否完整安装,缺哪个补哪个:
pip install pyqt5-plugins pyqt5-qt5如果还不行,加环境变量QT_QPA_PLATFORM_PLUGIN_PATH指向 site-packages 下 PyQt5 的 plugins 目录。我一般在代码开头加一段兼容逻辑:
import os os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = os.path.join( os.path.dirname(__file__), 'venv', 'Lib', 'site-packages', 'PyQt5', 'Qt5', 'plugins', 'platforms' )5.2 labelme 安装把 PyQt5 环境搞坏了
现象:先装 PyQt5 跑通了界面,后来为了标注数据pip install labelme,再启动原来程序发现界面样式突变,甚至模块冲突。
原因:labelme 会在安装时拉它的 PyQt5 依赖,pip 为了满足依赖把 PyQt5 升级或降级到兼容版本,和原来的版本不一致。
解决:给标注单独建一个虚拟环境,跟界面开发环境彻底隔离。从那以后我手动给每个项目都建独立 conda 环境,避免任何两个项目共用一套 site-packages。如果已经坏掉的,就重新建环境装一遍。
5.3 界面一推理就卡死
现象:点击检测按钮后整个窗口变成白屏无法操作,等几秒后恢复,视频检测时画面一顿一顿的。
原因:推理和 UI 刷新在同一个线程。YOLOv5 推理要占用 CPU 或者 GPU,Qt 事件循环被阻断,界面自然就死了。
解决:把推理放进 QThread 子线程,界面只用信号槽接收结果。这个方法前面已经写过了,这是所有 PyQt5 集成 YOLOv5 项目里最重要的一步,没有之一。
5.4 图片显示颜色变蓝
现象:检测框和标签都在,但整张图偏蓝色调,看起来很不自然。
原因:OpenCV 的imread读出来是 BGR 格式,没有转换就把数据塞进QImage.Format_RGB888,Qt 把 BGR 当 RGB 解析,红和蓝通道互换了。
解决:推理前把图转成 RGB,或者构造 QImage 时用Format_BGR888格式。前者更通用,因为 YOLOv5 的推理输入按 RGB 预处理,转一次解决了显示和推理的双重问题。
5.5 训练时显存溢出 OOM
现象:训练跑了一半,报CUDA out of memory,进程直接退出。
原因:--batch设置过大或--workers数据加载线程数过多,超出显存容量。这问题在我用单卡 8GB 显存训 YOLOv5s 时经常出现。
解决:batch 减半从 32 降到 16,再不行降到 8;--workers从默认 8 降到 4;不要用--cache以外的--rect(矩形训练)组合。YOLOv5 还有个--device 0参数明确指定 GPU。
5.6 导出 ONNX 后推理结果对不上
现象:TorchScript 推理正常,但 ONNX 导出后同一张图检测框位置偏移,置信度明显下降。
原因:ONNX 输入的通道顺序或归一化方式与 PyTorch 原始模型不一致,常见于转其他推理框架时预处理没对齐。
解决:导出时固定好opset版本,推理端严格按 YOLOv5 源码的预处理方式:BGR 转 RGB、除以 255 归一化、letterbox 缩放。如果不做推理引擎部署,直接用 TorchScript 格式就够用了,别折腾 ONNX。我自己用 TorchScript 格式从没出过这样的问题。
6. 进阶:把界面打成 exe 与推理速度优化
交付给别人的时候,不可能要求对方配 Python 环境、装 CUDA、下载模型权重。打成 exe 是必走的一步。PyInstaller 是目前最稳的打包工具,但 YOLOv5 这种带 torch 和 PyQt5 的项目打包体积通常 2GB 起步,过程和踩坑点非常固定。
pip install pyinstaller pyinstaller -n YOLODetector --windowed --add-data "models;models" --add-data "yolov5s.pt;." main.py--windowed表示不显示命令行窗口;--add-data把模型权重文件打包进可执行文件,分号前面是源路径,分号后面是目标目录;打包 torch 项目经常遇到ModuleNotFoundError,需要在 spec 文件里手动加 hidden imports:
--hidden-import numpy --hidden-import cv2 --hidden-import torch我用 PyInstaller 打包过几个界面程序,有一条经验很重要:尽量用英文路径打包,相关文件和输出目录不要出现中文。PyInstaller 对中文路径的支持一直不好,打包过程能跑完但生成的 exe 启动时报各种奇怪的错。
推理速度优化方面,实际操作中性价比最高的是切换半精度推理。YOLOv5 的模型在加载时支持半精度:
self.model = torch.hub.load('ultralytics/yolov5', 'custom', path=weights_path) self.model.half() # 转为 FP16 推理half()把模型权重和输入都转到torch.float16,推理速度提升明显,显存占用直接减半,代价是检测精度损失很小,在目标较大、场景简单的业务场景里完全感知不到差异。另一个技巧是把--img-size从 640 降到 320,检测速度提升约 3 到 4 倍,但对小目标的漏检率会升高。项目里如果检测对象距离近、尺寸大,我一般直接降到 416,速度和精度平衡得不错。
模型加载耗时也值得优化。torch.hub.load每次启动时如果不加force_reload=False,它会去检查远程仓库版本,导致启动慢几秒。设置force_reload=False会直接读本地缓存。我把权重路径写进配置文件,程序启动时先读配置再加载模型,换模型不用改代码。
最后验收阶段我有两个固定动作:先在目标机器上跑一段压力测试,连续检测 100 张不同场景的图片,看是否出现内存暴涨或显存泄漏;再把打包好的 exe 在一个干净环境里双击运行,验证模型路径、依赖库是否都内置齐全。从那以后我每次做完检测界面程序都会强制走一遍这两项验收流程,宁可多花半小时,也不要在交付现场出洋相。这套流程从环境搭建到打包交付一条线走下来,绝大多数坑都提前踩平了,希望帮到你。
本文还有配套的精品资源,点击获取