一、前言:毕设痛点

很多同学一到毕设季就头大:

  • 环境难配:论文里写的是 TensorFlow 1.x,GitHub 上是 PyTorch 1.x,自己电脑装的是 2.x,一路报错。
  • 代码到处报错:依赖缺、路径错、GPU/CPU 不兼容,改了一堆还是跑不通 Demo。
  • 没有完整思路:只有一个简单的分类脚本,没有数据预处理、可视化、UI 展示,很难写出一篇像样的设计与实现章节。

本项目就是专门为这类“想做深度学习毕设,但不想被环境折磨”的同学准备的:

  • 纯 Python 开发:项目核心采用 Python + PyTorch + OpenCV + PyQt5,Windows 下可直接运行。
  • 一键运行主程序:pycharm上一键运行主程序。
  • 完整 UI 界面:使用 Qt5 设计精美的手写汉字识别界面,包含单张检测、批量检测、结果导出等功能,非常适合课堂展示和答辩 Demo。
  • 配套大规模数据集all_data 文件夹中已经准备好了大量手写汉字样本(数万张图片),可以按需裁剪成 毕设数据集。
  • 训练 + 推理一条龙:从数据划分、训练、可视化、到最终 GUI 调用模型预测,完整闭环,非常适合写论文“系统设计与实现”章节。

二、系统演示(视觉冲击)

  • 主界面效果图
    [请在此处插入项目主界面截图]

    主界面采用深色背景 + 卡片式布局,左边是识别结果和置信度,中间展示当前输入图像,右侧支持单张检测批量检测操作,并可将识别结果一键导出为 CSV 文件。

  • 识别效果图
    [请在此处插入识别成功效果图]

    在实测环境(普通笔记本 + CPU / 简单独显)下:

    • 分类准确率:在验证集上可稳定在 95% 左右(具体视数据划分与训练轮数而定)。
    • 识别速度:单张图像预测延迟在几十毫秒量级,FPS > 30 完全可满足实时交互的体验需求。
    • 批量检测:支持选中文件夹后自动逐张识别,表格实时更新图片路径、类别和置信度。

