一、项目背景⭐

        本项目是“从零开始学习深度学习”系列中的第二个实战项目,旨在实现第一个简易App(图像分类任务——水果分类),进一步地落地AI模型应用,帮助初学者初步了解模型落地。

        基于PyQt5图形界面的水果图像分类系统,用户可以通过加载模型、选择图像并一键完成图像识别。

二、项目目标🚀:

        基于PyQt5图形界面实现以下功能:

  • 加载本地 .pth 训练好的模型;

  • 加载本地图像进行展示;

  • 自动完成图像预处理(Resize、ToTensor、Normalize);

  • 使用模型完成预测并展示结果;

  • 界面美观,交互友好。

三、适合人群🫵:

  • 深度学习零基础或刚入门的学习者
  • 希望通过项目实战学习BP神经网络、卷积神经网络模型搭建的开发者
  • 对图像识别、分类应用感兴趣的童鞋
  • 适用于想学习通过界面实现AI模型推理,

四、项目实战✊:

1.主界面构建

    def initUI(self):# 主窗口设置self.setWindowTitle("水果分类应用")self.setGeometry(100, 100, 800, 600)# 创建主窗口部件central_widget = QWidget()self.setCentralWidget(central_widget)# 创建主布局main_layout = QVBoxLayout()# 模型选择部分model_layout = QHBoxLayout()model_label = QLabel("模型路径:")self.model_path_edit = QtWidgets.QLineEdit()model_button = QPushButton("选择模型")model_button.clicked.connect(self.select_model_path)self.load_model_button = QPushButton("加载模型")self.load_model_button.clicked.connect(self.load_model)self.load_model_button.setEnabled(False)model_layout.addWidget(model_label)model_layout.addWidget(self.model_path_edit)model_layout.addWidget(model_button)model_layout.addWidget(self.load_model_button)main_layout.addLayout(model_layout)# 图像显示部分self.image_label = QLabel()self.image_label.setAlignment(QtCore.Qt.AlignCenter)self.image_label.setMinimumSize(600, 400)main_layout.addWidget(self.image_label)# 图像选择部分image_layout = QHBoxLayout()image_path_label = QLabel("图像路径:")self.image_path_edit = QtWidgets.QLineEdit()image_select_button = QPushButton("选择图像")image_select_button.clicked.connect(self.select_image_path)self.predict_button = QPushButton("分类预测")self.predict_button.clicked.connect(self.predict_image)self.predict_button.setEnabled(False)image_layout.addWidget(image_path_label)image_layout.addWidget(self.image_path_edit)image_layout.addWidget(image_select_button)image_layout.addWidget(self.predict_button)main_layout.addLayout(image_layout)# 结果显示部分self.result_label = QLabel("请先加载模型并选择图像")self.result_label.setAlignment(QtCore.Qt.AlignCenter)self.result_label.setStyleSheet("font-size: 20px")main_layout.addWidget(self.result_label)central_widget.setLayout(main_layout)

2.功能辅助函数

    def select_model_path(self):file_path, _ = QFileDialog.getOpenFileName(self,"选择模型文件","","Pytorch模型 (*.pth);;所有文件(*)")if file_path:self.model_path_edit.setText(file_path)self.load_model_button.setEnabled(True)def load_model(self):model_path = self.model_path_edit.text()if not model_path:returntry:# 模型类型(根据你的模型的时间需求进行修改)self.model = FruitClassificationModelResnet18(4)self.model.load_state_dict(torch.load(model_path, map_location=self.device, weights_only=False))self.model = self.model.to(self.device)self.model.eval()self.result_label.setText("模型加载成功!请选择图像进行预测.")self.predict_button.setEnabled(True)except Exception as e:self.result_label.setText(f"模型加载失败: {str(e)}")self.model = Noneself.predict_button.setEnabled(False)def select_image_path(self):file_path, _ = QFileDialog.getOpenFileName(self,"选择图像文件","","图像文件 (*bmp *.png *.jpg *.jpeg);;所有文件(*)")if file_path:self.image_path_edit.setText(file_path)self.display_image(file_path)def display_image(self, file_path):pixmap = QtGui.QPixmap(file_path)if not pixmap.isNull():scaled_pixmap = pixmap.scaled(self.image_label.size(),QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation)self.image_label.setPixmap(scaled_pixmap)else:self.image_label.setText("无法加载图像")def preprocess_image(self, image_path):try:# 定义图像预处理流程transform = transforms.Compose([transforms.Resize((224, 224)),  # 调整图像大小为224x224transforms.ToTensor(),  # 转换为Tensor格式transforms.Normalize([0.485, 0.456, 0.406],  # 标准化均值(ImageNet数据集)[0.229, 0.224, 0.225])  # 标准化标准差])# 打开图像文件image = Image.open(image_path)# 如果图像不是RGB模式,转换为RGBif image.mode != "RGB":image = image.convert("RGB")# 应用预处理变换并添加batch维度(unsqueeze(0)),然后移动到指定设备image = transform(image).unsqueeze(0).to(self.device)return imageexcept Exception as e:self.result_label.setText(f"图像预处理失败: {str(e)}")return None

