分享一套锋哥原创的基于PyTorch的手写数字识别系统(深度学习+PyQt6+LetNet-5)
大家好我是Java1234_小锋老师分享一套锋哥原创的基于PyTorch的手写数字识别系统(深度学习PyQt6LetNet-5)项目介绍手写数字识别是模式识别与计算机视觉领域的经典问题在邮政分拣、票据录入、教育测评、表单数字化等场景中具有重要应用价值。随着深度学习技术的快速发展卷积神经网络在图像分类任务中展现出优异性能。本文围绕“基于PyTorch的手写数字识别系统”这一课题设计并实现了一套面向桌面端的可视化应用系统完成了从模型训练到实时识别的完整闭环。系统以 Python 为主要开发语言采用 PyTorch 深度学习框架构建改进版 LeNet-5 卷积神经网络并在经典 MNIST 手写数字数据集上完成训练与评估前端交互层基于 PyQt6 实现集成首页介绍、模型训练、数字识别与关于说明等功能模块。训练模块支持在图形界面中配置训练轮数、批次大小与学习率通过 QThread 在后台执行训练任务并利用 Matplotlib 实时绘制损失与准确率曲线识别模块支持鼠标手写与本地图片上传两种输入方式经过灰度化、反色、裁剪、缩放与归一化等预处理后送入模型推理输出预测数字及各类别置信度。在系统设计方面本文采用分层架构思想将用户交互、业务逻辑、模型计算与数据存储进行解耦在功能设计方面形成介绍、训练、识别、关于四大模块在实现方面重点解决了训练不阻塞界面、手写输入采集、上传图片自适应预处理、最优模型保存与结果概率可视化等问题。论文同步给出系统架构图、功能结构图、模块结构图、网络结构图、流程图、时序图、用例图等多类图示增强了设计表达的规范性与可读性。实验结果表明所设计的改进版 LeNet-5 模型在 MNIST 测试集上可取得较高识别准确率系统界面操作简便、流程清晰能够满足本科毕业设计对功能完整性、技术综合性与可演示性的要求。本文工作对理解卷积神经网络原理、掌握 PyTorch 训练流程以及运用 PyQt6 构建桌面应用具有较好的实践意义也可作为后续扩展多位数字识别、模型对比实验或 Web 化部署的基础平台。源码下载链接: https://pan.baidu.com/s/1EIy2zxFCAHBxNn89tPg8Uw?pwd1234提取码: 1234系统展示核心代码数字识别页面。 from io import BytesIO from pathlib import Path from PyQt6.QtCore import Qt, QByteArray from PyQt6.QtGui import QImage, QPixmap from PyQt6.QtWidgets import ( QFileDialog, QFrame, QGridLayout, QHBoxLayout, QLabel, QMessageBox, QPushButton, QVBoxLayout, QWidget, ) from PIL import Image from app.core.predictor import Predictor from app.widgets.draw_canvas import DrawCanvas class RecognizePage(QWidget): 数字识别页面支持手写画板与图片上传两种输入方式。 def __init__(self, project_root: Path, parentNone): 初始化识别页。 Args: project_root: 项目根目录 parent: 父控件 super().__init__(parent) self.project_root project_root self.model_path project_root / models / best_model.pth self.predictor Predictor(self.model_path) self._uploaded_image: Image.Image | None None self._init_ui() def _init_ui(self): 构建识别页界面。 layout QVBoxLayout(self) layout.setContentsMargins(32, 24, 32, 24) layout.setSpacing(16) title QLabel(数字识别) title.setObjectName(pageTitle) subtitle QLabel(在画板上手写数字或上传本地图片进行识别) subtitle.setObjectName(subtitle) layout.addWidget(title) layout.addWidget(subtitle) content_layout QHBoxLayout() content_layout.setSpacing(24) # 左侧输入区域 input_frame QFrame() input_frame.setObjectName(card) input_layout QVBoxLayout(input_frame) input_layout.setContentsMargins(20, 20, 20, 20) input_layout.setSpacing(12) input_title QLabel(输入区域) input_title.setStyleSheet(font-weight: bold; font-size: 15px;) input_layout.addWidget(input_title) # 手写画板 canvas_label QLabel(鼠标手写:) self.draw_canvas DrawCanvas(280, 280) input_layout.addWidget(canvas_label) input_layout.addWidget(self.draw_canvas, alignmentQt.AlignmentFlag.AlignCenter) canvas_btn_layout QHBoxLayout() self.clear_btn QPushButton(清空画板) self.clear_btn.setObjectName(secondaryButton) self.clear_btn.clicked.connect(self._clear_canvas) canvas_btn_layout.addWidget(self.clear_btn) input_layout.addLayout(canvas_btn_layout) # 分隔线 sep QFrame() sep.setFrameShape(QFrame.Shape.HLine) sep.setStyleSheet(color: #e2e8f0;) input_layout.addWidget(sep) # 上传图片 upload_label QLabel(或上传图片:) self.upload_preview QLabel(暂无图片) self.upload_preview.setFixedSize(140, 140) self.upload_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) self.upload_preview.setStyleSheet( border: 1px dashed #cbd5e1; border-radius: 8px; background: #f8fafc; ) upload_btn_layout QHBoxLayout() self.upload_btn QPushButton(选择图片) self.upload_btn.setObjectName(secondaryButton) self.upload_btn.clicked.connect(self._upload_image) upload_btn_layout.addWidget(self.upload_btn) input_layout.addWidget(upload_label) input_layout.addWidget(self.upload_preview, alignmentQt.AlignmentFlag.AlignCenter) input_layout.addLayout(upload_btn_layout) # 识别按钮 self.recognize_btn QPushButton(开始识别) self.recognize_btn.setObjectName(primaryButton) self.recognize_btn.clicked.connect(self._recognize) input_layout.addWidget(self.recognize_btn) content_layout.addWidget(input_frame) # 右侧结果区域 result_frame QFrame() result_frame.setObjectName(card) result_layout QVBoxLayout(result_frame) result_layout.setContentsMargins(20, 20, 20, 20) result_layout.setSpacing(12) result_title QLabel(识别结果) result_title.setStyleSheet(font-weight: bold; font-size: 15px;) result_layout.addWidget(result_title) # 预测数字 self.digit_label QLabel(-) self.digit_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.digit_label.setStyleSheet( font-size: 96px; font-weight: bold; color: #2563eb; padding: 20px; ) result_layout.addWidget(self.digit_label) self.confidence_label QLabel(置信度: -) self.confidence_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.confidence_label.setStyleSheet(font-size: 16px; color: #64748b;) result_layout.addWidget(self.confidence_label) # 预处理后图像预览 pre_label QLabel(预处理后图像 (28×28):) self.preprocessed_preview QLabel() self.preprocessed_preview.setFixedSize(112, 112) self.preprocessed_preview.setAlignment(Qt.AlignmentFlag.AlignCenter) self.preprocessed_preview.setStyleSheet( border: 1px solid #e2e8f0; border-radius: 8px; background: #000000; ) result_layout.addWidget(pre_label) result_layout.addWidget( self.preprocessed_preview, alignmentQt.AlignmentFlag.AlignCenter ) # 概率条形图 prob_title QLabel(各类别概率:) prob_title.setStyleSheet(font-weight: bold; margin-top: 8px;) result_layout.addWidget(prob_title) self.prob_grid QGridLayout() self.prob_grid.setSpacing(4) self.prob_bars: list[QLabel] [] self.prob_labels: list[QLabel] [] for i in range(10): digit_lbl QLabel(str(i)) digit_lbl.setFixedWidth(20) digit_lbl.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) bar_lbl QLabel() bar_lbl.setFixedHeight(18) bar_lbl.setStyleSheet(background: #e2e8f0; border-radius: 4px;) prob_lbl QLabel(0.0%) prob_lbl.setFixedWidth(55) prob_lbl.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter) self.prob_grid.addWidget(digit_lbl, i, 0) self.prob_grid.addWidget(bar_lbl, i, 1) self.prob_grid.addWidget(prob_lbl, i, 2) self.prob_bars.append(bar_lbl) self.prob_labels.append(prob_lbl) result_layout.addLayout(self.prob_grid) result_layout.addStretch() content_layout.addWidget(result_frame, stretch1) layout.addLayout(content_layout) self._update_model_status() def _update_model_status(self): 更新模型加载状态提示。 if self.predictor.is_loaded: self.recognize_btn.setEnabled(True) self.recognize_btn.setText(开始识别) else: self.recognize_btn.setEnabled(False) self.recognize_btn.setText(请先训练模型) def refresh_model(self): 刷新模型训练完成后调用。 self.predictor.load_model(self.model_path) self._update_model_status() def _clear_canvas(self): 清空画板。 self.draw_canvas.clear() self._uploaded_image None self.upload_preview.setText(暂无图片) self.upload_preview.setPixmap(QPixmap()) def _upload_image(self): 上传本地图片。 file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , 图片文件 (*.png *.jpg *.jpeg *.bmp *.gif), ) if file_path: self._uploaded_image Image.open(file_path) pixmap QPixmap(file_path).scaled( 130, 130, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) self.upload_preview.setPixmap(pixmap) self.upload_preview.setText() def _recognize(self): 执行数字识别。 if not self.predictor.is_loaded: QMessageBox.warning(self, 提示, 模型未加载请先在「模型训练」页面完成训练。) return # 优先使用上传图片否则使用画板内容 if self._uploaded_image is not None: input_image self._uploaded_image elif self.draw_canvas.has_content(): input_image self.draw_canvas.to_pil_image() else: QMessageBox.information(self, 提示, 请先在画板上手写数字或上传一张图片。) return try: digit, probs, processed self.predictor.predict(input_image) confidence probs[digit] * 100 self.digit_label.setText(str(digit)) self.confidence_label.setText(f置信度: {confidence:.1f}%) # 显示预处理后图像 buffer BytesIO() processed.save(buffer, formatPNG) qimage QImage.fromData(QByteArray(buffer.getvalue())) preview_pixmap QPixmap.fromImage(qimage).scaled( 100, 100, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, ) self.preprocessed_preview.setPixmap(preview_pixmap) # 更新概率条 max_prob max(probs) if probs else 1.0 for i, (bar, lbl) in enumerate(zip(self.prob_bars, self.prob_labels)): ratio probs[i] / max_prob if max_prob 0 else 0 width max(1, int(ratio * 200)) color #2563eb if i digit else #94a3b8 bar.setStyleSheet( fbackground: {color}; border-radius: 4px; min-width: {width}px; ) bar.setFixedWidth(width) lbl.setText(f{probs[i]*100:.1f}%) except Exception as exc: QMessageBox.critical(self, 识别失败, f识别过程出错: {exc})基于PyTorch的手写数字识别系统 - 程序入口。 import sys from pathlib import Path from PyQt6.QtWidgets import QApplication from app.main_window import MainWindow def main(): 启动应用程序。 project_root Path(__file__).resolve().parent (project_root / data).mkdir(exist_okTrue) (project_root / models).mkdir(exist_okTrue) (project_root / assets).mkdir(exist_okTrue) app QApplication(sys.argv) app.setApplicationName(基于PyTorch的手写数字识别系统) window MainWindow(project_root) window.show() sys.exit(app.exec()) if __name__ __main__: main()

