ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于PyQt5的语义分割系统:从模型原理到GUI部署实战

基于PyQt5的语义分割系统:从模型原理到GUI部署实战 简介面向计算机相关专业学生与开发者这份基于Python与PyQt5实现的语义分割系统源码包集成了图形界面与训练好的模型权重下载后即可运行体验也可作为毕业设计、课程设计、大作业或初期项目立项的完整参考。资源共25个文件包含20个Python源码、Qt界面文件、训练好的pth模型权重以及requirements依赖清单、项目说明文档和效果预览图其中源码覆盖了DFormer、DeepLabV3Plus、UPernet等多个分割网络实现并配套数据集处理、分割图生成、模型测试等工具脚本整体压缩包约44.7MB目录结构清晰便于按模块学习与二次开发。目前已有284人学习下载。项目代码经过验证可稳定运行解压后建议将项目路径重命名为英文避免中文路径导致解析错误。1. 这个语义分割系统到底是什么一个带界面的分割工具箱如果你正在为毕设、课程设计或者期未大作业发愁手里拿到的这份基于 Python PyQt5 的语义分割系统源码大概率就是你想要的那种“开箱能跑、改动能用”的项目。它不是一个只带几个 py 文件的空壳而是把模型训练、推理脚本、GUI 界面和训练到第 50 轮的模型权重一起打包好的完整工程。换句话说你用它既能给导师演示“我做了个带界面的分割系统”又能把里面的 DFormer、DeepLabV3 这些模型拆出来做自己的实验。我拆过不少这类资源最怕的就是“代码一堆、没有一个能跑通”。这份源码比较好的地方在于GUI 部分是用 Qt Designer 生成的 .ui 文件再加逻辑代码组合的模型部分集中放在 model 目录下连数据集转换脚本create_seg_map.py和数据加载器mini_dataset.py都写好了。适合三类人想快速完成毕设演示的在校生、需要拿现成代码做二次开发的从业者以及想从 GUI 工程角度理解分割系统怎么组织代码的初学者。下面我会从头到尾把每个文件、每个关键逻辑、每个容易翻车的地方讲透让你拿到手之后不用对着黑匣子干瞪眼。2. 语义分割原理与模型选型从 FCN 到 DeepLabV3文件里每个模块都在干什么2.1 分割任务本质给每个像素贴标签语义分割不是把整张图打个分类标签而是把图片里每一个像素都归类到某一类别比如“路”“车”“人”“背景”。这个任务的难点在于你要让模型既懂得全局语义这片区域是马路又保留局部细节马路和人行道的边界要清晰。早期做法是滑窗分类用分类网络扫过每个小块但这样计算量爆炸而且感受野受限。直到 FCN 出现把全连接层换成卷积层才实现端到端的像素级预测。FCN 的典型做法是先让骨干网络比如 ResNet做下采样提取特征再用转置卷积把特征图逐步上采样回原图尺寸。但上采样会丢失细节所以后续的 DeepLab 系列引入了空洞卷积Atrous Convolution在增大感受野的同时不牺牲分辨率再配合 ASPP 模块从多个尺度捕获上下文。这份源码里 model 目录同时出现了 deeplabv3plus.py 和 fcnhead.py还有 Up 系列常用的 upernet.py说明项目方并不是只用了一个模型而是把几种常用分割头都做成了可选的模块方便切换对比。2.2 模型目录逐个拆解骨架、解码器和分割头的分工打开 model 文件夹你会发现它不是简简单单一个 model.py而是按照“编码器-解码器-分割头”的思路拆分的。EncoderDecoder.py 是统筹入口它负责把骨干网络编码器和分割头解码器组装起来DFormer.py 是骨干网络我判断这里用的是带 Transformer 结构的 DFormer 系列这类骨架在语义分割里近几年很受用因为它的自注意力机制比纯 CNN 更容易建模长距离依赖。再往下看 decoders 子目录fcnhead.py 是经典 FCN 分割头deeplabv3plus.py 是 DeepLabV3 的解码器这两个可以应对大多数通用分割场景。ham_head.py 属于较新的聚合注意力头LMLPDecoder 和 MLPDecoder 都是基于多层感知机的轻量解码器速度更快、显存占用更低。nl_head.py 则是非局部注意力头适合需要建模全局关系的任务。我在实际项目里一般会用 DeepLabV3 做基准再切到 LMLPDecoder 看速度收益这套代码的结构恰好支持这种对比实验。2.3 为什么要带 GUI训练好的模型需要一个能被演示的壳很多同学拿语义分割源码只跑命令行推理但往毕设里一放你得给老师展示“我能对任意一张图做分割”。这时 GUI 的作用就体现出来了用 PyQt5 做界面用户点按钮选图片界面显示原图和分割结果后台调用训练好的模型完成推理。这份源码里 Qt_seg.ui 是界面布局文件Qt_seg.py 是界面逻辑与控制代码main.py 是入口程序。从工程组织的角度看这种“UI 与逻辑分离”的做法也算是个加分项。.ui 文件可以用 Qt Designer 直接打开改布局改完再用 pyuic 工具转成 .py 文件不用手写界面代码。save_model 目录下的 ckpt_epoch_50.pth 就是训练了 50 个 epoch 的模型权重直接交给 GUI 做推理省得你从头训练。理解了这个架构你就能回答老师最常见的夺命追问“你这个系统分成哪几层每一层在干什么”3. 把系统跑起来环境安装、目录规范与 GUI 推理全流程3.1 环境安装用 requirements.txt 把坑一次填平先把 Python 版本确定下来。我建议用 Python 3.8 到 3.10 之间的版本太新的 3.12 容易遇到 torch 和 pyqt5 的轮子没跟上而装不上。项目里带了 requirements.txt但不同机器的 CUDA 版本不一样我一般会分两步装# 第一步装基础依赖numpy、pillow、pyqt5 等 pip install numpy pillow pyqt5 # 第二步安装 PyTorch根据自己的显卡驱动选对应版本 # CPU 版没独立显卡也能跑只是慢 pip install torch torchvision # CUDA 版示例以 CUDA 12.1 为例 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121装完以后用下面的命令验证 torch 和 pyqt5 是否都能正常 import。这一步很多人都会跳过结果跑到一半才发现 torch 装成了 CPU 版或 pyqt5 没装上非常浪费时间。python -c import torch; print(torch.__version__, torch.cuda.is_available()) python -c from PyQt5.QtWidgets import QApplication; print(PyQt5 OK)第一行会输出 torch 版本号和一个布尔值如果你装了 CUDA 版且显卡驱动正常True 表示能用 GPU 加速推理False 也不影响跑通只是速度慢些。第二行没报错就说明 PyQt5 绑定没问题。如果 torch.cuda.is_available() 返回 False优先检查显卡驱动版本和 torch 的 CUDA 版本是否匹配别急着重装系统。3.2 目录规范项目名路径别带中文这一点我在拆分这份源码时格外想强调。项目说明里已经写了“项目名字和项目路径不要用中文”这不仅是经验之谈背后有实实在在的技术原因PyTorch 加载权重文件时如果路径含中文某些 Windows 环境下 torch.load 会因编码问题直接崩溃PyQt5 的 Qt Designer 生成的 .ui 文件里如果路径编码不一致也可能在加载界面时报“Could not load file”。# 建议解压后立即重命名为英文路径 unzip 毕设项目基于Pythonpyqt5实现的语义分割系统源码\(带GUI模型\).zip -d C:/work/ mv C:/work/毕设项目基于Pythonpyqt5实现的语义分割系统源码(带GUI模型) C:/work/seg_project cd C:/work/seg_project注意第二条命令里我把压缩包内层目录直接改名成了 seg_project这一步看起来笨但能省掉后面 80% 的玄学报错。如果你用的是 Linux 或 macOS英文路径问题相对少但依然建议保持统一规范方便以后把项目分享给别人。3.3 启动入口从 main.py 到 GUI 界面整个系统的主入口是 main.py它不是简单地弹个窗口而是要把模型加载、界面初始化、推理回调都串起来。大致流程是这样的先创建 QApplication 实例再加载 Qt_seg.ui 生成的主窗口初始化时读取 save_model/ckpt_epoch_50.pth把模型放进全局变量最后启动事件循环等待用户操作。# main.py结构说明源码里已经是完整实现 import sys from PyQt5.QtWidgets import QApplication from Qt_seg import QtSegMainWindow app QApplication(sys.argv) window QtSegMainWindow() window.show() sys.exit(app.exec_())这段逻辑本身不复杂主窗口类 QtSegMainWindow 被放在 Qt_seg.py 里UI 布局从 Qt_seg.ui 加载。论文里如果你要描述系统设计可以说“本系统采用 MVC 思想界面层由 Qt_seg.ui 定义控制层由 Qt_seg.py 实现模型层由 model 目录封装入口由 main.py 统一调度”这是答辩时很稳妥的讲法。跑起来验证一下模型推理是否正常这一步值得做因为 GUI 会挡住很多细节错误。直接用项目里的 test.py 跑一张图片python test.py --image demo.jpg --ckpt save_model/ckpt_epoch_50.pth如果 test.py 里没写 argparse那就直接改脚本内部默认的图片路径。跑通以后再启动 main.py选图片、点推理按钮看看分割结果能不能正常显示。这一步能让你快速区分“模型有问题”还是“GUI 有问题”避免两头一起排查。3.4 GUI 操作上传图片、推理与结果存储启动系统后界面通常是这样的流程点“打开图片”按钮弹出文件选择框选一张 JPEG 或 PNG 图片图片加载到界面左侧点“开始分割”按钮程序把图片传给模型做前向推理再将生成的彩色分割掩码叠加显示在右侧。内部的推理过程一般涉及三步读图、预处理、模型推理、后处理着色。预处理的核心是把输入 Resize 到模型要求的尺寸然后归一化后处理是把模型输出的概率图取最大类别索引再通过调色板映射成可见的彩色图。下面是这个流程最常见的实现方式# 图像预处理与推理过程常见写法具体以源码为准 import torch from PIL import Image from LDN_transforms import get_transforms transform get_transforms(size(512, 512)) img Image.open(test.jpg).convert(RGB) img_tensor transform(img).unsqueeze(0) # 加 batch 维度 with torch.no_grad(): logits model(img_tensor) # 模型前向输出 pred torch.argmax(logits, dim1).squeeze(0) # 取类别索引这里有三点要说清楚transform 里的 size 参数决定了输入图像分辨率512x512 是分割任务的常见配置显存不够可以改成 320x320但分割边界会变粗.unsqueeze(0) 是为图片加上 batch 维模型要求输入是 NCHW 格式的四维张量torch.argmax 表示对每个像素选择概率最大的类别编号这是分割输出的标准做法。save 逻辑一般用分调色板的方式给类别编号上色你可以改调色板来区分前景和背景尽量选对比度大的颜色效果演示时更直观。4. 部署避坑中文路径、PyQt5 控件绑定与模型加载三个翻车场景4.1 中文路径导致模型和 UI 双双加载失败现象双击 main.py 后窗口能弹出来但一点“开始分割”按钮就报错要么是 FileNotFoundError 找不到权重文件要么是 torch.load 提示 EOFError 或者 UnicodeDecodeError还有的项目在启动阶段就直接死在加载 .ui 文件这一步报错信息里夹杂乱码。原因Windows 默认编码 GBKPython 默认字符串编码 UTF-8路径里一旦出现中文os 模块还能勉强处理但 C 扩展层比如 torch 和 Qt 的部分组件对宽字符路径的兼容性很差会出现识别错乱。这不是代码逻辑问题是运行时环境的编码问题。解决按 3.2 节的做法把整个项目挪到纯英文路径下目录层次不要太深比如 C:/seg_project同时把 Windows 系统区域设置里的“Beta 版使用 Unicode UTF-8 提供全球语言支持”打开。如果不想改系统设置就用短路径名重命名这是最稳妥的办法。从那以后我拿到任意带 GUI 的深度学习项目第一件事就是检查路径里有没有中文。4.2 PyQt5 控件信号槽没连接按钮点了没反应现象界面按钮可以点击但点击后什么都没有发生控制台也没有任何报错。初学者容易以为程序卡死了实际上是界面的按钮信号没有和对应的方法绑定。原因用 Qt Designer 设计的 .ui 文件只是界面布局按钮的 clicked 信号需要代码里手动 connect 到处理函数。如果源码的 Qt_seg.py 里漏了 connect 语句按钮自然没有行为。还有一种情况是 connect 了但处理函数的参数数量和信号不匹配PyQt5 会静默忽略异常。解决检查 Qt_seg.py 中所有的按钮绑定代码。常见写法是 self.pushButton.clicked.connect(self.on_open_image)其中 clicked 是无参信号处理函数就不能接收额外参数。如果你的按钮是从 .ui 加载的先确认 objectName 和代码中引用的名字完全一致包括大小写。我一般会在启动脚本里加一个断言来排查assert hasattr(window, pushButton), UI 中找不到 pushButton请检查 objectName4.3 高版 PyTorch 加载旧权重报错weights_only 的坑现象加载 ckpt_epoch_50.pth 时在 PyTorch 2.6 及以上环境报错 RuntimeError: Weights only load failed, this file can still be loaded with weights_onlyFalse但在 PyTorch 2.0 环境却完全正常。原因PyTorch 从 2.6 开始把 torch.load 默认参数 weights_only 改为 True目的是防止反序列化任意代码被执行。这份项目的权重可能是用旧版 torch.save 保存的包含了非张量对象比如自定义类实例或字典配置默认模式下加载器不认。解决加载时显式指定 weights_onlyFalse。虽然这个参数名字看着像“不安全”但对本地可信权重文件来说没有风险。注意这只影响加载逻辑不影响网络结构定义state_dict torch.load(save_model/ckpt_epoch_50.pth, map_locationcpu, weights_onlyFalse)如果不方便改项目源码下一行加在这也能缓解。除此之外还有一个坑作者保存的可能不是纯 state_dict而是整个模型对象或者包含 optimizer 状态的字典加载时要从 dict 里再取一次 model 或 state_dict 键名用 print(state_dict.keys()) 看一眼就知道。4.4 CPU 机器推理卡住没有 GPU 时的应急策略现象在只有 CPU 的电脑上启动 GUI 后导入图片点推理按钮界面直接卡住转圈很久才出结果甚至直接未响应。原因DFormer 这类带 Transformer 结构的模型参数量不小如果输入原图直接进网络计算量会非常夸张。CPU 推理单张 512x512 的图片可能要几十秒到几分钟期间 GUI 事件循环被阻塞看起来就是假死。解决先在 test.py 里用一张小图比如 256x256验证模型能跑通再把 GUI 推理的输入尺寸调小。另一个实用技巧是把推理放进 QThread 子线程不阻塞主界面的事件循环。就算你不会写 QThread至少要在推理前加一行打印日志方便判断卡在哪一层。如果只是演示用 DFormer 的小版本配置替代默认大模型也能显著提速。4.5 排查顺序经验遇到问题先按四步走阶段一先确认环境torch.cuda.is_available() 是不是 Truepil 能不能 import阶段二跑 test.py 纯命令行推理判定模型链路是否正常阶段三跑 main.py 不带模型加载判定界面是否正常阶段四两者都正常再合并。这四步都不需要改源码只是通过运行顺序来缩小问题半径。很多同学一上来就怀疑“代码写得有问题”我发现 90% 的报错集中在环境版本冲突、路径编码、参数类型不匹配这三种。检查顺序不对后面会越改越乱。我自己的习惯是拿到项目先复制一份干净备份再在这一圈里进行排错改错了好恢复不用从头再解压。5. 进阶玩法把 mini_dataset.py 和 create_seg_map.py 用起来做出自己的分割数据集5.1 理解数据管线原图和标签掩码怎么对齐前面的推理只是拿现成模型跑图如果你要拿这套系统做毕设创新点必须动数据。源码里 mini_dataset.py 是个数据加载器create_seg_map.py 是生成分割标签的脚本这两个文件配合使用目的是把你自己收集的图片转换成模型能用的数据集格式。语义分割训练需要两张图一张是原始 RGB 图一张是和原图尺寸一致的标签图每个像素值是类别编号。整型标签不能直接拿去给交叉熵损失用一般在训练时会把长边缩放、随机裁剪、翻转都一起处理好。# mini_dataset.py 逻辑骨架源码已实现完整类这里展示数据流 class MiniSegDataset(Dataset): def __init__(self, image_dir, mask_dir, transformNone): self.image_dir image_dir self.mask_dir mask_dir self.images sorted(glob.glob(image_dir /*.jpg)) self.masks [p.replace(images, masks).replace(.jpg, .png) for p in self.images] self.transform transform def __getitem__(self, idx): img Image.open(self.images[idx]).convert(RGB) mask Image.open(self.masks[idx]).convert(L) if self.transform: img, mask self.transform(img, mask) # 同步增强 mask torch.as_tensor(np.array(mask), dtypetorch.long) return img, mask数据管线里有个最容易被忽视的设计mask 用“L”模式读取也就是灰度模式每个像素值直接就是类别编号不需要 one-hot在损失函数里再处理。而做同步增强的时候图像和掩码要做完全一样的几何变换翻转、缩放否则训练时空间不对齐模型怎么学都学不会。很多初学者把原图做了 RandomResizedCrop掩码却只做 Resize结果训练指标极其诡异这种问题就属于数据预处理环节的踩坑。5.2 用 create_seg_map.py 把杂乱标注转成训练格式最常见的做法是准备一张图片和一张掩码图掩码图里用不同颜色表示不同物体比如红色像素代表“车”绿色像素代表“树”。create_seg_map.py 做的事情就是把这类彩色标签图映射成类别编号。常见的有两种映射方式一种是按调色板颜色索引映射还有一种是按灰度值直接映射。如果你用 Labelme 这类工具标注导出的 json 还需要先转成 PNG再调 create_seg_map.py 转成训练格式。# 将彩色掩码映射为类别 id按颜色字典映射 color2id { (0, 0, 0): 0, # 背景 (255, 0, 0): 1, # 车 (0, 255, 0): 2, # 树 (255, 255, 255): 3, # 路 } mask np.array(Image.open(label.png).convert(RGB)) seg_map np.zeros(mask.shape[:2], dtypenp.uint8) for color, cls_id in color2id.items(): seg_map[(mask color).all(axis-1)] cls_id Image.fromarray(seg_map, modeL).save(train_mask.png)关键在 .all(axis-1) 这一步它表示只有在 RGB 三个通道都匹配时才认为该像素属于这个类别避免颜色相近导致误判。如果发现生成的 seg_map 里全是 0先查你的掩码图是不是被某种软件自动转换成了索引模式导致实际颜色和预想不一致直接在代码里打印颜色列表对比即可。5.3 基于 ckpt_epoch_50.pth 做迁移学习不从头训练也能出结果毕设场景里拿一个 50 epoch 的权重直接在自己的小数据集上继续微调比从头训练要稳得多。加载预训练权重后替换掉最后的分类头。不同分割头的分类头输出维度取决于类别数比如原来训练时是 21 类你现在只有 4 类需要把最后的卷积层输出改为 4再冻结骨干网络的部分参数只训练分割头。# 迁移学习关键片段替换分类头并冻结骨干 model.load_state_dict(torch.load(ckpt, map_locationcpu), strictFalse) for name, param in model.named_parameters(): if decode_head not in name: param.requires_grad False # 修改分类头输出维度为 4 model.decode_head.conv_last nn.Conv2d(256, 4, kernel_size1)strictFalse 是关键因为预训练权重里包含原来 21 类的分类头参数你替换成 4 类后参数名对不上不写这个参数就会直接报 missing key。冻结骨干只训练 head 的做法在小数据集上既快又不容易过拟合。如果是显存不足的用户还建议把训练尺寸降到 320x320batch size 设为 4学习率用 0.001 起步在验证集上观察 mIoU 的上升速度涨不动就降到 0.0003 再跑几个 epoch。我每次拿到一个带预训练模型的毕设项目都会习惯性做三件事先确认权重能不能加载、再拿一张和训练集分布不同的测试图看推理效果、最后看 decode_head 的实现细节判断这个模型的调参空间。这种先跑通再改的流程能帮你把时间花在真正有产出的地方。希望这次拆解对你有点帮助。本文还有配套的精品资源点击获取
返回列表