3.加载模型

    def load_model(self):model_path = self.model_path_edit.text()if not model_path:returntry:# 模型类型(根据你的模型的时间需求进行修改)self.model = FruitClassificationModelResnet18(4)self.model.load_state_dict(torch.load(model_path, map_location=self.device, weights_only=False))self.model = self.model.to(self.device)self.model.eval()self.result_label.setText("模型加载成功!请选择图像进行预测.")self.predict_button.setEnabled(True)except Exception as e:self.result_label.setText(f"模型加载失败: {str(e)}")self.model = Noneself.predict_button.setEnabled(False)

4.预测函数

    def predict_image(self):if not self.model:self.result_label.setText("请先加载模型")returnimage_path = self.image_path_edit.text()if not image_path:self.result_label.setText("请选择图像")returninput_tensor = self.preprocess_image(image_path)if input_tensor is None:return# 预测with torch.no_grad():input_tensor = input_tensor.to(self.device)outputs = self.model(input_tensor)_, predicted = torch.max(outputs.data, 1)class_id = predicted.item()# 显示结果class_names = ['Apple', 'Banana', 'Orange', 'Pinenapple']  # 示例类别  根据你的模型进行修改if class_id < len(class_names):self.result_label.setText(f"预测结果: {class_names[class_id]}")else:self.result_label.setText(f"预测结果: 未知类别 ({class_id})")QtWidgets.QApplication.processEvents()

6.完整实现代码