相关新闻

Android CameraServer PreviewFrameSpacer梳理

Android CameraServer PreviewFrameSpacer梳理

PreviewFrameSpacer 设计与实现 文件位置: device3/PreviewFrameSpacer.h / PreviewFrameSpacer.cpp 1. 问题背景 理想情况下,预览帧应按传感器输出节奏呈现给用户。但实际中,CPU 负载、请求设置差异等因素导致帧处理延迟不均匀,造成预览帧间距忽大忽小,用户感知到卡顿。…

2026/9/25 21:45:40 阅读更多 →
主流在线学习平台AI功能深度测评与选择指南

主流在线学习平台AI功能深度测评与选择指南

1. 项目概述:在线学习平台测评的价值与意义 在终身学习时代,继续教育已经成为职场人士的刚需。作为一名在线教育行业从业者,我经常被问到一个问题:"市面上这么多AI课程平台,到底哪个最适合我?"这…

2026/9/30 15:39:58 阅读更多 →
携手阿里云全国总经销,解锁企业数字化转型新动能

携手阿里云全国总经销,解锁企业数字化转型新动能

引言:企业上云,为何选择“总经销”? 在数字经济浪潮席卷全球的今天,云计算已成为企业数字化转型的核心引擎。作为亚太地区市场份额第一的云服务商,阿里云为企业提供了强大、稳定、安全的云基础设施与平台服务。然而&a…

