拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Python+KNN手写拼音识别课程设计:图像预处理与分类实战

简介面向高校机器学习课程设计场景这份基于Python开发的手写拼音识别资源以KNNK最近邻算法为分类核心覆盖从手写图像输入到拼音类别输出的完整流程。包体包含2589个文件主体为1649个txt与924个jpg前者多用于标签或特征数据组织后者为手写拼音样本图片同时提供4个Python源码、4个xml配置文件、1个docx设计报告及若干辅助说明文件资源总大小仅1.79MB轻量且便于本地运行调试。配套的设计报告涵盖算法原理、数据集构造与实现细节源码和图片数据可直接运行验证适合学生用于课程设计、毕业设计或KNN算法入门实践。数据规模适中且分类逻辑清晰学习者还可自行扩展手写样本或调整K值观察分类效果。目前已有170人学习下载兼顾完整性与易用性对需要快速搭建手写识别原型的学习者较有参考价值。1. 手写拼音识别为什么说这是入门 KNN 最合适的一套课程设计同样是手写拼音识别有人跑通示例只要半小时有人折腾三天还在跟灰度图较劲。差别不在 KNN 算法本身——它的逻辑就是找最近的 k 个邻居投票真正耗时的是把一张张扫描图变成能喂给分类器的向量。这份基于 Python 的手写拼音识别课程设计包里设计报告加源码加图片样本齐整核心是 KNN 最近邻分类适合刚学 Python 和机器学习、想交一份能演示能答辩的课程设计的人。如果你已经会调 sklearn别再花时间读理论直接跳到第三章预处理和第五章避坑那才是这份资源里最值钱的部分。2. KNN 做手写拼音识别的完整链路从“最近邻居”到分类投票2.1 为什么选 KNN零训练参数但预测要付出全量计算的代价K 最近邻的逻辑一句话能说清给你一个未知样本在特征空间里找离它最近的 k 个已知样本然后看这 k 个样本的标签少数服从多数。手写拼音识别正好是个多分类问题——把汉字拼音的声母韵母或完整音节当作类别比如 a、o、e、b、p、m、f 这些KNN 不需要训练阶段去拟合权重所以课程设计阶段不需要 GPU不用调学习率只要样本整理得当一个人用笔记本就能跑完。但这个“懒惰学习”也不是白捡便宜没有显式训练意味着每次预测都要实时计算未知样本和所有训练样本的距离。样本量上千时预测一次就要做几千次距离运算。资源里几百张 jpg 这样的体量还好如果往上堆到上万张图就要考虑 KDTree 或者换算法了。2.2 把图片变成特征向量像素展开、尺寸统一、归一化KNN 没法直接吃图片需要把二维图像变成一维向量。以常见的 28×28 灰度图为例展开就是 784 维向量。手写拼音字符和手写数字不一样的地方在于笔画结构更复杂、可能出现连笔所以尺寸统一要格外讲究。from PIL import Image import numpy as np def load_and_flatten(img_path, size(28, 28)): # 统一转灰度再缩放到固定尺寸 img Image.open(img_path).convert(L).resize(size) arr np.asarray(img, dtypenp.float32) # 除以 255 把像素值归一化到 0~1避免量纲影响距离计算 arr arr / 255.0 return arr.reshape(-1) vector load_and_flatten(3_88.jpg) print(vector.shape) # (784,)这里 PIL 的 resize 默认用双线性插值对宽高比严重失衡的手写图直接 resize 会被拉伸变形。更稳的做法是先把图按最长边等比缩放再贴到画布上后面的预处理章节会展开。归一化这一步别省欧氏距离对绝对像素值敏感同样是深色笔画0~255 和 0~1 两种量纲算出来的距离完全不同。参数说明convert(L)把三通道 RGB 压成单通道灰度resize(size)做双线性缩放(28, 28)是 MNIST 的经典尺寸手写拼音笔画细节多我一般会试 32×32 或者 48×48信息量更大但特征维数也跟着涨。2.3 距离度量和投票规则欧氏距离、曼哈顿距离与 k 值选择特征向量定下来后第二个关键参数是距离度量。scikit-learn 的 KNeighborsClassifier 默认用闵可夫斯基距离p2 时退化为欧氏距离p1 时是曼哈顿距离。对手写笔画图来说欧氏距离能放大较大像素差异曼哈顿距离对个别噪点更钝感。我一般先在欧氏距离下跑一遍再试曼哈顿距离对比改动很小。距离度量计算方式适合场景欧氏距离sqrt(sum((x-y)^2))像素级特征首选区分度好曼哈顿距离sum(abs(x-y))图像噪点多、想抑制离群像素影响余弦相似度向量夹角一般不用在图像像素特征上k 值是绕不开的玄学。k1 最灵活但容易过拟合k 偏大又会让距离远的样本参与投票。手写拼音类别多不同拼音之间很像的比例比手写数字高我一般会遍历 k 从 1 到 15每个 k 值下做十折交叉验证而不是拍脑袋选 5。from sklearn.model_selection import cross_val_score for k in range(1, 16): knn KNeighborsClassifier(n_neighborsk) scores cross_val_score(knn, X_train, y_train, cv10) print(k, scores.mean())这段脚本跑完后把 k 值和对应准确率画成曲线选拐点处的 k。如果曲线一路向下说明训练样本本身区分度不够问题在预处理不在算法。3. 把 jpg 变成训练集灰度化、二值化、单字切分与标签对齐3.1 灰度化与二值化别再让背景噪点参与距离计算KNN 的距离是逐像素算的背景噪点越多距离越脏。预处理第一步是把彩色扫描图转成灰度再用二值化把笔画和背景彻底分开。固定阈值 127 最省事但很脆弱不同纸张底色、不同笔压会让灰度分布漂移。我习惯用 OTSU 自适应阈值按整张图的灰度直方图自动找分割点。from skimage.io import imread from skimage.color import rgb2gray from skimage.filters import threshold_otsu import numpy as np def to_binary(img_path): img imread(img_path) if img.ndim 3: img rgb2gray(img) thresh threshold_otsu(img) # 小于阈值的算前景笔画 return (img thresh).astype(np.uint8)逻辑说明rgb2gray把三通道压成一通道threshold_otsu返回一个全局阈值img thresh把笔画置 1、背景置 0。参数说明有些样本前景亮背景暗阈值方向就要反过来写成img thresh这一步建议抽样打印几张三值图确认别扫完所有图再回头看。3.2 单字切分投影法把整行拼音拆成一个个字符如果原始图是整行拼音必须先把单字切出来再训练。最常见的做法是垂直投影把二值图按列累加前景像素字符所在的列投影值大字符之间的空隙投影值接近 0找到连续非 0 段就是单个字符的左右边界。def vertical_projection(binary_img): # 按列累加前景像素返回一维数组 return binary_img.sum(axis0) proj vertical_projection(binary_img) # 从左到右扫描连续非0段就是一个字符 bounds [] in_char False for i, val in enumerate(proj): if val 0 and not in_char: start i in_char True elif val 0 and in_char: end i bounds.append((start, end)) in_char False这段代码手工实现了切分逻辑in_char记录是否正处在一个字符内遇到投影值从 0 变正时记起始列从正变 0 时记结束列。切出的每个片段再按宽度截取原图区域统一 resize 成固定尺寸。连笔会导致字符粘连投影切不开这时候要么人工调整阈值要么改用轮廓外接矩形切割。3.3 图片命名与标签对齐4_96.jpg 这类文件名的信息量这套资源里的图片命名是“数字_数字.jpg”的格式比如 4_96.jpg。课程设计里这类命名通常约定为“类别_序号.jpg”下划线前的数字就是标签。训练时直接从文件名解析标签最省事不用另建标注文件。import re import os def label_from_filename(path): name os.path.basename(path) m re.match(r(\d)_\d\.jpg$, name) if not m: raise ValueError(funexpected filename: {name}) return int(m.group(1))逻辑说明re.match用正则从文件名里抓第一个数字字段。参数说明如果资源里的命名规则不是这样先打开目录随便看几张确认标签字段在第几个下划线之前再改正则硬编码路径虽然能跑但答辩时会被一问就卡住。3.4 划分数据集train_test_split 要固定 random_state 和分层抽样手写拼音类别多某些拼音可能样本很少直接随机切分容易让某个类别在测试集里消失。固定random_state保证每次跑结果一致stratify按类别比例分层抽样。from sklearn.model_selection import train_test_split X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy )参数说明test_size0.2表示 20% 数据留作测试数据量只有几百张时可以改成 0.3stratifyy要求训练集和测试集的类别分布比例一致这样算出的准确率才有参考价值。4. 源码包主流程拆解从项目结构到一键训练与预测4.1 文件清单哪些是代码、哪些是数据、哪些不用管先看清资源里有什么再决定从哪里动手。这套资源里文件不多但对新手来说有几个容易误解的文件作用处理建议设计报告.docx课程设计文档包含原理、伪代码、结果分析答辩前通读一遍重点看算法流程章节.gitignoreGit 版本控制忽略规则放本地仓库用不参与运行Shouxiepinyin.imlIDE 模块描述文件PyCharm 或 IDEA 打开项目时自动生成不用管4_96.jpg、4_97.jpg、3_76.jpg、3_88.jpg 等手写拼音样本图训练数据注意像素尺寸是否统一.iml文件是 IntelliJ 系 IDE 的项目元数据删了也能跑.gitignore控制哪些文件不提交到 Git跟算法无关。先别急着写代码把 jpg 全部翻出来看一遍确认是单字图还是整行图这决定了要不要先做切分。4.2 主流程代码组装数据集、训练、评估把前面几步串起来就是一整套可运行的主流程。关键是把数据集读取、划分、训练、评估拆成函数答辩时能一步步讲清楚。import os import re import numpy as np from PIL import Image from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier from sklearn.metrics import accuracy_score def load_and_flatten(img_path, size(28, 28)): img Image.open(img_path).convert(L).resize(size) arr np.asarray(img, dtypenp.float32) / 255.0 return arr.reshape(-1) def load_dataset(img_dir): X, y [], [] for fname in os.listdir(img_dir): if not fname.lower().endswith(.jpg): continue label int(re.match(r(\d)_, fname).group(1)) vec load_and_flatten(os.path.join(img_dir, fname)) X.append(vec) y.append(label) return np.array(X), np.array(y) X, y load_dataset(data) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42, stratifyy ) clf KNeighborsClassifier(n_neighbors5, metriceuclidean) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(accuracy:, accuracy_score(y_test, y_pred))逻辑说明load_dataset遍历目录下的每张 jpg用正则解出标签把特征向量和标签分别堆进两个列表最后转成 numpy 数组。KNeighborsClassifier只是声明了一个分类器对象真正干活的是fit和predict两行。参数说明n_neighbors5可调metriceuclidean对应欧氏距离如果你在前面实验发现曼哈顿距离更好改成metricmanhattan即可。这一步跑通后先别急着调参。把accuracy_score那一行拆开输出y_pred和y_test对比一下看看错的是哪些拼音比只盯着一个准确率数字有用得多。4.3 保存与加载模型joblib 比 pickle 更省心有人每次预测都重跑一遍训练样本多了纯粹浪费时间。训练完的 KNN 分类器可以用 joblib 存成文件下次直接加载。KNN 虽然不训练权重但要保存全部训练样本和标签模型文件会随样本量增大。import joblib # 保存模型到本地文件 joblib.dump(clf, model.pkl) # 从文件恢复分类器 clf2 joblib.load(model.pkl) new_pred clf2.predict(new_vector)逻辑说明joblib.dump把分类器对象序列化到磁盘joblib.load读回来直接用不需要重新fit。参数说明模型文件和 sklearn 版本强相关新环境加载旧模型容易报版本不兼容解决办法是同一个环境里保存和加载或者别换 sklearn 大版本。5. 手写拼音识别避坑手册五个会浪费一整天的典型问题5.1 训练集上准换自己的字全错离线测试的准确率有水分现象跑测试集准确率 95% 以上老师现场写几个拼音字符识别结果一塌糊涂。原因训练集里的手写样本来源单一要么是同一个人写的要么是固定字体渲染出来的。新写字符的笔画粗细、倾斜角度、落笔位置跟训练集差异太大KNN 找“最近邻居”时找来的全是风格不匹配的样本。解决先看训练集里每个类别的实际样本数量太少就自己补写。补写后做数据增强是个实用手段——把每张图随机旋转几度、平移几个像素、缩放一点相当于变出多张不同样本。from scipy.ndimage import rotate, shift def augment(img): # 随机旋转±5度随机平移±2个像素 img rotate(img, anglenp.random.uniform(-5, 5), reshapeFalse) img shift(img, shiftnp.random.uniform(-2, 2, size2)) return img给训练集每张图跑几次augment样本量扩大 3 到 5 倍应对写字风格变化会稳很多。5.2 k3 最好k5 反而下降投票机制被远样本带偏现象遍历 k 值发现 k3 准确率最高调到 5 反而明显下降。原因手写拼音类别之间相似度太高比如 n 和 m、u 和 v距离稍远的样本很可能来自另一个类别。k 越大越远的样本越有机会参与投票把正确的多数票冲掉。解决一是用分层抽样保证每个类别样本均衡二是给投票加距离权重让近的邻居话语权更大。把分类器参数改成weightsdistanceKNN 会把距离倒数作为投票权重多数时候能压过等权投票。5.3 样本一多预测卡顿内存爆KNN 的全量距离计算是瓶颈现象训练几百张图还流畅样本加到两三千张之后预测一批数据要等好几秒内存占用肉眼可见往上涨。原因KNN 是懒惰学习预测时要实时算未知样本和所有训练样本的距离。样本量和特征维数一起涨距离矩阵的规模就是爆炸式的。解决分类器里指定algorithmkd_tree用 KD 树做近邻搜索比默认暴力计算快得多。特征维数高时可以先做主成分分析降维把 784 维压到 50 维左右距离计算量小一个量级。clf KNeighborsClassifier( n_neighbors5, algorithmkd_tree, leaf_size40 )leaf_size控制 KD 树叶子节点的大小默认 30 或 40 都行样本量小的时候调了也没感觉样本量大时值得来回试。5.4 设计报告和代码对不上答辩现场被问住现象设计报告里写的是算法伪代码和数学推导代码里用的却是 sklearn 封装答辩时老师顺着报告问实现细节答不上来。原因报告和代码没有对齐报告是“理论版”代码是“调包版”中间缺了一层转化。解决报告里明确写一句“本实现基于 scikit-learn 的 KNeighborsClassifier内部使用 KDTree 进行近邻搜索”然后把fit、predict的调用代码贴进报告附录再补一张自己写的预处理流程图。这样理论、实现、代码三者就对上了。5.5 sklearn 版本变动导致模型加载失败环境一致性要提前锁死现象今天训练完保存模型过几天打开项目加载模型报错提示 joblib 或者 sklearn 版本不兼容。原因模型文件里存了算法内部结构版本升级后类定义和参数名变了旧文件读不出来。解决在项目根目录放一个requirements.txt把关键依赖版本固定下来换机器时先装同一套版本再跑。pip install scikit-learn1.3.2 joblib1.3.2 pillow版本号按你自己环境实际装的写重点是让助教或者老师复现时不会因为版本问题翻车。6. 最后一步用混淆矩阵找软肋、错题回写与距离加权投票6.1 混淆矩阵看清哪些拼音之间最容易被搞混准确率只是一个数字混淆矩阵才告诉你错误长在哪。把测试集的预测结果和真实标签拉出来画矩阵对角线越亮越好非对角线上的亮点就是最容易混淆的拼音对。from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt cm confusion_matrix(y_test, y_pred) ConfusionMatrixDisplay(cm).plot() plt.show()拿到这张图你会很直观地看到比如“m”经常被识别成“n”“u”经常被识别成“v”这类相似字符就是后续优化的主攻方向。别说“准确率挺高的就直接交”混淆矩阵能在五分钟内给你一个明确的方向。6.2 错题回写把预测错的图补进训练集混淆矩阵找出易混类别后把预测错的样本图片挑出来人工确认真实标签再补进训练集重新训练。这相当于给模型开了一个“错题本”是成本最低的迭代方式。做法是把y_pred ! y_test对应的图片路径打印出来逐张看过之后归入新目录重新跑训练流程。几次迭代后准确率提升比调 k 值明显得多。6.3 距离加权投票给“更近的邻居”更大话语权我前面提到weightsdistance这个参数再多说一句等权投票看的是“哪类人多”距离加权看的是“哪类人离我近”。当易混类别之间样本数量差距大时距离加权能有效防止远处的大类把近处的小类压掉。把这行参数替换进去重新跑一遍交叉验证评分绝大多数时候不会比等权差。从那以后我每次跑这类课程设计都会强制走一遍“混淆矩阵找软肋 → 错题回写 → 距离加权对比”的流程整套下来至少能挤出五个点的准确率。这套资源里设计报告、源码和 jpg 数据都是现成的对照上面的思路先跑通基线再按第五节踩坑清单逐项排一遍比自己从零开始写省力得多。希望帮到你。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门