import cv2
import sys
import numpy as np
import torch
import torchvision.transforms as transforms
from PIL import Image
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtWidgets import QFileDialog, QLabel, QPushButton, QVBoxLayout, QWidget, QHBoxLayout
from model import FruitClassificationModelResnet18class FruitClassificationApp(QtWidgets.QMainWindow):def __init__(self):super().__init__()self.model = Noneself.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")self.initUI()def initUI(self):# 主窗口设置self.setWindowTitle("水果分类应用")self.setGeometry(100, 100, 800, 600)# 创建主窗口部件central_widget = QWidget()self.setCentralWidget(central_widget)# 创建主布局main_layout = QVBoxLayout()# 模型选择部分model_layout = QHBoxLayout()model_label = QLabel("模型路径:")self.model_path_edit = QtWidgets.QLineEdit()model_button = QPushButton("选择模型")model_button.clicked.connect(self.select_model_path)self.load_model_button = QPushButton("加载模型")self.load_model_button.clicked.connect(self.load_model)self.load_model_button.setEnabled(False)model_layout.addWidget(model_label)model_layout.addWidget(self.model_path_edit)model_layout.addWidget(model_button)model_layout.addWidget(self.load_model_button)main_layout.addLayout(model_layout)# 图像显示部分self.image_label = QLabel()self.image_label.setAlignment(QtCore.Qt.AlignCenter)self.image_label.setMinimumSize(600, 400)main_layout.addWidget(self.image_label)# 图像选择部分image_layout = QHBoxLayout()image_path_label = QLabel("图像路径:")self.image_path_edit = QtWidgets.QLineEdit()image_select_button = QPushButton("选择图像")image_select_button.clicked.connect(self.select_image_path)self.predict_button = QPushButton("分类预测")self.predict_button.clicked.connect(self.predict_image)self.predict_button.setEnabled(False)image_layout.addWidget(image_path_label)image_layout.addWidget(self.image_path_edit)image_layout.addWidget(image_select_button)image_layout.addWidget(self.predict_button)main_layout.addLayout(image_layout)# 结果显示部分self.result_label = QLabel("请先加载模型并选择图像")self.result_label.setAlignment(QtCore.Qt.AlignCenter)self.result_label.setStyleSheet("font-size: 20px")main_layout.addWidget(self.result_label)central_widget.setLayout(main_layout)def select_model_path(self):file_path, _ = QFileDialog.getOpenFileName(self,"选择模型文件","","Pytorch模型 (*.pth);;所有文件(*)")if file_path:self.model_path_edit.setText(file_path)self.load_model_button.setEnabled(True)def load_model(self):model_path = self.model_path_edit.text()if not model_path:returntry:# 模型类型(根据你的模型的时间需求进行修改)self.model = FruitClassificationModelResnet18(4)self.model.load_state_dict(torch.load(model_path, map_location=self.device, weights_only=False))self.model = self.model.to(self.device)self.model.eval()self.result_label.setText("模型加载成功!请选择图像进行预测.")self.predict_button.setEnabled(True)except Exception as e:self.result_label.setText(f"模型加载失败: {str(e)}")self.model = Noneself.predict_button.setEnabled(False)def select_image_path(self):file_path, _ = QFileDialog.getOpenFileName(self,"选择图像文件","","图像文件 (*bmp *.png *.jpg *.jpeg);;所有文件(*)")if file_path:self.image_path_edit.setText(file_path)self.display_image(file_path)def display_image(self, file_path):pixmap = QtGui.QPixmap(file_path)if not pixmap.isNull():scaled_pixmap = pixmap.scaled(self.image_label.size(),QtCore.Qt.KeepAspectRatio,QtCore.Qt.SmoothTransformation)self.image_label.setPixmap(scaled_pixmap)else:self.image_label.setText("无法加载图像")def preprocess_image(self, image_path):try:# 定义图像预处理流程transform = transforms.Compose([transforms.Resize((224, 224)),  # 调整图像大小为224x224transforms.ToTensor(),  # 转换为Tensor格式transforms.Normalize([0.485, 0.456, 0.406],  # 标准化均值(ImageNet数据集)[0.229, 0.224, 0.225])  # 标准化标准差])# 打开图像文件image = Image.open(image_path)# 如果图像不是RGB模式,转换为RGBif image.mode != "RGB":image = image.convert("RGB")# 应用预处理变换并添加batch维度(unsqueeze(0)),然后移动到指定设备image = transform(image).unsqueeze(0).to(self.device)return imageexcept Exception as e:self.result_label.setText(f"图像预处理失败: {str(e)}")return Nonedef predict_image(self):if not self.model:self.result_label.setText("请先加载模型")returnimage_path = self.image_path_edit.text()if not image_path:self.result_label.setText("请选择图像")returninput_tensor = self.preprocess_image(image_path)if input_tensor is None:return# 预测with torch.no_grad():input_tensor = input_tensor.to(self.device)outputs = self.model(input_tensor)_, predicted = torch.max(outputs.data, 1)class_id = predicted.item()# 显示结果class_names = ['Apple', 'Banana', 'Orange', 'Pinenapple']  # 示例类别  根据你的模型进行修改if class_id < len(class_names):self.result_label.setText(f"预测结果: {class_names[class_id]}")else:self.result_label.setText(f"预测结果: 未知类别 ({class_id})")QtWidgets.QApplication.processEvents()if __name__ == '__main__':app = QtWidgets.QApplication(sys.argv)window = FruitClassificationApp()window.show()sys.exit(app.exec_())

五、学习收获🎁:

通过本次 PyTorch 与 PyQt5 的项目实战,不仅巩固了深度学习模型的使用方法,也系统地学习了如何将模型部署到图形界面中。以下是我的一些具体收获:

1️⃣ 深度学习模型部署实践

  • 学会了如何将 .pth 格式的模型加载到推理环境;

  • 熟悉了图像的预处理流程(如Resize、ToTensor、Normalize);

  • 掌握了 torch.no_grad() 推理模式下的使用,避免梯度计算加速推理。

