博主介绍:✌全网粉丝10W+,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业项目实战6年之久,选择我们就是选择放心、选择安心毕业✌
> 🍅想要获取完整文章或者源码,或者代做,拉到文章底部即可与我联系了。🍅

点击查看作者主页,了解更多项目!

🍅感兴趣的可以先收藏起来,点赞、关注不迷路,大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助同学们顺利毕业 。🍅

1、毕业设计:2026年计算机专业毕业设计选题汇总(建议收藏)✅

2、大数据毕业设计:2026年选题大全 深度学习 python语言 JAVA语言 hadoop和spark(建议收藏)✅

1、项目介绍

技术栈:python语言、Keras模型、CNN算法、PyQt5图形界面、OpenCV

研究背景:
手写数字识别是计算机视觉入门经典任务,传统人工录入易出错,亟需一套可交互、可上传、实时识别的桌面端工具辅助教学与票据录入。

研究意义:
本系统基于Keras+CNN训练MNIST模型,集成PyQt5图形界面,支持画板手写、图片上传、橡皮擦修正,可为毕业设计提供“模型-界面-OpenCV处理”完整闭环,预计识别准确率>98%,显著提升课堂演示与票据处理效率。


2、项目界面

(1)手写数字识别检测
在这里插入图片描述

(2)橡皮擦功能
在这里插入图片描述

(3)上传图片检测识别
在这里插入图片描述

(4)手写数字识别检测
在这里插入图片描述

(5)界面效果
在这里插入图片描述


3、项目说明

程序基于Keras框架构建CNN卷积神经网络,使用MNIST手写数字数据集(60 000张训练图像、10 000张测试图像)进行训练。首先通过mnist.load_data加载数据,并利用matplotlib展示样本;随后将28×28图像reshape为(28,28,1)并归一化至0-1区间,标签转one-hot编码。网络结构包含3个卷积层+2个全连接层,激活函数采用ReLU,优化器为RMSProp,损失函数为交叉熵。训练5轮、batch_size=64、验证集比例10%,最终在测试集获得>98%准确率,模型保存为my_mnist_model.h5

图形界面使用PyQt5开发,主程序CallWindows.py启动PaintBoard.py画板:支持鼠标手写、橡皮擦修正、颜色对比度增强、OpenCV轮廓分割;点击“识别”即时返回预测数字与置信度。同时支持图片上传批量检测,结果可保存为CSV。系统界面简洁,操作步骤:绘制→识别→清除→保存,为教学演示与票据录入提供轻量级、可落地的手写数字识别解决方案。

关键词:手写数字识别;CNN;Keras;PyQt5;MNIST


4、核心代码


from PyQt5.QtWidgets import QWidget
from PyQt5.Qt import QPixmap, QPainter, QPoint, QPaintEvent, QMouseEvent, QPen, \
    QColor, QSize
from PyQt5.QtCore import Qt

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from keras.datasets import mnist
import matplotlib.pyplot as plt
from keras import models
from keras import layers
from keras.utils.np_utils import to_categorical
import os

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


# 定义模型
def model_conv():
    model = models.Sequential()
    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.MaxPooling2D((2, 2)))
    model.add(layers.Conv2D(64, (3, 3), activation='relu'))
    model.add(layers.Flatten())
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(10, activation='softmax'))
    model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['acc'])
    return model


# 导入MNIST数据集
(train_data, train_labels), (test_data, test_labels) = mnist.load_data()
print('train_shape {} {}'.format(train_data.shape, train_labels.shape))
print('test_shape {} {}'.format(test_data.shape, test_labels.shape))
plt.imshow(train_data[0])
plt.title('number {}'.format(train_labels[0]))
plt.show()

# 数据预处理
x_train = train_data.reshape((60000, 28, 28, 1))
x_train = x_train.astype('float32') / 255
x_test = test_data.reshape((10000, 28, 28, 1))
x_test = x_test.astype('float32') / 255
y_train = to_categorical(train_labels)
y_test = to_categorical(test_labels)
print(x_train.shape, y_train.shape)

