Matlab app 【第九期】 手写数字识别软件

首先我们需要利用matlab自带的数据集DigitDataset训练CNN神经网络,然后利用APP中的回调函数来调用训练好的网络,对新上传的手写体进行识别,然后再将结果进行可视化展示。
我们需要在.m文件中训练CNN网络:
第一步:导入matlab中自带的数据集DigitDataset;
% 1. 加载 MATLAB 自带的手写数字数据digitDatasetPath = fullfile(matlabroot,'toolbox','nnet','nndemos','nndatasets','DigitDataset');imds = imageDatastore(digitDatasetPath, 'IncludeSubfolders',true,'LabelSource','foldernames');
第二步:我们将这个数据集按照0.75:0.25的比例划分为训练集和验证集;
% 2. 划分训练集 (75%) 和验证集 (25%)[imdsTrain,imdsValidation] = splitEachLabel(imds,0.75,'randomized');
第三步:开始设置CNN网络结构,在这里我们将图片划分为28×28的灰度网格,因此输入层为28×28×1,输出层为10个数字因此我们输出层为10个节点;
% 3. 定义网络架构% 注意:输入层是 28x28x1 (灰度图)layers = [ imageInputLayer([28 28 1])convolution2dLayer(3,8,'Padding','same')batchNormalizationLayerreluLayermaxPooling2dLayer(2,'Stride',2)convolution2dLayer(3,16,'Padding','same')batchNormalizationLayerreluLayermaxPooling2dLayer(2,'Stride',2)fullyConnectedLayer(10) % 0-9 共10个数字softmaxLayerclassificationLayer];
第四步:我们设置训练的相关配置;
% 4. 训练配置options = trainingOptions('sgdm', ...'MaxEpochs',5, ...'ValidationData',imdsValidation, ...'ValidationFrequency',30, ...'Verbose',false, ...'Plots','training-progress');
第五步:保存网络结构,方便app调用;
% 5. 开始训练并保存net = trainNetwork(imdsTrain,layers,options);save('digitNet.mat','net'); % 保存模型供 App 使用disp('模型训练完成,已保存为 digitNet.mat');
最后,为方便大家复现,训练代码整体文件如下:
clcclear% 1. 加载 MATLAB 自带的手写数字数据digitDatasetPath = fullfile(matlabroot,'toolbox','nnet','nndemos',...'nndatasets','DigitDataset');imds = imageDatastore(digitDatasetPath,...'IncludeSubfolders',true,'LabelSource','foldernames');% 2. 划分训练集 (75%) 和验证集 (25%)[imdsTrain,imdsValidation] = splitEachLabel(imds,0.75,'randomized');% 3. 定义网络架构 (经典的小型CNN)% 注意:输入层是 28x28x1 (灰度图)layers = [imageInputLayer([28 28 1])convolution2dLayer(3,8,'Padding','same')batchNormalizationLayerreluLayermaxPooling2dLayer(2,'Stride',2)convolution2dLayer(3,16,'Padding','same')batchNormalizationLayerreluLayermaxPooling2dLayer(2,'Stride',2)fullyConnectedLayer(10) % 0-9 共10个数字softmaxLayerclassificationLayer];% 4. 训练配置options= trainingOptions('sgdm',...'MaxEpochs',5, ...'ValidationData',imdsValidation,...'ValidationFrequency',30, ...'Verbose',false,...'Plots','training-progress');% 5. 开始训练并保存net= trainNetwork(imdsTrain,layers,options);save('digitNet.mat','net');% 保存模型供App 使用disp('模型训练完成,已保存为digitNet.mat');
下面我们开始设计界面部分,在设计之前我们先梳理一下需要哪些功能:
1.首先我们需要一个坐标区用来写数字,用拖拽鼠标进行书写;
2.随后需要一个识别按钮来调用保存好的模型进行识别以及一个清除手绘区的按钮,最后需要一个label来展示识别结果。
因此根据这些我们可以拉一个简单的页面,其中为让大家能够看到结果展示的label,我将其背景颜色变为了黑色(否则默认为透明色,看不到其位置),后续我会将其回复为透明色。