2026/9/30 5:07:06 阅读更多 →

最新新闻

Jev 结构化决策模型:TypeSafe AI 与 RLCD 实战指南

Jev 结构化决策模型:TypeSafe AI 与 RLCD 实战指南

1. 从“不说话”的模型说起:Jev 到底在解决什么问题第一次看到“Jev”这个名字,加上“前 OpenAI 研究员做的‘不说话’模型”这个描述,我脑子里冒出来的第一个疑问是:一个不输出自然语言的模型,到底能拿来干什么&#…

2026/9/30 16:23:39 阅读更多 →
C++控制台小游戏开发:零依赖320行实现迷宫逃脱

C++控制台小游戏开发:零依赖320行实现迷宫逃脱

1. 这个“3天编好的C小游戏”到底是什么?——从标题拆解真实项目边界看到标题里“c小游戏(免费复制)(3天编好的,希望各位3连)”,第一反应不是点开,而是先问自己:这到底是…

2026/9/30 16:23:39 阅读更多 →
异步加载原理与性能优化:从FCP到INP的指标解读

异步加载原理与性能优化:从FCP到INP的指标解读

看到“异步加载”这四个字,大多数人第一反应是给 script 加个 async 属性,或者把路由改成懒加载。但如果你只做到这一步,说明还停留在工具层面。真正理解异步加载,要回答的是:浏览器在加载页面时为什么必须同步&#x…