# 定义模型
model = model_conv()
print(model.summary())

# 开始训练
his = model.fit(x_train, y_train, epochs=5, batch_size=64, validation_split=0.1)

# 计算准确度
loss, acc = model.evaluate(x_test, y_test)
print('loss {}, acc {}'.format(loss, acc))

# 保存模型
model.save("my_mnist_model.h5")


class Sketchpad(QWidget):

    def __init__(self, Parent=None):
        super().__init__(Parent)

        self.__InitData()  # 先初始化数据,再初始化界面
        self.__InitView()

    def __InitData(self):
        # 私有变量size,画板初始大小为480,460
        self.__size = QSize(611, 461)
        # 新建QPixmap作为画板,尺寸为__size
        # 私有变量__board就是画板实体,大小就是__size
        self.__board = QPixmap(self.__size)
        self.__board.fill(Qt.white)  # 用白色填充画板

        self.__IsEmpty = True  # 默认为空画板
        self.EraserMode = False  # 默认为禁用橡皮擦模式

        self.__lastPos = QPoint(0, 0)  # 上一次鼠标位置
        self.__currentPos = QPoint(0, 0)  # 当前的鼠标位置

        self.__painter = QPainter()  # 新建绘图工具:画笔

        self.__thickness = 10  # 默认画笔粗细为10px
        self.__penColor = QColor("black")  # 设置默认画笔颜色为黑色
        self.__colorList = QColor.colorNames()  # 获取颜色列表

    def __InitView(self):
        # 设置界面的尺寸为__size
        self.setFixedSize(self.__size)

    def Clear(self):
        # 清空画板
        self.__board.fill(Qt.white)
        self.update()
        self.__IsEmpty = True

    def ChangePenColor(self, color="black"):
        # 改变画笔颜色
        self.__penColor = QColor(color)

    def ChangePenThickness(self, thickness=10):
        # 改变画笔粗细
        self.__thickness = thickness

    def IsEmpty(self):
        # 返回画板是否为空
        return self.__IsEmpty

    def GetContentAsQImage(self):
        # 获取画板内容(返回QImage)
        image = self.__board.toImage()
        return image

    def paintEvent(self, paintEvent):
        # 绘图事件
        # 绘图时必须使用QPainter的实例,此处为__painter
        # 绘图在begin()函数与end()函数间进行
        # begin(param)的参数要指定绘图设备,即把图画在哪里
        # drawPixmap用于绘制QPixmap类型的对象
        self.__painter.begin(self)
        # 0,0为绘图的左上角起点的坐标,__board即要绘制的图
        self.__painter.drawPixmap(0, 0, self.__board)
        self.__painter.end()

    def mousePressEvent(self, mouseEvent):
        # 鼠标按下时,获取鼠标的当前位置保存为上一次位置
        self.__currentPos = mouseEvent.pos()
        self.__lastPos = self.__currentPos

    def mouseMoveEvent(self, mouseEvent):
        # 鼠标移动时,更新当前位置,并在上一个位置和当前位置间画线
        self.__currentPos = mouseEvent.pos()
        self.__painter.begin(self.__board)

        if self.EraserMode == False:
            # 非橡皮擦模式
            self.__painter.setPen(QPen(self.__penColor, self.__thickness))  # 设置画笔颜色,粗细
        else:
            # 橡皮擦模式下画笔为纯白色,粗细为10
            self.__painter.setPen(QPen(Qt.white, 20))

        # 画线
        self.__painter.drawLine(self.__lastPos, self.__currentPos)
        self.__painter.end()
        self.__lastPos = self.__currentPos

        self.update()  # 更新显示

    def mouseReleaseEvent(self, mouseEvent):
        self.__IsEmpty = False  # 画板不再为空

5、源码获取方式

🍅由于篇幅限制,获取完整文章或源码、代做项目的,查看我的【用户名】、【专栏名称】、【顶部选题链接】就可以找到我啦🍅

感兴趣的可以先收藏起来,点赞、关注不迷路,下方查看👇🏻获取联系方式👇🏻

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