组件清单如下:
|
名称 |
组件 |
作用 |
|---|---|---|
|
label |
label |
软件标题行 |
|
DrawingAxes |
坐标区 |
手绘区 |
|
RecognizeButton |
按钮 |
识别按钮 |
|
ClearButton |
按钮 |
清除手绘区 |
|
ResultLabel |
label |
展示结果 |
下面开始设计回调函数,由于需要记录鼠标所绘制的图样,因此需要记录坐标区按下鼠标后的坐标,因此需要两个公共属性来储存鼠标的X和Y的坐标,并且需要读取之前保存的网络文件。
第一步:我们需要设置四个公共变量;
isDrawing = false; % 标记鼠标是否按下X = []; % 记录笔迹轨迹XY = []; % 记录笔迹轨迹YtrainedNet; % 存放加载的网络
第二步:我们需要设置一下app的startupFcn属性,让app在运行时就去读取保存的网络模型,并且给坐标区做一个初始化;
% 1. 加载训练好的模型tryd = load('digitNet.mat');app.trainedNet = d.net;catchuialert(app.UIFigure,'请先运行训练脚本生成digitNet.mat','错误');end% 2. 初始化画板cla(app.DrawingAxes);axis(app.DrawingAxes,[0 28 0 28]);hold(app.DrawingAxes,'on');% 允许连续绘图
第三步:我们需要设置坐标区,让其可以识别到按住鼠标左键时移动的轨迹;
1.设置坐标区回调函数ButtonDownFcn,从而获取点击时的鼠标位置;
app.isDrawing = true;% 获取当前鼠标位置coords = app.DrawingAxes.CurrentPoint;app.X = coords(1,1);app.Y = coords(1,2);
2.设置窗口的回调函数WindowButtonMotionFcn,从而检测鼠标移动的轨迹并展示出来。
该回调函数的添加方法如下:右键app. UIFigure,鼠标移动到回调上,随后移动到窗口回调上,然后选择WindowButtonMotionFcn;
if app.isDrawing% 获取新坐标coords = app.DrawingAxes.CurrentPoint;newX = coords(1,1);newY = coords(1,2);% 限制只能在画板范围内画if newX > 0 && newX < 28 && newY > 0 && newY < 28% 连线绘图plot(app.DrawingAxes,[app.X,newX],[app.Y,newY],'k-','LineWidth',15);% 更新起点app.X = newX;app.Y = newY;endend
3.点击和移动设置完成后,需要继续设置松开鼠标后的命令,设置窗口的回调函数WindowButtonUpFcn,从而检测鼠标移动的轨迹并展示出来。
该回调函数的添加方法如下:右键app. UIFigure,鼠标移动到回调上,随后移动到窗口回调上,然后选择WindowButtonUpFcn。
app.isDrawing = false;
第四步:我们开始设置识别按钮的回调函数,该回调函数需要完成:
1.手绘字体转换成图片;
2.将图片转换为网络可以识别的样式;
3.利用训练好的神经网络进行识别;
4.展示在结果展示区内。
因此可以将回调函数写为:
% 1. 获取画板上的图像帧% getframe 是截屏函数,捕捉 Axes 区域f = getframe(app.DrawingAxes);img = f.cdata;% 2. 预处理% 神经网络是用 28x28 的灰度图训练的% 转灰度imgGray = im2gray(img);% 调整大小到 28x28imgResized = imresize(imgGray,[28 28]);% --- 颜色反转 ---% 你的画板是“白底黑字” (plot用的是黑色'k')% 但 MNIST 数据集是“黑底白字”% 所以必须反转颜色,否则识别率极低!imgInverted = imcomplement(imgResized); % 二值化增强imgBin = imbinarize(imgInverted);% 3. 识别label = classify(app.trainedNet,imgInverted);% 4. 显示结果app.ResultLabel.Text = ["识别结果: " + char(label)];
第五步:清除回调函数的编写。
cla(app.DrawingAxes); % 清除坐标轴内容axis(app.DrawingAxes, [0 28 0 28]); % 重置范围

夜雨聆风
