做手写数字识别,是很多搞图像处理和机器学习的同学绕不开的一个课题。之前帮人搭过一套完整的 matlab 方案,链路是图像定位、二值化、5×5 分割、主成分分析法(PCA)降维,最后用决策树做分类,再用交叉验证评估效果。这套流程走下来,模型精度虽然不是最顶尖的,但整个方案的工程意义很强,尤其适合特征工程的练手,以及做一些轻量级的数字识别场景,比如答题卡数字识别、表单编号自动读取、票据金额区域识别这类对模型大小和可解释性有要求的任务。
这篇文章就把这套基于决策树的手写数字识别 matlab 实现完整拆开,从图像预处理开始讲到交叉验证的结果评估。整个项目最核心的关键词就五个:matlab、决策树、手写数字识别、主成分分析法、二值化,下文会逐个展开,把每一步为什么这么做、怎么做、踩过什么坑都交代清楚。无论你是刚入门图像处理的新手,还是在做课程设计、毕业设计,这套方案都可以直接参考复现。
1. 项目整体设计与思路拆解
1.1 核心需求解析
这个项目要解决的并不是简单的"给一张数字图片输出一个标签",而是一个完整的技术链路。我们在实际场景里拿到的手写数字图像,往往是整张纸、整块区域,而不是一张干干净净的单数字图片。所以项目的完整链路是:
- 从原始图片中定位出数字所在的区域;
- 对定位区域做灰度化和二值化处理,把背景和数字前景清晰分离;
- 把每个数字图像归一化后做 5×5 网格分割,提取结构化特征;
- 用主成分分析法对高维特征做降维,去掉冗余信息;
- 用降维后的特征训练决策树分类器;
- 最后用交叉验证评估模型的稳定性和泛化能力。
这个设计思路其实是传统机器学习解决图像识别问题的标准范式:图像预处理 → 特征提取 → 特征降维 → 分类器训练 → 模型评估。相比现在动辄上百万参数的卷积神经网络,这条路线的计算开销小很多,并且在数据量不大的情况下,模型的训练速度和可解释性都要好得多。如果你想理解"特征工程到底在做什么",这套流程就是最好的教材。
1.2 为什么选择"决策树 + PCA"而不是神经网络
很多人看到手写数字识别,第一反应是上卷积神经网络(CNN),MNIST 数据集上 CNN 的准确率确实可以轻松做到 99% 以上。但决策树方案也有自己独特的价值,不能一概而论。
首先是数据量的现实问题。真实业务场景里,手写数字样本往往只有几百到几千个,这个量级下深度学习模型的优势发挥不出来,反而容易过拟合。决策树天生对中小数据集友好,训练速度快,参数少,不太容易翻车。
其次是可解释性。决策树可以把分类规则直接可视化出来,比如"特征 12 > 0.35 并且特征 7 < 0.21 时判定为数字 3",这对需要向非技术同事解释模型逻辑的场景非常关键。我见过不少银行票据识别项目,风控人员对"黑箱"模型是天然不信任的,这时候决策树就比神经网络有优势。
第三是计算资源。跑 matlab 决策树,一颗普通 CPU 几十秒就能完成训练和交叉验证,而训练 CNN 往往需要几分钟到几十分钟。对于课程设计、毕业设计这种场景,决策树方案时间成本低,出成果快,放在论文里技术逻辑也更清晰。
1.3 技术流程总览
整个方案在 matlab 中的实现可以分为六个模块:
- 图像读取与灰度化
- 数字区域定位(连通域分析 / 投影法)
- 二值化与形态学去噪
- 5×5 网格分割与特征向量构建
- PCA 降维
- 决策树训练与交叉验证
后面每个模块我都会给出能直接跑的 matlab 代码片段,并解释关键参数的含义,这样你拿到手就能在此基础上改。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 图像预处理:定位、二值化详细实现
2.1 读图与灰度化处理
matlab 中读图用 imread,这一步几乎没什么坑,但有一个细节容易被忽略:读进来的彩色图是三维矩阵(H×W×3),需要转成灰度图才能做阈值分割。如果你拿到的是扫描件或者手机拍照的图片,建议先用 imresize 统一尺寸,避免后续处理时不同图片分辨率差异过大。
matlab复制% 读入图像
img = imread('handwritten_digits.png');
% 如果是彩色图,转为灰度图
if size(img, 3) == 3
gray = rgb2gray(img);
else
gray = img;
end
% 可选:降采样统一尺寸,加快处理速度
gray = imresize(gray, [400, 400]);
这里 imresize 到 400×400 是我常用的做法。原始图像如果很大,比如 3000×2000 的扫描件,连通域分析和形态学操作都会变慢。缩小到 400×400 能保留足够的数字轮廓信息,同时处理速度快一个量级。
2.2 数字区域定位:投影法与连通域分析
定位是手写数字识别里最容易翻车的一步。如果图片背景干净,数字是黑色手写体、白色纸张,定位其实非常简单。我常用的方法是连通域分析,配合 regionprops 提取每个连通域的包围盒。
所谓连通域,就是二值图像中像素值为 1 且相邻的像素组成的区块。每个手写数字在二值图里就是一个连通域(写得不潦草的情况下)。通过 regionprops 的 'BoundingBox' 和 'Area' 属性,就能拿到每个数字的大致位置。
matlab复制% 先用 Otsu 阈值做初版二值化(后续小节再细说)
level = graythresh(gray);
bw = imbinarize(gray, level);
% 假设数字是深色,背景是浅色,取反让数字前景为1
if mean(bw(:)) > 0.5
bw = ~bw;
end
% 连通域分析
cc = bwconncomp(bw);
stats = regionprops(cc, 'BoundingBox', 'Area');
% 过滤掉面积过小的噪声区域
minArea = 50;
validIdx = [stats.Area] > minArea;
boxes = round(vertcat(stats(validIdx).BoundingBox));
过滤面积小于 minArea 的连通域,是为了去掉纸张上的噪点、污渍、扫描杂质。这个阈值要看你的实际图片尺寸,一般取图像总像素数的千分之一量级比较合理。比如 400×400 的图,总像素 16 万,千分之一就是 160,但手写数字笔画本身面积会比较大,所以取 50 是给噪声留了一点放宽空间。
如果你的图片数字排列比较整齐,比如一行数字,还可以用投影法来定位。投影法的思路是:把二值图在水平方向和垂直方向分别做像素累加,得到两个投影曲线,曲线的谷底就是数字行、列之间的空隙,峰顶就是数字密集区域。这种方法对"整行数字在同一水平线"的场景非常好用,比如答题卡上的数字编号。
`