2026/9/30 16:23:39 阅读更多 →
OpenCode 模型接入实战:免费池、OpenRouter 与本地 Ollama 配置指南

OpenCode 模型接入实战:免费池、OpenRouter 与本地 Ollama 配置指南

OpenCode 这个工具最近在开发者圈子里讨论度很高,但真正让大多数人卡住的不是它怎么装,而是装完之后怎么让它跑起来不花钱。官方免费池有使用限制,OpenRouter 的免费模型额度规则经常变,本地 Ollama 又涉及下载和配置的一堆坑。我…

2026/9/30 16:23:39 阅读更多 →
Ubuntu 20.04下NVIDIA驱动、CUDA、CUDNN与NVENC配置实战

Ubuntu 20.04下NVIDIA驱动、CUDA、CUDNN与NVENC配置实战

先把结论放在前面:这套环境配置本身不难,难的是很多人把“驱动、CUDA Toolkit、CUDNN、NVENC”这四层东西混在一起,导致出了问题根本不知道该查哪一层。这篇文章我会从头到尾走一遍 Ubuntu 20.04 下的部署流程,覆盖 NVIDIA 显卡驱…

2026/9/30 16:23:39 阅读更多 →
大模型推理优化实战:从PyTorch到TensorRT的四层工程方法论

大模型推理优化实战:从PyTorch到TensorRT的四层工程方法论

1. 项目概述:Model-Optimizer 不是工具名,而是一套可落地的模型推理加速工程方法论“Model-Optimizer”这个标题乍看像某个开源工具或商业软件,但结合NVIDIA、TensorRT-LLM、vLLM、PT文件转换TensorRT等热搜词,它实际指向的是一类…

2026/9/30 16:22:32 阅读更多 →

日新闻

Base64 图片头部特征识别:从文件头到格式判断的完整指南

Base64 图片头部特征识别:从文件头到格式判断的完整指南

1. 项目概述:为什么说看懂 base64 图片头部是基本功这几年跟 base64 打交道的机会越来越多,后端接口返回图片、前端渲染验证码、小程序里存小图、还有一些老系统导出报表,动不动就给你一段长到怀疑人生的 base64 字符串。很多人拿到字符串就直…

2026/9/30 0:00:35 阅读更多 →
Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

Java公交站牌广告管理系统:JSP+Servlet+MySQL实战落地指南

简介:本资源是一份面向Java初学者与课程设计学生的公交站牌广告灯箱管理系统毕业设计文档,聚焦城市公共广告资源信息化管理痛点,提供从需求分析到技术实现的完整方案。文档采用标准学术论文结构,含摘要、英文摘要、目录及五章正文…

2026/9/30 0:00:35 阅读更多 →
用 Redis Lua 构建大模型 API 多租户原子配额治理体系

用 Redis Lua 构建大模型 API 多租户原子配额治理体系

我去年年底接了一个内部 AI 平台的治理需求,背景很直接:公司把 DeepSeek、MiniMax 这类大模型 API 统一封装成内部网关,开放给几个业务团队用。结果第一个月账单出来,额度直接超了 4 倍。仔细查日志,发现原因并不复杂—…

2026/9/30 0:00:35 阅读更多 →

周新闻

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解

如何划分训练/验证集:Spirula Studio五种eval_mode策略详解 【免费下载链接】spirula-studio Cross-vendor 3D Gaussian Splatting trainer - video to splat to mesh, Vulkan or CUDA. 项目地址: https://gitcode.com/GitHub_Trending/sp/spirula-studio Sp…

2026/9/30 13:14:22 阅读更多 →
SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南

SEO怎么推广速查手册新手避坑实战指南 模板网站太丑不够用?别急着加滤镜,那是治标不治本。很多老板盯着后台流量掉得眼红,却还在纠结首页Banner的圆角是不是3像素。这就像穿着西装去挖土,姿势不对,努力白费。我整理这份 速查手册…

2026/9/29 16:41:41 阅读更多 →
FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏

FireRed-OpenStoryline少样本仿写深度解析:AI Agent如何复刻你的独特文案风格与节奏 【免费下载链接】FireRed-OpenStoryline FireRed-OpenStoryline is an AI video editing agent that transforms manual editing into intention-driven directing through natural language …

2026/9/30 13:14:49 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/29 19:29:29 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/29 5:58:00 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/30 15:27:04 阅读更多 →