三、核心技术栈解析

  • Python 3.8+(实测 3.11 亦可)

    • 优势:生态成熟,第三方库齐全,遇到问题基本都能在社区搜到答案,非常适合毕设和课程设计。
  • PyTorch(深度学习框架)

    • 用途:实现 MobileNet 网络结构、训练、验证、模型推理等。
    • 优势:动态图机制友好,上手快,官方和中文社区教程丰富。
  • OpenCV(cv2

    • 用途:对手写汉字图片做基本预处理,例如灰度化、二值化、轮廓提取等。
    • 优势:经典图像处理库,和深度学习框架搭配非常常见,适合课程报告中介绍“传统+深度”的结合。
  • PyQt5 / Qt5(桌面 UI)

    • 用途:构建完整的可视化操作界面,包括图片显示、按钮交互、表格展示、结果导出等。
    • 优势:界面美观、交互友好,老师和答辩评委更容易直观理解你的系统功能。
  • Matplotlib / TensorBoard

    • 用途:绘制精度曲线、损失曲线,并支持用 TensorBoard 查看训练过程。
    • 优势:非常适合写“实验结果与分析”章节,报告里可以直接放训练曲线截图。

四、项目目录结构(Markdown Tree)

下面是精简后的项目结构(去掉大量图片文件,只保留关键目录和脚本),方便大家整体把握项目组织方式:

.
├── all_data/                     # 手写汉字原始数据集(按类别子文件夹划分)
├── models/
│   └── mobilenet.py             # MobileNet 网络结构定义(特征提取 backbone)
├── process/
│   └── yuchuli.py               # 图像预处理相关脚本(辅助处理/可选)
├── ui/
│   ├── background.png           # 界面背景图
│   ├── 图像.png                 # 单张图片选择图标
│   ├── 文件夹.png               # 文件夹选择图标
│   └── main.qrc / 等资源        # Qt 资源文件
├── font/
│   └── simsun.ttc / ...         # 中文字体文件,用于绘制中文可视化
├── weights/
│   ├── handwrite-best-epoch.pth # 训练好的最佳模型权重(推理时加载)
│   ├── loss.txt                 # 最优 epoch 指标记录
│   └── log.txt                  # 训练过程日志(loss / acc)
├── 先看我-项目使用说明/
│   ├── 使用说明.txt             # 图文版项目使用说明
│   ├── 毕设文档示例.pdf         # 论文模板/示例(用于撰写毕业论文)
│   └── ...                      # 其他辅助说明与截图
├── 主界面.py                    # 项目主入口(PyQt5 图形界面 + 模型推理)
├── ui.py                        # 由 Qt Designer 生成的 UI 布局 Python 文件
├── predict.py                   # 命令行版本单张图片预测脚本(无 UI)
├── train.py                     # 模型训练主程序(支持多 epoch 训练与最优模型保存)
├── my_dataset.py                # 自定义数据集类(用于 DataLoader)
├── utils.py                     # 工具函数:数据划分、训练/验证循环、可视化等
├── class_indices.json           # 类别索引与汉字标签对应关系
├── data_name.txt                # 类别编码与中文名称映射(用于界面展示)
├── dataset.png                  # 各类别样本数统计柱状图(自动生成)
├── 精确度曲线图.png              # 训练/验证精度曲线(train.py 自动生成)
├── 损失值曲线图.png              # 训练/验证损失曲线(train.py 自动生成)
└── confusion_matrix.py / .png   # 混淆矩阵绘制脚本及结果

从目录可以看出,训练、数据处理、推理、界面展示、结果分析都被清晰拆分成独立模块,结构非常适合直接写进毕设报告的“系统结构设计”小节。


五、核心代码展示

1. 主窗口 UI 初始化与模型加载

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.setupUi(self)  # 加载 Qt Designer 生成的界面布局

        # ===== 日期与星期显示 =====
        today = str(datetime.datetime.now().date())
        weekday = int(datetime.datetime.now().weekday())  # 0~6 对应周一到周日
        self.label_13.setText(today.split('-')[0] + '/' + today.split('-')[1]+ '/' + today.split('-')[2])
        if weekday == 0:
            self.label_14.setText('星期一')
        if weekday == 1:
            self.label_14.setText('星期二')
        if weekday == 2:
            self.label_14.setText('星期三')
        if weekday == 3:
            self.label_14.setText('星期四')
        if weekday == 4:
            self.label_14.setText('星期五')
        if weekday == 5:
            self.label_14.setText('星期六')
        if weekday == 6:
            self.label_14.setText('星期日')

        # ===== 表格组件初始化 =====
        self.tableWidget.resizeColumnsToContents()
        self.tableWidget.resizeRowsToContents()
        self.tableWidget.setRowCount(4)
        self.tableWidget.setColumnCount(4)
        self.tableWidget.horizontalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)  # 列自适应
        self.tableWidget.verticalHeader().setSectionResizeMode(QtWidgets.QHeaderView.Stretch)    # 行自适应
        self.tableWidget.verticalHeader().hide()  # 隐藏行号
        self.tableWidget.setHorizontalHeaderLabels(['序号', '图像路径', '识别类型','置信度'])
        self.tableWidget.setShowGrid(True)  # 显示网格线

        # ===== 结果标签置空 =====
        self.label_res.setText('')
        self.label_pro.setText('')

        self.lineEdit_pic.setPlaceholderText('  点击选择图像')
        self.lineEdit_folder.setPlaceholderText('  点击选择文件夹')

        # ===== 模型加载部分 =====
        os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
        self.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

        # 读取类别索引映射,后续用于把模型输出转换为汉字标签
        json_path = './class_indices.json'
        assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)
        json_file = open(json_path, "r")
        self.class_indict = json.load(json_file)

        # 创建网络结构并加载权重(核心结构在 models/mobilenet.py 中定义)
        self.model = create_model().to(self.device)
        model_weight_path = "weights/handwrite-best-epoch.pth"
        self.model.load_state_dict(torch.load(model_weight_path, map_location=self.device))
        self.model.eval()  # 推理模式,不进行反向传播

        # ===== 信号与槽绑定(按钮交互)=====
        self.tableWidget.cellPressed.connect(self.getPosContent)             # 点击表格中的一行,展示对应图片结果
        self.pushButton_pic.clicked.connect(self.select_img)                 # 选择单张图像
        self.pushButton_pic_begin.clicked.connect(self.img_detect)           # 对当前图像进行检测
        self.pushButton_folder.clicked.connect(self.choose_folder)           # 选择批量检测文件夹
        self.pushButton_folder_begin.clicked.connect(self.start_detect_folder)  # 启动/停止批量检测
        self.timer_folder = QtCore.QTimer()
        self.timer_folder.timeout.connect(self.detect_folder_timer)          # 定时器驱动逐张处理文件夹图片
        self.pushButton_data.clicked.connect(self.save_data)                 # 导出识别结果到 CSV

        # ===== 读取类别中文名称,用于界面显示 =====
        with open('data_name.txt', 'r', encoding='utf-8')as fb:
            name_data = fb.readlines()
        self.name_dict = {}
        for i in name_data:
            self.name_dict[i.strip().split('+')[0]] = i.strip().split('+')[1]

