资讯动态

基于神经网络的字符识别:Matlab从零到部署的完整实践

发布时间:2026/10/17 3:32:18 来源:尧图企业网站定制
字符识别是个特别适合拿来入门神经网络的题目数据好搞、任务直观、效果一眼就能看出来而且背后那一套“采集图像→预处理→训练模型→部署测试”的流程放到车牌识别、票据识别、答题卡批改、快递单号录入这些真实场景里全都用得上。这篇博文我就讲讲基于神经网络的字符识别如何在Matlab里从零跑通并附上可复用的Matlab代码。内容主要面向两类人一类是做毕业设计或课设、需要快速出结果的在校学生另一类是刚接触图像识别、想把手写数字识别当练手项目的工程师。我会把网络选型、数据预处理、训练参数、踩坑排查和最后的部署测试都串起来讲尽量让不同基础的人都能直接照着做。1. 字符识别这件事为什么值得亲自动手做一遍1.1 字符识别不是小众需求先说说我为什么对“字符识别”这个主题印象深。前阵子有人问我说想做一套自动识别快递面单上电话号码的系统问我用什么方案最省事。我当时给的答案是核心模块其实就是字符识别——先把一串数字从复杂的背景里切出来再逐个字符去分类最后按顺序拼回去。类似的场景还有试卷客观题批改里的考号识别、工厂流水线上D打印的序列号读取、手机App拍银行卡号自动填充……它们的共同点都是“把图像里的字母和数字变成计算机能处理的文本”。这也就是所谓的OCR大类里的一个子方向字符识别。神经网络出现之前大家做字符识别主要靠模板匹配和手工设计特征比如提取笔画密度、投影直方图、区域的拓扑特征再丢给SVM或Adaboost分类。说句公道话这些方法在小规模、规整的印刷体场景里效果并不差但一旦遇到歪斜、模糊、光照不均、字体变化手工特征就开始吃力了。而神经网络本质上干的事情是把“特征提取”和“分类器”一起端到端地学好你喂给它的不是人工设计的特征而是原始像素。这也是这个题目放到现在依然值得做的原因。1.2 传统方法和神经网络方法的差异在哪我经常用一句话跟人解释这个差异传统方法像是你给一个没见过世面的人列了一堆“如何识别数字”的手写规则告诉他笔画怎么交叉、哪里封闭、哪里凸起规则写满好几页换一种字体或者模糊一点就失效。神经网络则是直接给他看一万张带标签的数字图片让他自己观察规律最后他可能说不清自己到底掌握了什么规律但新图来了他就是能认出来。落到字符识别这个具体任务上神经网络的优势主要有三点端到端建模输入图片直接输出类别概率中间不需要人工设计特征。泛化能力好对轻微旋转、平移、噪声的抗性比固定模板强得多。可复用到其他字符集把数字换成英文字母、车牌汉字甚至验证码只需要换数据集网络结构几乎不用动。而在所有能实现神经网络的工具里Matlab又是个很特殊的存在它不用装一堆依赖包、不用管CUDA和Python环境打架装好工具箱、写几十行代码就能看到训练曲线动起来。对于教学演示、毕设验证、快速原型来说这比Python系工具链省心不少。所以我这篇文章的代码方案完全基于Matlab的深度学习工具箱版本要求不高后面我会说到具体的版本检查方法。2. 环境与数据准备Matlab里最容易被忽略的几个前置细节2.1 版本与工具箱检查别小看这一步环境问题能卡掉一半的新手。基于神经网络的字符识别在Matlab里主要依赖这几个工具箱工具箱作用Deep Learning Toolbox提供层的定义、trainNetwork训练函数、trainingOptions配置Image Processing Toolbox灰度化、二值化、形态学处理等图像预处理Parallel Computing Toolbox可选GPU训练和并行池相关功能依赖它检查方法很简单在Matlab命令行输入ver % 列出所有已安装工具箱输出列表里如果找不到Deep Learning Toolbox那就得先安装或者换一台装了完整工具箱的机器。用Deep Learning Toolbox训练网络的最低版本要求不算低建议直接用R2019b之后的版本因为R2019b开始trainNetwork对数据存储、层定义的兼容性比较完善R2021a之后用起来更舒服。还有一个常用的检查命令license(test, Deep_Learning_Toolbox)返回1表示当前许可证可用。这一步最好开跑前就确认好不然代码敲到一半才发现缺工具箱心态容易崩。2.2 数据集从哪来做字符识别有两条路用公开数据集或者自己造数据。公开数据集最经典的是手写数字MNIST它有六万张训练图和一万张测试图每张是28x28的灰度图正好对应0到9共十个类别。拿这个数据集跑通流程相当于拿到了字符识别项目的“最小可用版本”。自己造数据的情况更多出现在毕设和真实项目里比如你要识别的是印刷体英文字母、特定字体的身份证号或者某些特殊字符集。这时候我建议不要一张张去另存图片而是写个小脚本批量生成或批量采集% 批量读取用户准备好的字符图片文件夹 imds imageDatastore(fullfile(pwd, charData), ... IncludeSubfolders, true, ... LabelSource, foldernames);注意这个imageDatastore的用法它会把charData下面每个子文件夹当作一个类别文件夹的名字自动变成标签。所以数据目录的组织方式应该是这样charData/ ├── 0/ │ ├── 0_001.jpg │ ├── 0_002.jpg │ └── ... ├── 1/ │ ├── 1_001.jpg │ └── ... └── ...每个类别下的样本尽量多一些最低也别低于一两百张不然训练出来的模型很容易过拟合。2.3 数据预处理的关键点如果你用的是MNIST这类已经规范化好的数据集预处理任务就轻松很多。但如果你是自己拍的照片那预处理就决定了最终识别率的上限甚至比网络结构还重要。我这里给一个通用的字符图片预处理函数把任意输入图片统一成28x28的单通道灰度图function img preprocessChar(im) % 输入RGB或灰度图像 % 输出28x28x1的灰度图用于输入神经网络 if size(im, 3) 3 im rgb2gray(im); end % 自适应二值化把前景字符和背景分开 bw imbinarize(im, adaptive, ForegroundPolarity, dark); % 统一尺寸 imgResized imresize(bw, [28 28]); % 注意极性MNIST是白字黑底若你的图片是黑字白底则取反 imgResized imcomplement(imgResized); img single(imgResized); end这个函数里藏着一个特别容易踩的坑二值化极性。MNIST数据集里字符是白色的、背景是黑色的而你自己用手机拍的字符绝大多数是黑字白底。如果你不取反训练和预测时的输入分布不一致后面测试准确率会突然掉到接近随机水平。这也是我在后面踩坑章节里会重点展开的一个问题。还有一点需要提醒imbinarize自适应的阈值在图片光照不均匀时比固定阈值好用但如果在某些特别干净的黑白合成图上直接用固定阈值imbinarize(im, 0.5)反而更快更稳。你可以根据自己数据的实际情况选判断标准是看二值化结果里字符是否完整、背景是否干净。3. 网络搭建与训练从MLP到CNN的选择逻辑和Matlab实现3.1 网络结构的选择逻辑训练字符识别最基础的是全连接神经网络MLP。把28x28的图片拉成一维就是784个输入特征中间加两层全连接层输出层用10个神经元对应0到9。这个结构在MNIST上跑出97%以上的准确率并不难但它有个天然的劣势它把图片的二维空间结构完全丢掉了每个像素位置都被当成彼此独立的特征。人眼能轻松识别一个数字靠的是笔画之间的相对位置关系而不是某个孤立的像素值。CNN卷积神经网络则保留二维空间信息通过卷积核在图像上滑动来提取局部模式比如边缘、弧线、交叉点。之后再逐层组合成更高级的特征。对于字符识别这种任务CNN几乎是默认选择参数更少、准确率更高、对字符轻微形变的鲁棒性也更好。那为什么我还要先讲MLP因为MLP简单适合用来理解训练过程等你把全流程跑通了再替换成CNN就是改几行层定义的事。我建议的路线是先用一个简单的CNN把流程跑通确认数据和训练代码没问题然后再回头尝试更深的模型。这个“从简单到复杂”的顺序能帮你把变量控制在最少。3.2 完整训练代码下面这套代码就是一个可运行的最小CNN示例网络结构包含卷积层、池化层、全连接层和输出层% 定义网络结构 layers [ imageInputLayer([28 28 1], Name, input) convolution2dLayer(3, 32, Padding, same, Name, conv1) reluLayer(Name, relu1) maxPooling2dLayer(2, Stride, 2, Name, pool1) convolution2dLayer(3, 64, Padding, same, Name, conv2) reluLayer(Name, relu2) maxPooling2dLayer(2, Stride, 2, Name, pool2) fullyConnectedLayer(128, Name, fc1) reluLayer(Name, relu3) fullyConnectedLayer(10, Name, fc_out) softmaxLayer(Name, softmax) classificationLayer(Name, output) ]; % 训练选项 options trainingOptions(sgdm, ... InitialLearnRate, 0.01, ... MaxEpochs, 10, ... MiniBatchSize, 128, ... Shuffle, every-epoch, ... ValidationData, auVal, ... ValidationFrequency, 20, ... Verbose, true, ... Plots, training-progress); % 训练网络 net trainNetwork(auTrain, layers, options);这里有个细节auTrain和auVal是什么它们是增强图像数据存储作用是统一一组图片的尺寸。如果你的图片本身就是28x28直接用imageDatastore也行但不用尺寸不一的图片硬训imageInputLayer会直接报错。统一尺寸的做法如下auTrain augmentedImageDatastore([28 28], trainImds); auVal augmentedImageDatastore([28 28], valImds);augmentedImageDatastore会在读取每张图时自动resize到[28 28]相当于把尺寸不一致的问题提前消化掉。在实际图像识别项目里这个对象很适合用来做在线数据增强比如随机旋转、平移、翻转但做手写数字识别时别随便用平移和旋转——数字的语义和位置无关但字符本身的方向是有意义的。3.3 trainingOptions参数调优思路很多人拿到trainingOptions只会照抄参数不知道每个参数在干嘛结果训练不收敛的时候也不知道改哪。我挑几个主要的说下我的经验InitialLearnRate初始学习率MLP和简单CNN用0.01到0.03是安全区间。学习率太大会导致损失函数发散太小会让训练在有限epochs里走不到最优。如果损失曲线长期不下降先考虑调低学习率而不是盲目加层数。MiniBatchSize一次性喂给网络的样本数。128是小数据集下比较平衡的值显存紧张就调成64训练集很大可以试256。batch越大梯度越稳定但占用内存也越大。MaxEpochs遍历整个训练集的次数。MNIST这种规模CNN跑10到15个epoch一般够了如果你的数据量只有几千张可能需要20到30个epoch并配早停。ValidationData和ValidationFrequency这两个参数特别重要它们能让你在训练过程中实时看到验证集准确率及时发现过拟合。没有验证集的训练就是盲人摸象。Shuffle设为every-epoch让每个epoch数据顺序不同能防止模型学到样本顺序带来的偶然规律。训练结束后trainNetwork返回一个net结构体里面有整个网络的权重和层信息。到这一步你的“基于神经网络的字符识别”模型就已经训练完毕了接下来要做的是验证它到底好不好用。4. 训练中的意外情况与排查记录我的实测踩坑全过程4.1 损失直接变成NaN我首先要说的这个问题几乎每个跑过深度学习的人都遇到过训练一开始还正常跑了没几个iteration损失直接变成NaN训练曲线像被一刀切断。看到这个现象很多人的第一反应是换一个更大的网络但方向完全反了。NaN的根因大概率是梯度爆炸或数据里出现了非法数值。排查链路应该是先降低InitialLearnRate比如从0.01降到0.001重新训练。以我的经验十次NaN里有七次是学习率过高导致的。检查输入数据里是否有NaN或Inf。简单方法assert(~any(isnan(imdsSample(:))), 输入数据存在NaN);如果图像采集或预处理阶段出现了除零之类的错误训练的损失也会变成NaN。检查分类标签是否从0开始编号。classificationLayer要求标签是连续的整数如果有类别没有任何样本网络可能出现梯度异常。4.2 训练集很好、验证集很差过拟合问题第二个高频问题是训练准确率跑到99%验证准确率却只有80%出头。这就是典型的过拟合。模型把训练样本“背”下来了但对没见过的图片没有泛化能力。我在一个小样本实验里跑出来的现象更明显每个类别只有30张图CNN训练到第10个epoch时训练集准确率100%验证集却停在85%。这时候怎么处理优先顺序是这样的先收集更多数据哪怕每个类别多50张都有明显改善这是最有效的一招。在网络里加dropout层。Matlab里一行就能加dropoutLayer(0.5, Name, dropout1)放在全连接层之前。它的作用类似“抑制过拟合的随机开关”训练时随机丢弃一部分神经元强迫网络不要过度依赖某几个节点。数据增强。适当加入小角度旋转、轻微缩放、随机亮度变化让模型见到更多样本形态。换小一点的模型。如果数据本身就不多把网络层数减少往往比堆更深的网络更好训。4.3 自拍字符图片识别失败预处理不一致这个坑真的很隐蔽。一开始我用MNIST数据训练测试集准确率也能到98%以上但一到我拿手机拍的一串数字去识别十张里有七八张是错的。当时我的第一反应是网络太差、不够深后来一步步排查才发现问题出在三个预处理环节颜色相反训练集是白字黑底我的测试图片是黑字白底。网络看到的输入模式完全反了准确率自然崩。尺寸不一致MNIST图片是28x28我拍的照片经过拉伸变形后比例不对。背景干扰手机照片的背景有桌面纹路、阴影没有做二值化就去resize字符和背景糊在一起。那次排查我写了一个小脚本把测试图片经过每步预处理后的中间结果逐张显示出来一眼就看出问题所在。我的建议是无论什么时候做图像识别都要把预处理流程的可视化放在重要位置——你不看到网络实际“吃”进去的图像内容就永远不知道为什么效果不好。4.4 训练时间过长与内存问题还有个容易被忽视的现实问题训练速度。在我的CPU笔记本上一个简单的CNN跑10个epoch、约60000张训练图片需要接近二十分钟如果换成更深的网络可能要一个多小时。这时你有几个选择检查是否真的在用GPU。Matlab的trainNetwork会自动启用可用GPU但前提是Parallel Computing Toolbox已安装并且你在trainingOptions里没有显式指定ExecutionEnvironment为cpu。调小图片尺寸。比如从28x28改成24x24训练时间会显著下降精度损失通常很小。减少epochs并用验证集早停而不是傻跑满所有轮数。如果数据量很大可以先用一个子集做10分钟的快速实验确认网络不炸、准确率趋势正确再全量训练。这些经验适用于大多数小型图像识别项目字符识别只是其中典型的代表。5. 部署与测试模型搭好后还不能松气的那几步5.1 测试集评估与混淆矩阵训练完成不等于项目完成。你还需要在完全没参与训练的测试集上做最终评估并且最好画一张混淆矩阵把每个类别的错误情况看清楚YPred classify(net, auTest); YTest auTest.Labels; accuracy sum(YPred YTest) / numel(YTest); fprintf(测试集准确率: %.2f%%\n, accuracy * 100); figure; confusionchart(YTest, YPred);混淆矩阵的横轴是预测标签纵轴是真实标签。对角线上数值大说明这个类别识别得好非对角线上的亮点就是模型容易混淆的地方。我遇到最多的是“8”和“3”互相认错、“0”和“6”偶尔混淆这跟字符本身形态相近有很大关系。发现这类问题最直接的办法是补充这类易混淆样本或者对它们做专门的数据增强。5.2 单张图片识别的完整函数测试集评估是针对大量样本的统计结果但实际部署时你往往要处理的是“一张图一次调用”的场景。我习惯封装一个识别函数传入图片路径返回识别标签和置信度function [label, score] classifyCharImage(imgPath, net) im imread(imgPath); im preprocessChar(im); [label, score] classify(net, im); % 显示原图与结果方便直观验证 figure; imshow(imgPath); title(sprintf(识别结果: %s (置信度: %.3f), char(label), max(score))); end这个函数特别适合在交付时给用户演示拍一张照片调用它界面弹出字符和置信度。注意classify函数在输入单张图片时会返回标签和概率分数分数数组的每个元素对应一个类别取最大值所在位置就是最终判别的类别。5.3 模型导出与落地Matlab训练好的网络不能只在Matlab里玩真实项目往往要求在Python服务、C程序或者嵌入式设备上运行。这里有两个常见的导出方向导出方式命令适用场景保存工作区模型save(charRecogNet.mat, net)继续在Matlab里识别、二次训练或演示ONNX模型导出exportONNXNetwork(net, charRecogNet.onnx)让其他语言框架加载推理模型导出ONNX后你可以把它放到支持ONNX的推理引擎里走标准的模型转换和部署流程。这里我想强调一点导出模型时预处理流程和导出后加载图端的预处理必须保持完全一致包括灰度化、二值化、缩放尺寸和极性方向。部署时最先出问题的往往就是这种“训练时做了某步、部署时忘了”的隐性不一致。最后再分享一个我反复踩坑后总结出的习惯做字符识别项目永远先跑通一条最简单、可端到端验证的小流程再逐步优化网络结构和参数。不要一开始就上超大的模型、十几个自选层、一堆花哨的数据增强——那样出了问题你都说不清是哪一环导致的。先把一个小CNN用默认参数训练起来拿到合理的准确率确认数据预处理没问题再去调精度。这个顺序能让你节省至少一倍的时间也更容易找到问题到底出在网络、数据还是代码上。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