2️⃣ PyQt5 图形界面开发

  • 掌握了 PyQt5 中常用的控件如 QLabelQPushButtonQLineEdit 等;

  • 学会了如何使用 QFileDialog 实现文件选择;

  • 了解了如何通过 QPixmap 加载并展示图像;

  • 熟悉了 QVBoxLayoutQHBoxLayout 进行界面布局。

3️⃣ 端到端流程整合

  • 实现了从模型加载 → 图像读取 → 图像预处理 → 推理 → 展示结果 的完整流程;

  • 初步理解了如何将 AI 模型变成一个用户可交互的软件;

  • 为后续构建更复杂的推理系统(如视频流识别、多模型切换)打下了基础。

注:完整代码,请私聊,免费获取。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/88743.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/88743.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/88743.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小架构step系列13:测试用例的加载

1 概述测试用例的编写要有一些基础的规范&#xff0c;在本文先定义文件名称和测试用例方法名的规范。2 文件加载原理先从源码来看一下测试用例的文件加载原理。2.1 文件的匹配主要是通过注解来扫描测试用例。// 在IDEA测试用例启动时&#xff0c;调用junit-platform-launcher-x…

K8S的CNI之calico插件升级至3.30.2

前言宿主机ping不通K8S的pod&#xff0c;一直存在丢包的现象&#xff0c;排查了防火墙、日志、详细信息等没发现什么问题&#xff0c;最后搜索发现&#xff0c;是因为把K8S的版本升级之后&#xff0c;旧版本的CNI插件不适配原因导致的&#xff0c;于是就把calico也一并升级并且…

Spring Boot RESTful API 设计指南:查询接口规范与最佳实践

Spring Boot RESTful API 设计指南&#xff1a;查询接口规范与最佳实践 引言 在 Spring Boot 开发中&#xff0c;查询接口的设计直接影响着系统的可用性、可维护性和性能。本文将深入探讨如何规范设计查询接口&#xff0c;包括 GET/POST 的选择、参数定义、校验规则等&#xff…

ctfshow萌新题集

记录一下前半部分是能自己写出来的&#xff0c;后半部分是需要提示的&#xff0c;感觉自己归来两年仍是萌新 misc部分 知识点 base家族密文特征 Base16 (Hex) 字符集&#xff1a;0-9, A-F&#xff08;不区分大小写&#xff09;。特征&#xff1a; 长度是 2 的倍数&#xff…

2025年语言处理、大数据与人机交互国际会议(DHCI 2025)

&#x1f310;&#x1f916;&#x1f9e0; 语言处理、大数据与人机交互&#xff1a;探索智能未来 —— DHCI 2025国际会议2025年语言处理、大数据与人机交互国际会议&#xff08;DHCI 2025&#xff09; 将于2025年在中国重庆市召开。这次盛会将汇聚全球顶尖专家、学者及行业领袖…

RIP实验以及核心原理

RIP&#xff08;Routing Information Protocol&#xff0c;路由信息协议&#xff09;是一种内部网关协议&#xff0c;基于距离矢量算法&#xff0c;用于在自治系统内交换路由信息。RIP 核心原理距离矢量算法&#xff1a;RIP 使用跳数作为路径选择的唯一度量标准。每经过一个路由…

基于大数据的电力系统故障诊断技术研究

摘要本文提出了一种创新性的基于大数据技术的电力系统故障诊断方法&#xff0c;该方法通过整合先进的机器学习算法和交互式可视化技术&#xff0c;实现了对电力系统各类故障的智能化识别与深度分析。该系统采用随机森林算法作为核心分类器&#xff0c;构建了高精度的故障分类模…

MySQL 分区功能应用专门实现全方位详解与示例

MySQL 分区功能允许将表的数据分散存储在不同的物理分区中,同时保持逻辑上的单一表结构。下面我将从基础概念到高级应用,全面讲解 MySQL 分区实现。 一、分区核心作用 1. 性能提升 分区剪枝(Partition Pruning):查询时自动跳过不相关的分区,减少数据扫描量 并行处理:不…

汽车功能安全-嵌入式软件测试(软件合格性测试)【目的、验证输入、集成验证要求】11