2. 图像预处理 + 模型推理入口(界面版)

def img_detect(self):
    # 清空表格内容,只保留一行用于当前图片的结果
    self.tableWidget.clearContents()
    self.tableWidget.setRowCount(1)
    self.folder_res = []

    # ===== 与训练阶段保持一致的预处理 =====
    img_size = 224
    data_transform = transforms.Compose(
        [transforms.Resize(int(img_size * 1.143)),   # 先按比例缩放
         transforms.CenterCrop(img_size),            # 再从中心裁剪为 224x224
         transforms.ToTensor(),                      # 转为张量 [C,H,W]
         transforms.Normalize([0.485, 0.456, 0.406], # 归一化(与 ImageNet 一致)
                              [0.229, 0.224, 0.225])])

    # ===== 读取并预处理图像 =====
    assert os.path.exists(self.img_path), "file: '{}' dose not exist.".format(self.img_path)

    # 先做一次传统图像处理(如二值化+轮廓填充),增强手写汉字的结构信息
    self.process_image('test.png')                      # 这里封装了 OpenCV 相关预处理逻辑
    img = Image.open('processed_image.jpg')

    img = data_transform(img)                           # 套用和训练阶段完全一致的 transform
    img = torch.unsqueeze(img, dim=0)                   # 增加 batch 维度 -> [1,C,H,W]

    # ===== 调用训练好的模型进行前向推理 =====
    with torch.no_grad():                               # 不计算梯度,更快更省显存
        output = torch.squeeze(self.model(img.to(self.device))).cpu()
        predict = torch.softmax(output, dim=0)          # 转为概率分布
        predict_cla = torch.argmax(predict).numpy()     # 获取概率最大的类别索引

    # 打印每个类别的概率,可以作为 Debug 或命令行版本输出
    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(self.class_indict[str(i)],
                                                  predict[i].numpy()))

    # 取出最大概率对应的类别编码与置信度
    res = self.class_indict[str(list(predict.numpy()).index(max(predict.numpy())))]
    num = "%.2f" % (max(predict.numpy()) * 100) + "%"

    # 将结果显示到界面:大号汉字 + 置信度
    self.label_res.setText(self.name_dict[res])         # 将编码转成真正汉字,如 "你"、"我"
    self.label_pro.setText(num)

    # 同步写入表格(序号 / 路径 / 类型 / 置信度)
    for column, data in enumerate(['1', self.img_path, self.name_dict[res], num]):
        self.tableWidget.setItem(0, column, QtWidgets.QTableWidgetItem(str(data)))

3. 命令行预测脚本入口(无界面版本)

如果你想在pycharm上或命令行下快速测试模型效果,可以直接调用 predict.py 中的 main 函数,这段代码结构清晰,非常适合作为“模型推理模块设计”的代码示例。

