
简介基于 PyTorch/OpenCV 与 PyQt5 实现的手写数字识别系统面向毕业设计、课程设计和深度学习入门实践既能检测单个数字位置并完成识别也能连续识别并输出多位数字GUI 界面中阈值等参数可调。压缩包共 1758 个文件、约 130.22MB其中 Python 源码负责训练与推理pt 文件为预训练模型权重xml 与 txt 文件为人工标注的检测标签yaml 用于配置模型参数jpg/png 图像则涵盖训练样本、测试图片与评估曲线ui 文件对应 PyQt5 界面设计还附带了依赖清单与运行说明。目前已有 174 人学习下载适合快速上手和二次开发。使用者可对照项目中的教程与脚本快速复现运行环境也可以基于自带的训练和推理代码重新训练或微调模型人工标注的数据集和完整的 GUI 工程结构为毕设展示、功能扩展和答辩演示提供了可落地的参考实现完整覆盖数据准备、模型训练、评估到界面推理的闭环。1. 基于深度学习OpenCV的手写数字识别多位数识别才是毕设的真正分水岭手写数字识别这个题目做单个数字是入门做连续多位数字才是分水岭。这个项目基于深度学习和 OpenCV在 PyTorch 1.8 Python 3.8 环境下训练 CNN 模型推理时先用 OpenCV 把图像里多个数字区域切出来再逐字识别最后组合还原成完整多位数。带 PyQt5 的 GUI 界面加载一张手写照片能看到检测框、识别结果和置信度几个阈值滑杆随时可拖。适合毕设选图像识别方向、又不想从零调模型的人也适合课程设计要交带界面完整系统的人。它不是科普 Demo是能直接交作业的完整工程。2. 项目骨架PyQt5 GUI、CNN 模型和 OpenCV 推理链路各自干了什么接手一个带 GUI 的深度学习项目第一步不是急着跑而是先拆清楚三层东西界面层、模型层、图像处理层。这个项目恰好把三层分得很清楚理解了这个结构后面出任何问题你都知道该去哪一层查。2.1 GUI 层加载、画布、控制区和结果区PyQt5 做界面没有太多黑匣子主窗口里无非是按钮区、图片显示区、参数控制区、结果输出区。项目打开 main.py 之后看到的界面大致就是这四块。加载图片按钮弹出文件选择框画布负责显示原图和检测框阈值滑杆放在控制区识别按钮触发后端推理。这个分层是 GUI 项目的常规做法信号与槽机制把 UI 事件和推理函数连起来逻辑上很直接。# main_window.py 中核心的信号槽连接 # 加载图片按钮点击后把文件路径传给 _load_image self.btn_load.clicked.connect(self._load_image) # 识别按钮点击后解析当前阈值参数并触发推理线程 self.btn_predict.clicked.connect(self._start_predict) def _start_predict(self): # 从滑杆控件读取阈值二值化阈值范围 0-255置信度阈值常见范围 0.3-0.95 thresh_bin self.slider_binary.value() conf_thresh self.slider_conf.value() / 100.0 # 推理耗时较长用线程避免界面假死 threading.Thread( targetself._run_predict, args(thresh_bin, conf_thresh), daemonTrue ).start()这里有两个值得注意的参数习惯。二值化阈值 slider 一般直接映射到 0-255 的灰度阈值置信度滑块则要除以 100 转成 0-1 之间的小数两者量纲不同别混用。推理放线程里是 PyQt5 项目的常规做法模型预测在 CPU 上可能要几百毫秒直接放主线程里界面会卡住拖动窗口都没响应。项目里如果看到类似daemonTrue的写法说明作者已经处理过这个点。2.2 模型层CNN 结构、训练环境和权重加载模型层是整套系统的核心。项目用的是 CNN输入是灰度小图输出是 0-9 十个类别的概率分布。训练环境固定为 PyTorch 1.8Python 3.8这两个版本匹配很关键。torch 1.8 对应 torchvision 0.9如果装成 torch 1.13 加 torchvision 0.8加载权重时大概率报错。项目自带训练好的模型文件加载方式如下import torch from model import SimpleCNN # 项目里定义的网络结构 model SimpleCNN(num_classes10) checkpoint torch.load(weights/best.pt, map_locationcpu) # 有些权重直接是 state_dict有些包了一层 dict这里要兼容两种情况 if state_dict in checkpoint: model.load_state_dict(checkpoint[state_dict]) else: model.load_state_dict(checkpoint) model.eval() # 切到推理模式Dropout 和 BN 的行为会变化model.eval()这行很多人会漏。训练模式下 BatchNorm 会按当前 batch 的统计量归一化推理时不切模式同一个输入每次输出的结果可能有微小差异多位数识别时某个字符可能翻车。加载权重时顺手打印一下model.state_dict().keys()和 checkpoint 里的 key能快速确认权重文件是不是配套的。跑推理时记得包一层torch.no_grad()省内存也提速。2.3 OpenCV 推理链路从彩色图像到多位数输出多位数字识别比单数字多一步关键流程先分割定位再逐个识别。OpenCV 在这里干的是分割定位的粗活CNN 干的是识别的细活。完整链路是读图、灰度化、二值化、找轮廓、按坐标排序、截取每个数字区域、缩放后送进模型、把单字结果拼成串。这一套流程是手写字符识别项目里最通用的做法。import cv2 import numpy as np # 固定阈值 vs OTSU手写图像质量良莠不齐时OTSU 自动阈值更稳 img cv2.imread(test.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) _, binary cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) # RETR_EXTERNAL 只找外层轮廓避免把数字内部结构也切出来 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) boxes [] for c in contours: x, y, w, h cv2.boundingRect(c) # 过滤掉太小或太扁的噪声区域 if w 10 or h 10 or w / h 4: continue boxes.append((x, y, w, h)) # 按 x 坐标排序保证识别结果的数字顺序正确 boxes.sort(keylambda b: b[0]) for x, y, w, h in boxes: roi gray[y:yh, x:xw] roi cv2.resize(roi, (28, 28)) # 统一尺寸和训练时一致 roi roi.astype(np.float32) / 255.0 # 送入模型的张量 shape 是 [1, 1, 28, 28] roi_tensor torch.from_numpy(roi).unsqueeze(0).unsqueeze(0)THRESH_BINARY_INV里那个_INV很容易被忽略。手写笔迹通常是深色背景、浅色笔迹如果用了THRESH_BINARY会把纸面当白色前景轮廓全是反的最终识别结果一塌糊涂。还有RETR_EXTERNAL这个参数它只取最外层轮廓像「0」「6」「8」这种中间有空腔的数字用RETR_TREE会把内部空洞也算成轮廓过滤条件反而更复杂。排序那行boxes.sort(keylambda b: b[0])看着简单但漏了它识别结果会按轮廓检测顺序输出多位数字直接乱序。3. 部署与运行从 Anaconda 到 main.py 的完整操作流程拿到资源后第一个诉求是把它跑起来。这个项目部署路径很标准Anaconda 建环境、装依赖、跑 main.py。跟着走一遍大概十几分钟能见到 GUI 窗口。3.1 创建 Python 3.8 环境Anaconda 是必装的用 conda 建独立环境避免把系统 Python 搞乱。项目标注 Python 3.8PyTorch 1.8 官方支持 3.6-3.93.8 是安全选择。pycharm 里配解释器时直接选这个 conda 环境就行。conda create -n digit_rec python3.8 -y conda activate digit_rec建好后在 PyCharm 的 Settings → Project Interpreter 里选择这个环境的 python.exe后续跑 main.py 就用的是干净环境。不建议直接用 base 环境毕设项目经常要来回换依赖环境隔离后出问题重建一个就行不用重装 Anaconda。3.2 requirements.txt 依赖安装项目带了 requirements.txt装依赖一条命令。关键包是这几样torch、torchvision、PyQt5、opencv-python、numpy。torch 1.8 在 PyPI 上的安装包比较大耐心等一会儿。pip install -r requirements.txt装的时候注意版本约束。项目环境是 torch 1.8对应的 torchvision 版本是 0.9.0如果 requirements.txt 里没写死版本装到了最新版 torch 2.x残差连接、卷积接口都会有变化加载旧权重不一定兼容。我一般装完先跑一行命令验证python -c import torch, torchvision, cv2; print(torch.__version__, torchvision.__version__, cv2.__version__)看到三个版本号都正常打印再继续下一步。如果 torch 和 torchvision 版本对不上卸载重装对应版本参考官方版本对应表别乱试。3.3 首次启动 main.py 并验证功能环境就绪后就启动主程序。项目入口是 main.py跑起来后弹出 GUI 窗口。首次运行建议先加载项目里自带的测试图片而不是自己随便画一张因为自带的 test 图片经过了标注验证识别结果可以预期。python main.py界面出现后点加载图片按钮选一张 test 目录下的图片再点识别按钮几秒后画布上会出现框和数字串。看到这一步说明环境、模型、代码链路全部通顺。如果识别结果和图片实际数字对不上优先怀疑二值化阈值和置信度阈值设置先恢复默认值再试。窗口弹不出来或者闪退优先看命令行输出。PyQt5 报错常见的是缺少平台插件比如could not find or load the Qt platform plugin windows多数是 PyQt5 安装不完整重装 PyQt5 和 PyQt5-Qt5 两个包能解决。3.4 GUI 上几个可调阈值到底改的是什么GUI 界面里那几个滑杆不是摆设它们直接映射到预处理和推理的敏感参数。理解每个滑杆背后的物理含义才能针对不同图片调出正确结果。滑杆名称取值范围作用对象调大之后的效应二值化阈值0-255OpenCV threshold更严格区分笔迹和背景调大了细节易丢失最小轮廓宽度0-50 像素轮廓过滤过滤小噪点调大了会漏掉小数字置信度阈值0.3-0.95CNN 输出概率过滤低置信度识别调大了识别率下降轮廓高宽比1.0-6.0轮廓过滤过滤拉长噪声调小了会把 1 字滤掉二值化阈值是最常调的。浅色笔迹手写图片阈值太高会把笔迹全滤掉识别框直接为空阈值太低背景噪声全进来框会乱画。调的时候看画布上的二值化预览图笔迹清晰、背景干净就是合适的值。置信度阈值在多位数字场景下尤其重要某一个字符识别概率只有 0.4说明这个字符可能本身就模糊调高阈值可以把它标记为不确定避免错误结果混进完整数字串里。4. 数据集与人工标注多位数字样本是怎么组织出来的这个项目带手写数字数据集而且是人工标注过的这是它比网上那些只给 MNIST 的项目值钱的地方。MNIST 是单字符数据集训练出来的模型只能识别单字这个数据集里有多位数字的完整图片模型才能学会连续多位识别。理解数据组织形式你才能正确地重新训练。4.1 数据集文件结构与标注格式数据集按训练集和测试集划分每张样本图是一张包含多位手写数字的完整图片标注文件记录每个样本对应的数字串。常见组织方式是图片文件名对应标注内容比如5130.jpg表示这张图里写的是「5130」这样一个文件列表就能同时管理图片和标签不需要额外解析。dataset/ ├── train/ │ ├── 5130.jpg │ ├── 5201314.jpg │ └── ... └── test/ ├── 510.jpg └── ...这种文件名即标签的组织方式在毕设项目里最省事。读取时用os.listdir遍历文件解析文件名前几位作为 label比维护一份 JSON 标注文件少一步出错环节。代价是文件名不能乱改复训练制数据集时保持原样。4.2 多位样本的分割与标注方式多位数字样本的标注比单字符样本多一层信息。单字符数据集只需要 label多位样本还需要知道每个数字在图片中的位置否则训练时无法把「分割」和「识别」两个任务串起来。这个项目用的是人工标注标注内容大致是每个数字的边界框坐标和对应的数字值。{ image: train/5130.jpg, digits: [ {box: [24, 15, 28, 40], label: 5}, {box: [56, 15, 28, 40], label: 1}, {box: [88, 15, 28, 40], label: 3}, {box: [120, 15, 28, 40], label: 0} ] }标注坐标是给训练用的。训练时不光要让模型学会认字还要学会在整张图里找字。这种端到端的多位识别训练比 MNIST 那种给好裁剪图的训练难一个量级这也是这项目适合做毕设的原因工作量和难度都是完整课题级别。4.3 数据划分、增强和训练集比例数据划分直接影响模型泛化能力。训练集和测试集的比例建议保持 8:2 以上测试集不能和训练集来自同一批图片否则评估曲线虚高答辩时一问测试细节容易翻车。项目自带的划分如果你不打算重新训练直接用就行自己扩展数据时注意保持这个比例。数据增强是手写数字识别里性价比很高的操作。手写图片的形态差异主要在笔画的粗细、旋转、偏移这些变换对数字语义没有影响可以放心增强随机旋转 ±15 度随机缩放 0.9 到 1.1 倍随机平移几个像素笔画粗细模拟用腐蚀膨胀做细化和加粗增强后的样本能明显提高模型对倾斜笔迹、潦草笔迹的鲁棒性。如果训练出来的模型在测试集上有 97% 准确率、但换一批手写图片就掉到 90%优先补增强而不是加模型层数。手写数字识别这个任务数据多样性的收益远大于模型复杂度的收益。5. PyTorch 1.8 PyQt5 实战避坑五个必踩的坑与排查方法这个项目我在实际环境里跑过也帮别人排查过PyTorch 1.8 PyQt5 OpenCV 这套组合翻车点就那么几个。下面按「现象 → 原因 → 解决」的方式列出最常见的五条遇到任何一个都能照着排查。5.1 坑一torch 和 torchvision 版本不匹配导致 ImportError现象运行import torchvision直接报错提示找不到某个 C 扩展模块。原因torch 和 torchvision 必须配套安装torch 1.8 对应 torchvision 0.9.0。用默认pip install torchvision装了新版底层算子接口对不上。解决先卸载再装指定版本。项目是 CPU 推理的话直接装 CPU 版省空间。pip uninstall torch torchvision -y pip install torch1.8.0 torchvision0.9.0检查是否成功跑python -c import torchvision; print(torchvision.__version__)打印 0.9.0 就对了。5.2 坑二opencv-python 报 libGL.so.1 缺失现象import cv2时提示libGL.so.1: cannot open shared object file。原因opencv-python 依赖系统里的 libGL 图形库在缺少桌面环境的 Linux 服务器或精简版容器里经常崩在这。解决装依赖库或者改用 opencv-python-headless。apt-get install libgl1 libglib2.0-0 # 或 pip install opencv-python-headlessWindows 本地跑一般不会碰到这个问题部署到 Linux 服务器上或者 Docker 里大概率撞上。5.3 坑三中文路径引发模型加载失败现象项目放在带中文名的文件夹下比如D:\毕业设计\手写识别\运行 main.py 时 torch.load 报找不到权重文件或者 OpenCV 读取图片失败。原因Windows 中文路径编码问题PyTorch 的 load 函数对非 ASCII 路径处理不友好OpenCV 的imread也不认中文路径。解决项目根目录全部用英文最省心。如果必须放中文路径下手动读文件再转字节流import cv2 import numpy as np def imread_unicode(path): data np.fromfile(path, dtypenp.uint8) return cv2.imdecode(data, cv2.IMREAD_COLOR)配 PyCharm 时也检查一下运行配置里的工作目录是否包含中文包含就改掉。5.4 坑四多位数粘连导致识别结果错乱现象图片里两个数字挨得很近识别的框把「52」当成一个整体输出结果变成「5」或「? 2」数字串直接被拆错。原因二值化之后轮廓连在一起findContours把两个数字圈成一个连通区域后续 ROI 裁剪就截错位置。解决先尝试形态学开运算断开粘连再尝试用水牌投影分割。垂直投影是最常用的手写字符分割手段——把二值图按列求和笔画密集的列值高列值归零的地方就是字符边界。# 垂直投影分割粘连字符 col_sum binary.sum(axis0) # 每列像素和 in_char False segments [] start 0 for i, val in enumerate(col_sum): if val 0 and not in_char: in_char True start i elif val 0 and in_char: in_char False segments.append((start, i))如果垂直投影切不出边界说明字迹确实连死了这种样本在数据增强时可以做随机偏移让模型在训练时见过虚线状态的数字推理时容忍度会高一些。5.5 坑五GUI 界面拖拽图片时卡死现象点击识别后界面无响应Windows 提示「窗口未响应」等几秒才恢复。原因推理放在主线程PyQt5 的事件循环被阻塞界面没法重绘。解决推理放到子线程用信号把结果传回主线程。前面 2.1 节那段代码已经展示了这个模式。如果项目原代码里没有用线程在_run_predict外面包一层threading.Thread就能解决。注意不要在子线程里直接改 UI 控件要通过信号槽让主线程执行界面更新。6. 进阶操作重新训练模型、读懂评估曲线、多位数识别落地扩展跑通项目只是第一步真正拉开差距的是能不能自己训模型、看懂训练过程中的曲线、把系统用到新场景里。6.1 评估曲线怎么读项目里带评估曲线一般是训练过程中的 loss 曲线和 accuracy 曲线。看曲线抓三个点。第一loss 是否持续下降训练 10 个 epoch 后 loss 还在明显波动可能是学习率太大建议从 0.001 降到 0.0001第二训练集和验证集准确率的差距两者差距超过 3 个百分点模型可能过拟合加大增强或加 dropout第三曲线尾部的趋势结束前还在缓慢上升说明还没收敛加大 epoch 数继续训。6.2 重新训练训练和评估代码怎么跑项目带完整训练代码重新训练时先看训练入口的参数设置常见的 batch size 在 32 到 128 之间epoch 在 20 到 50 之间。学习率建议从 0.001 起步用 Adam 优化器配合 StepLR 每 10 个 epoch 衰减 0.1 倍。训练完成后模型会存成权重文件替换原来的 best.pt 即可。重训前备份原模型权重留个后悔药训崩了还能切回去。python train.py --batch-size 64 --epochs 30 --lr 0.0016.3 多位数识别的实用扩展技巧训练自己的数据时我习惯在数据里混一两条写得很潦草的样本模型对「人味」的容忍度会明显提升。把训练好的模型接到摄像头做实时识别也是加分项用 OpenCV 的 VideoCapture 读帧每帧跑一遍预处理和推理加一个帧间隔限制就能流畅运行。从那以后我每次换环境重跑项目都强制先跑一遍环境验证命令再开 main.py。模型、数据、代码三层都确认无误再继续能少走很多弯路。希望帮到你。本文还有配套的精品资源点击获取