文章目录1 嵌入式软件测试&#xff08;Testing of the embedded Software&#xff09;2 测试输入3 验证要求和建议3.1 测试环境3.2 测试方法3.2.1 基于需求的测试3.2.2 故障注入测试3.2.3 两种方法的区别与联系总结3.3 测试用例导出方法4 嵌入式软件的测试结果评价5 测试输出物…

【webrtc】gcc当前可用码率1:怎么决策的

【webrtc】当前最大码率是怎么决策的1 看日志,跟踪代码最大码率 是probe的上限 默认值很大 外部设置的较小,调用堆栈 无限大作为默认值 默认是无限大,所以使用预设值 【webrtc】码率设定中的 int64_t 的无限大

UE5 C++计时器

UE5 C计时器 计时器一&#xff1a; .h文件 FTimerHandle TimerHandle_BetweenShot;//定义时间句柄 void StartFire();void EndFire();.cpp文件 #include “TimerManager.h” void ASpaceShip::StartFire() {GetWorldTimerManager().SetTimer(TimerHandle_BetweenShot, this, &a…

【hivesql 已知维度父子关系加工层级表】

这里写自定义目录标题1. 维度表示例1.1清单表1.2层级表2.从清单表加工层级表2.1 注意点2.2 加工方式&#xff08;join&#xff09;2.3 使用函数3.清单表字段加工3.1通过上级编码信息加工级别信息3.2 通过级别信息&#xff0c;加工上级编码信息4.创建维度表的一般注意点1. 维度表…

Ubuntu重装系统后ssh连接不上(遇到 ​​“Unit ssh.service not found“​​ 错误)

重装系统时不知道为什么SSH 服务未安装&#xff0c;以下是解决方案&#xff1a;先检查ssh服务安装没安装 sudo systemctl status ssh # Ubuntu/Debian如果 systemctl 找不到服务&#xff0c;可能是 SSH 未安装&#xff1a;sudo apt update sudo apt install openssh-serve…

2025社交电商新风口:推客小程序的商业逻辑与技术实现

一、推客小程序市场前景与商业价值在当今社交电商蓬勃发展的时代&#xff0c;推客小程序已成为连接商家与消费者的重要桥梁。推客模式结合了社交传播与电商变现的双重优势&#xff0c;通过用户自发分享带来裂变式增长&#xff0c;为商家创造了全新的营销渠道。推客小程序的核心…

Go 单元测试进阶:AI 加持下的高效实践与避坑指南

单元测试的必要性与基础单元测试不仅是保障代码质量的手段&#xff0c;也是优秀的设计工具和文档形式&#xff0c;对软件开发具有重要意义。另一种形式的文档&#xff1a;好的单元测试是一种活文档&#xff0c;能清晰展示代码单元的预期用途和行为&#xff0c;有时比注释更有用…

VScode SSH远程连接Ubuntu(通过SSH密钥对的方式)

我们都知道在VScode上通过SSH插件的方式可以远程连接到虚拟机的Ubuntu系统&#xff0c;这样开发者就可以在Windows下的Vscode编译器下直接远程连接Ubuntu&#xff0c;这种方式是 “用 Windows 的便捷性操作 Linux 的专业性”—— 既保留了Windows系统的易用性和VS Code的强大功…

学术绘图(各种神经网络)

23种神经网络设计&可视化工具汇总 下面做简要罗列&#xff0c;具体请看相关链接 1.draw_convnet Github: https://github.com/gwding/draw_convnet​ star 数量&#xff1a;1.7k​ 这个工具最后一次更新是2018年的时候&#xff0c;一个Python脚本来绘制卷积神经网络的工…

Redis的高可用性与集群架构

Redis的高可用性与集群架构 引言&#xff1a;解释高可用性的重要性及Redis如何实现主从复制&#xff08;Replication&#xff09; 原理&#xff1a;异步复制&#xff0c;主从数据同步配置方法优缺点分析 哨兵模式&#xff08;Sentinel&#xff09; 功能&#xff1a;监控、通知、…

TCP的连接

TCP 三次握手过程是怎样的&#xff1f;TCP 是面向连接的协议&#xff0c;所以使用 TCP 前必须先建立连接&#xff0c;而建立连接是通过三次握手来进行的。三次握手的过程如下图&#xff1a;一开始&#xff0c;客户端和服务端都处于 CLOSE 状态。先是服务端主动监听某个端口&…