def main(img_path):
    import os
    os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    img_size = 224
    data_transform = transforms.Compose(
        [transforms.Resize(int(img_size * 1.143)),
         transforms.CenterCrop(img_size),
         transforms.ToTensor(),
         transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])

    # ===== 1. 加载待预测图片 =====
    assert os.path.exists(img_path), "file: '{}' dose not exist.".format(img_path)
    img = Image.open(img_path)
    plt.imshow(img)                    # 这里可以直接 matplotlib 显示原图(调试/可视化)
    img = data_transform(img)
    img = torch.unsqueeze(img, dim=0)  # [1,C,H,W]

    # ===== 2. 读取类别索引映射 =====
    json_path = './class_indices.json'
    assert os.path.exists(json_path), "file: '{}' dose not exist.".format(json_path)
    json_file = open(json_path, "r")
    class_indict = json.load(json_file)

    # ===== 3. 构建模型并加载权重 =====
    model = create_model(class_num=200).to(device)
    model_weight_path = "weights/best-epoch.pth"
    model.load_state_dict(torch.load(model_weight_path, map_location=device))
    model.eval()

    # ===== 4. 前向推理,输出概率与标签 =====
    with torch.no_grad():
        output = torch.squeeze(model(img.to(device))).cpu()
        predict = torch.softmax(output, dim=0)
        predict_cla = torch.argmax(predict).numpy()

    for i in range(len(predict)):
        print("class: {:10}   prob: {:.3}".format(class_indict[str(i)],
                                                  predict[i].numpy()))

    # ===== 5. 将类别编码转为可读中文名称 =====
    with open('data_name.txt','r',encoding='utf-8')as fb:
        name_data=fb.readlines()
    name_dict={}
    for i in name_data:
        name_dict[i.strip().split('+')[0]]=i.strip().split('+')[1]

    res = class_indict[str(list(predict.numpy()).index(max(predict.numpy())))]
    num= "%.2f" % (max(predict.numpy()) * 100) + "%"
    print(name_dict[res],num)
    return name_dict[res],num

六、如何运行本项目(保姆级步骤)

下面以 Windows + Python 环境为例,给出一套从零跑通项目的流程。假设你已经拿到了完整项目压缩包。

步骤 1:创建虚拟环境并安装依赖

建议使用 condavenv 创建隔离环境,例如:

# 进入项目根目录
cd mobile_net_handchinese_02

# (可选)创建并激活虚拟环境
conda create -n handchinese python=3.11 -y
conda activate handchinese

# 安装依赖(项目配套资源中提供了 requirements.txt)
pip install -r requirements.txt

主要依赖包括:torch, torchvision, PyQt5, opencv-python, matplotlib, tqdm, tensorboard, Pillow 等。

步骤 2:一键打开图形界面

项目主入口在 主界面.py,直接运行即可打开完整的手写汉字识别界面:

python 主界面.py

运行成功后,你会看到:

  • 窗口顶部显示“基于深度学习的手写汉字识别系统”标题;
  • 左侧大号汉字区域用于显示识别结果;
  • 中间为当前待识别图片预览;
  • 右侧为“单张检测”“批量检测”“结果导出”等按钮。

步骤 3:在界面中进行识别操作

  • 单张图片识别

    • 点击右侧小图片按钮(或“点击选择图像”输入框右侧图标),选择一张手写汉字图片;
    • 点击“开始检测”按钮;
    • 界面中部会显示该图片,左侧大号汉字显示识别结果,下方显示置信度,表格中新增一行记录。
  • 批量图片识别

    • 点击文件夹按钮,选择包含多张手写汉字图片的文件夹;
    • 点击“批量检测”,系统会自动逐张识别,表格实时刷新每一张图像的类别与置信度;
    • 检测完成后,点击“结果导出”,所有识别结果会被保存到 output.csv 文件中,方便后续统计或写论文中的实验结果表格。
  • 模型训练(可选,进阶)

    • 想要自己从零训练模型,可以直接运行:

      python train.py
      
    • 训练完成后,脚本会在 weights/ 下自动保存最优模型权重,并生成精度曲线图和损失曲线图,非常适合作为论文中的“实验结果可视化”。


七、获取方式

由于篇幅有限,本文只展示了项目的整体结构、部分核心调用代码和运行流程:

  • 完整的 Python 源码工程(含训练、预测、GUI、可视化等模块);
  • 已训练好的 MobileNet 手写汉字识别模型权重文件
  • 用于毕设/课程设计的 8500字项目报告(含系统分析、设计、实现与实验部分的写作模板)
  • 以及配套的 大规模手写汉字数据集与可视化图表文件

均已整理打包好。如果你希望直接上手跑通、按此项目写出一篇完整的毕业设计 / 课程设计报告,可以点击下方链接获取完整资源包。

项目获取链接:https://my.feishu.cn/wiki/NAnUwOrBBifpCfkfn3pcT9gsnVe?from=from_copylink
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