ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python+OpenCV的手写数字识别实战:从KNN训练到实时识别

基于Python+OpenCV的手写数字识别实战:从KNN训练到实时识别 简介基于Python和OpenCV实现的手写数字识别项目源码包含完整的文档说明适用于计算机相关专业期末大作业、课程设计也适合希望动手实践机器学习与图像处理的学习者。项目经导师指导并获认可是一份99分高分结课作业代码与资料完整可用可作课程设计或期末大作业的参考范本。压缩包共2000个文件包含1995张JPG手写数字样本图片、4个Python脚本和1个Markdown说明文档整体大小22.76MB。源码文件结构清晰适合在常用Python环境中运行调试图片数据集可直接用于模型训练与测试Python脚本实现图像预处理、特征提取和识别流程说明文档则梳理了设计思路与使用方式。目前已有235人浏览学习能够帮助使用者快速复现项目、理解识别原理并为撰写课程报告或准备答辩提供参考。1. 手写数字识别被当成“入门玩具”但它其实在考三件事手写数字识别MNIST手写数字识别几乎是每个学 Python 和 OpenCV 的人都会撞上的项目期末大作业里更是常客。表面上看它只是把 0 到 9 的图片分个类但真要交一份能跑的源码和像样的文档说明你其实要同时解决三件事图像预处理怎么做、分类器怎么选、以及识别失败时怎么排查。这三件事恰好对应了 OpenCV 最核心的使用场景。很多人一上来就想着搭卷积神经网络用 PyTorch 或者 TensorFlow 把 MNIST 刷到 99% 以上但真到了期末作业这个场景CNN 不一定是最好的选择——环境配置重、训练时间长、文档不好写。我一般会建议先用 OpenCV 自带的 KNN 或 SVM 把完整流程跑通再把深度学习作为进阶章节写进文档。这套方案在普通笔记本上几分钟训完代码量在 300 行以内答辩时还能现场演示摄像头实时识别。本文就把这条路线完整拆开从环境搭建到参数调优再到常见的翻车现场按可复现的方式过一遍。2. 从图像到数字手写数字识别的两条技术路线与选型理由2.1 传统视觉方案为什么在期末作业里更实用手写数字识别一共有两条技术路线。第一条是传统图像处理路线流程是图像预处理 → 特征提取 → 分类器分类。预处理包括灰度化、二值化、去噪、归一化特征提取就是找出数字的轮廓、像素分布或者方向梯度直方图HOG分类器用 KNN、SVM 或者随机森林。第二条是深度学习路线直接把原始像素喂给卷积神经网络让模型自己学特征。两条路线差异很大。CNN 在 MNIST 测试集上轻松到 99% 以上但换来的是更高的环境门槛。PyTorch 或者 TensorFlow 的安装、CUDA 的版本匹配如果你用 GPU、显存占用任何一个环节出问题对期末大作业来说都是灾难。更关键的是CNN 的“可解释性”差你很难在文档里讲清楚“为什么这张图被识别成了 5”而传统视觉方案每一步都有明确的中间结果可以截图——二值化后的图长什么样、轮廓框在哪、特征向量是什么样。这对写实验分析非常有利。我见过太多选 CNN 做期末作业的同学最后卡在环境配置上代码跑不起来文档只能抄。反过来用 OpenCV 的 KNN核心代码就三四十行剩下的工作量全在预处理上这才是这个项目真正想考察的能力。如果你后面要申算法岗位或者读研用传统方案把特征工程、模型对比、参数调优讲透比单纯说“我用了 LeNet”更有说服力。2.2 基于 KNN 的识别流程与关键参数KNNK 近邻的原理可以一句讲完对于一个待识别的图片计算它与所有训练样本的距离取距离最近的 K 个样本少数服从多数得票最多的那个数字就是识别结果。听起来像玄学但在手写数字这个场景里因为数字本身结构简单、类别只有 10 个、样本数据规范KNN 的效果出奇地好。完整流程分五步。第一步是灰度化和二值化把输入图像从三维彩色变成单通道黑白图前景是白、背景是黑这一步我用的是cv2.cvtColor和cv2.threshold。第二步是轮廓检测与裁剪用cv2.findContours找到数字的外轮廓根据轮廓坐标把数字区域裁出来。第三步是尺寸归一化OpenCV 的 KNN 要求输入特征向量的维度固定我需要把裁剪出来的数字图缩放到统一尺寸。第四步是特征向量提取常见的做法是把 20×20 的图像直接拉平成一维的 400 维向量或者用 HOG 特征降维后又保持区分度。第五步是训练与预测把特征向量交给cv2.ml.KNearest_create()训练再用findNearest预测。这里有几个参数直接影响最终准确率。K 值一般取 3 到 5太小容易受噪声干扰太大又会让边界样本投票时被拉偏。二值化阈值通常取 127 或者用大津法OTSU 自动计算阈值具体看你的图画质。尺寸归一化我习惯用 28×28因为这是 MNIST 的标准尺寸参考资料多后续如果你想切换到 CNN 也方便。特征向量的选择上直接拉平像素是最简单但最怕光照变化的做法HOG 特征更稳但对 OpenCV 的版本有要求。import cv2 import numpy as np def preprocess_digit(img): # 输入是一张三通道彩色图输出是拉平后的特征向量 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化像素值大于阈值的设为白色(255)小于的设为黑色(0) # 参数 127 是固定阈值实测光照不均匀时换成 cv2.THRESH_OTSU 更稳 _, binary cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV) # 找轮廓只取最外层减少干扰 contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 取面积最大的轮廓避免把噪点当数字 c max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(c) # 裁剪并加一点边距防止数字贴边导致特征变形 margin 5 x max(0, x - margin) y max(0, y - margin) w min(binary.shape[1] - x, w 2 * margin) h min(binary.shape[0] - y, h 2 * margin) digit binary[y:yh, x:xw] # 统一缩放到 28x28保持长宽比不变多余部分填充黑边 if digit.size 0: return None scale 20.0 / max(w, h) new_w max(1, int(w * scale)) new_h max(1, int(h * scale)) resized cv2.resize(digit, (new_w, new_h), interpolationcv2.INTER_AREA) # 居中到 28x28 画布 canvas np.zeros((28, 28), dtypenp.uint8) x_off (28 - new_w) // 2 y_off (28 - new_h) // 2 canvas[y_off:y_offnew_h, x_off:x_offnew_w] resized # 拉平成一维向量并转为 float32KNN 要求这个类型 feature canvas.reshape(1, 784).astype(np.float32) return feature这段代码里几个细节值得展开。cv2.threshold的THRESH_BINARY_INV是反二值化也就是前景写成白、背景写成黑。因为findContours处理白色物体更容易所以先反一下。RETR_EXTERNAL只提取最外层轮廓防止数字内部的孔洞造成误判。max(contours, keycv2.contourArea)取面积最大的轮廓这能过滤掉图片角落的小噪点但如果噪点本身很大这行代码就没救。尺寸归一化用INTER_AREA而不是默认的线性插值实测缩放含黑白的二值图时面积插值能保留边缘清晰度这对 KNN 的准确率影响不小。3. 基于 PythonOpenCV 的最小实现从数据集到手写数字识别3.1 环境准备Python、OpenCV、NumPy 的版本搭配与安装环境是这个项目里最容易劝退新手的环节尤其是 OpenCV 安装。最常见的报错是ModuleNotFoundError: No module named cv2绝大部分原因是装错了包——装了 opencv-contrib-python 但 import 时写错大小写或者根本没装上。我的建议是直接用 pip 安装版本搭配用 Python 3.9 以上、OpenCV 4.x 任意小版本、NumPy 1.21 以上。不要自己去编译源码除非你对 CMake 和编译链很有把握否则opencv-python这个预编译包已经够用。如果你要跑摄像头实时识别还需要额外装一个opencv-contrib-python它包含了cv2.VideoCapture所需的扩展模块。这里有个血泪教训安装时不要同时装 opencv-python 和 opencv-contrib-python两个包会互相覆盖导致某些模块 import 报错。# 建议用虚拟环境避免和系统 Python 打架 python -m venv digit_env # Windows 下激活虚拟环境 digit_env\Scripts\activate # Linux/macOS 下激活虚拟环境 # source digit_env/bin/activate pip install numpy opencv-python opencv-contrib-python安装完成后用一行命令验证python -c import cv2; print(cv2.__version__)如果能打印出 4.x 版本号环境就通了。如果提示找不到包先检查 pip 的 Python 路径和你运行 Python 的路径是否一致。很多人翻车是因为系统里装了多个 Pythonpip 装到了一个解释器命令行跑的是另一个。在虚拟环境里操作可以一劳永逸地避开这个问题。3.2 用 OpenCV 加载 MNIST 数据集并完成预处理MNIST 数据集是手写数字识别的标准基准训练集有 6 万张 28×28 的手写数字图片测试集有 1 万张。很多人的第一反应是去网上下载解压好的 PNG 图片但更规范的做法是直接读取原始格式的 idx 文件。每个样本由 784 个像素值组成28×28784像素值范围 0 到 2550 表示背景255 表示前景。我没用框架自带的数据集加载函数因为 OpenCV 本身不提供 MNIST 下载。常见做法是自己写一个解析函数。idx 文件的格式极简前 4 个字节是魔数后 4 个字节是样本数然后是图像的行数和列数接着就是纯像素数据。读取时要处理好字节序文件是大端存储而 Python 默认解析是小端。import struct import numpy as np import cv2 def load_mnist_images(filename): with open(filename, rb) as f: # 前4字节魔数后4字节样本数再4字节行数再4字节列数 magic, num, rows, cols struct.unpack(IIII, f.read(16)) # 一次性读入所有像素按 num*rows*cols 重排 buf f.read() data np.frombuffer(buf, dtypenp.uint8).reshape(num, rows, cols) return data def load_mnist_labels(filename): with open(filename, rb) as f: magic, num struct.unpack(II, f.read(8)) labels np.frombuffer(f.read(), dtypenp.uint8) return labels # 假设文件已经下载到 data/ 目录 train_images load_mnist_images(data/train-images.idx3-ubyte) train_labels load_mnist_labels(data/train-labels.idx1-ubyte) test_images load_mnist_images(data/t10k-images.idx3-ubyte) test_labels load_mnist_labels(data/t10k-labels.idx1-ubyte) print(f训练集: {train_images.shape}, 测试集: {test_images.shape})MNIST 原始数据的像素是 0 到 255 的灰度值背景是黑、前景是白已经有明确的前后景分离。但在喂给 KNN 之前我通常还会做一次二值化和归一化。二值化把 128 以上的像素设为 255以下的设为 0这样能消除灰度抖动带来的噪声。归一化则是把像素值从 0-255 缩放到 0-1防止大数值特征在距离计算时权重过高。后面这个操作对 KNN 很重要因为 KNN 依赖距离度量如果某个维度的数值范围特别大它会主导整个距离计算。cv2.ml.KNearest对输入类型有严格要求必须是float32整数数组直接传进去会报类型错误。3.3 训练 KNN 分类器并保存模型预处理做完之后训练本身极其简单。这一步的代码是全网都在抄的经典写法但它真正容易翻车的地方在数据格式上。训练集 6 万张图每张 28×28你要把它重排成 60000×784 的矩阵每行是一个样本的特征向量。标签要重排成 60000×1 的列向量因为train函数接收的标签格式是单列不是单行。我见过有人在这里用train_labels.reshape(1, -1)导致训练报错或者准确率直接崩掉。# 特征矩阵训练集60000张每张拉平成784维 train_features train_images.reshape(-1, 784).astype(np.float32) # 标签必须是 Nx1 形状 train_labels_2d train_labels.reshape(-1, 1).astype(np.int32) # 创建并训练 KNN 分类器 knn cv2.ml.KNearest_create() knn.setDefaultK(3) # K值取3减少边界误差 knn.setIsClassifier(True) # 分类模式不是回归 knn.train(train_features, cv2.ml.ROW_SAMPLE, train_labels_2d) # 保存模型后面直接加载不用每次重新训练 knn.save(mnist_knn.xml)setDefaultK(3)的意思是预测时默认取 3 个最近邻。K 越小分类边界越复杂过拟合风险越大K 越大模型越平滑但在数字形状相似时容易误判比如 4 和 9。setIsClassifier(True)必须显式设置因为 KNN 在 OpenCV 里既能做分类也能做回归默认值是分类还是回归取决于版本显式指定可以避免版本差异带来的坑。训练完成后调用save保存模型模型是 XML 格式只有几百 KB交作业时连同代码一起打包对方可以直接加载运行而不需要重新训练。3.4 单张图片识别与摄像头实时识别模型训练好之后预测一件很简单的事但要注意findNearest的返回值。它返回三个值预测结果、最近邻的距离向量、最近邻的标签向量。新手容易直接拿第一个值当结果但findNearest的返回值里第一个值是ret表示是否成功第二个才是预测结果。另外输入必须是浮点型二维数组即使只有一张图也要保持二维形状。def predict_digit(image_path, knn_model): img cv2.imread(image_path) if img is None: raise FileNotFoundError(f图片不存在: {image_path}) # 预处理得到 1x784 特征向量 feature preprocess_digit(img) if feature is None: return -1, 0 # 没找到轮廓 # findNearest 返回 (retval, results, neighborResponses, dist) ret, result, neighbors, dist knn_model.findNearest(feature, k3) # result 是 1x1 数组取 [0][0] 就是预测的数字 predicted int(result[0][0]) # dist[0][0] 是最近邻距离值越小代表越接近 confidence 1.0 / (1.0 dist[0][0]) return predicted, confidence # 加载保存好的模型 knn cv2.ml.KNearest_load(mnist_knn.xml) digit, conf predict_digit(my_digit.png, knn) print(f识别结果: {digit}, 置信度: {conf:.2f})这个置信度的计算方法是经验值把最近邻距离压缩到 0 到 1 之间。距离为 0 时置信度是 1.0距离无穷大时趋近 0。虽然它不是严格的后验概率但在演示时能直观告诉用户这次识别有多“确定”避免用户看到错误答案时一头雾水。摄像头实时识别只是把cv2.imread换成cv2.VideoCapture每帧都做一次预处理和预测。要控制识别频率不要每帧都跑完整流程否则会卡顿常见的做法是每隔 5 帧识别一次或者把预处理分辨率调低到 80×60。4. 识别效果上不去的排查思路样本、二值化、特征与距离度量4.1 测试集准确率 90%自己的手写图却全错这是期末作业里最经典的翻车现场。模型在 MNIST 测试集上准确率 92%一拿自己拍的照片或者用鼠标写的数字去识别几乎全错。原因很简单MNIST 训练样本是规规矩矩的 28×28 灰度图数字位于正中心笔画清晰背景干净。而你用摄像头拍的图光照不均匀、数字可能歪斜、笔画粗细不一、甚至还有阴影。这属于训练数据与应用数据分布不一致也就是迁移学习中说的域偏移。解决思路有两个方向。一是预处理往“MNIST 风格”靠把图像二值化之后做噪声去除cv2.medianBlur再做尺寸归一化并居中尽可能抹掉你输入的个性化差异。二是扩充训练数据把自己手写的几十个样本加入到训练集里重新训练这是最直接的效果提升手段。一个很实用的技巧是把训练集变成“混合训练集”在 MNIST 的 6 万张里随机抽 5 千张加上自己手写标注的 200 张模型在自己的手写风格上会明显变准。4.2 OpenCV 安装报错import cv2失败现象pip 安装显示成功但python -c import cv2报ModuleNotFoundError或者报ImportError: DLL load failed。前者是环境路径问题后者在 Windows 上最常见原因是缺少 Visual C 运行库或者 Python 是 32 位而 OpenCV 装的是 64 位。原因Python 环境的 sys.path 和 pip 安装的 site-packages 不在同一个解释器下或者 OpenCV 的依赖比如 numpy版本不兼容。解决先确认pip --version指向的解释器和命令行运行的 Python 是不是同一个。如果存在多版本 Python用python -m pip install opencv-python来强制安装到当前解释器。DLL load failed的话先升级 numpy然后安装微软 Visual C Redistributable 2019 以上版本。再不行就卸载重装pip uninstall opencv-python opencv-contrib-python然后只装opencv-contrib-python它已经包含了主模块和扩展模块。4.3 二值化方向搞反黑色数字识别成空白现象识别结果永远是 0 或者根本找不到轮廓。原因cv2.threshold的THRESH_BINARY和THRESH_BINARY_INV方向反了。如果你的输入图是白底黑字用THRESH_BINARY会把数字变成黑色、背景变成白色。这对人的视觉看起来没问题但 OpenCV 的轮廓检测会认为白色背景才是“物体”于是把整个图的边界当成轮廓裁出来的区域就是整张图特征向量完全没意义。解决先确定你的输入是“黑底白字”还是“白底黑字”。如果是白底黑字用THRESH_BINARY_INV这样数字为白色、背景为黑色。一个保险的做法是动态判断比较前景和背景像素数量如果白色像素占比超过 50%就反转一次。另外MNIST 原始数据是黑底白字如果你的预处理函数是给 MNIST 用的而手写识别时又用同一套函数处理白底黑字的摄像头图也会踩这个坑。建议在代码里加一个invert_if_needed的参数默认不反转。4.4 KNN 算距离时“大数字吃掉小数字”现象把像素值 0-255 直接拉平当特征准确率只有 80%而别人同样是 KNN 却有 92%。原因KNN 的距离度量默认是欧氏距离计算公式是sqrt(sum((x_i - y_i)^2))。像素值范围是 0-255如果某个维度差异是 100平方后就是 10000而其他维度差异是 1平方后只有 1。结果是高灰度值维度的差异主导了整个距离而低灰度值维度的细微信号比如笔画边缘的轻微灰度变化被完全忽略。这就是网上常说的“大数吃小数”。解决把特征归一化到 0-1或者用标准化均值 0、方差 1。归一化后每个维度的权重均衡KNN 的距离计算才有意义。另一个可选方案是改用曼哈顿距离但 OpenCV 的 KNN 不支持自定义距离函数所以归一化才是正解。特征归一化之后如果准确率还是不上来再检查 K 值和训练集是否需要做数据增强。5. 把大作业做成“能演示”的系统鼠标画板与实时摄像头识别5.1 用鼠标直接手写数字不在文件系统里来回切换纯命令行识别图片数字在答辩演示时非常吃亏。对方看到的是你在终端敲一行命令然后打印出一行结果没有任何视觉反馈。更常见也更稳妥的做法是写一个窗口程序用鼠标在黑色画布上写数字按一个键就识别再按一个键清除画布。这样既能展示 OpenCV 的图像处理过程又能展示模型的实时预测。这个程序的核心是用 OpenCV 的高阶 GUI 接口cv2.setMouseCallback来捕获鼠标事件再结合cv2.line在画布上画线。识别时调用前面写好的preprocess_digit和knn.findNearest把结果显示在窗口标题或者画布一角。import cv2 import numpy as np canvas np.zeros((280, 280, 3), dtypenp.uint8) # 10倍放大画布 drawing False last_point None def on_mouse(event, x, y, flags, param): global drawing, last_point if event cv2.EVENT_LBUTTONDOWN: drawing True last_point (x, y) elif event cv2.EVENT_MOUSEMOVE and drawing: # 在两张点之间画一条线让笔画连续 cv2.line(canvas, last_point, (x, y), (255, 255, 255), 15) last_point (x, y) elif event cv2.EVENT_LBUTTONUP: drawing False cv2.namedWindow(Draw Digit) cv2.setMouseCallback(Draw Digit, on_mouse) knn cv2.ml.KNearest_load(mnist_knn.xml) while True: cv2.imshow(Draw Digit, canvas) key cv2.waitKey(1) 0xFF if key ord(r): # r 清除画布 canvas[:] 0 elif key ord(s): # s 识别当前画布内容 # 画布是 280x280预处理函数期望收到 28x28 的输入 small cv2.resize(canvas, (28, 28)) feature preprocess_digit(small) if feature is not None: ret, result, neighbors, dist knn.findNearest(feature, k3) # 把结果画在画布左上角 cv2.putText(canvas, fPredict: {int(result[0][0])}, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) elif key 27: # ESC 退出 break cv2.destroyAllWindows()这里有一个容易忽视的坑画布是 280×280预处理函数内部期望的输入是任意尺寸但cv2.resize到 28×28 这一步颠倒了。正确做法是先把 canvas 缩放到 28×28再送进preprocess_digit。因为preprocess_digit里本身有灰度化和二值化所以彩色画布直接缩放也没问题但如果你把缩放放在预处理之后特征维度就对不上了。5.2 使用 OpenCV 读取摄像头画面做实时数字识别鼠标画板适合近距离演示但如果你想展示“摄像头对着纸上的数字实时识别”就需要读视频流并每帧处理。这个场景的核心压力在性能上如果每帧都做完整预处理和 KNN 预测在低配笔记本上会明显掉帧。常见的优化是降采样。摄像头分辨率默认 640×480你不需要全分辨率处理把帧缩放到 160×120 甚至 80×60KNN 的预测耗时能降一半。另一个优化是 ROI 区域裁剪把画面中间的某个矩形区域当作识别区这样避免整帧分析同时还能在窗口上画一个绿色矩形框告诉用户“把数字放在框里”。我用的是灰度化后直接二值化不做模糊因为模糊的代价是边界信息丢失对数字识别来说得不偿失。识别频率控制也很关键。用time.time()做帧间隔判断每 0.2 秒识别一次识别结果叠加在当前帧上。这样画面保持流畅识别数字的结果又不会跳得太频繁。5.3 文档说明怎么写从算法说明到实验结果期末大作业的文档部分往往是决定成绩下限的因素。代码写得再好文档糊弄也会扣分。文档的常见结构是五块需求分析、方案选型、算法原理、核心代码说明、实验结果与总结。方案选型部分要把 KNN 和 CNN 对比列出说明你为什么选 KNN——训练时间、环境依赖、可解释性这三个理由足以说服老师。算法原理部分要给两个图一个是预处理流程图原图→灰度→二值→轮廓→归一化→特征向量用文字描述即可另一个是 KNN 决策流程。核心代码说明不要贴整段源码挑三个重点片段讲。实验结果部分不要只写准确率要写混淆矩阵——哪个数字最容易识别错。MNIST 里 4 和 9、3 和 8 是经典的易混淆对你在文档里写出这个现象并分析为什么是加分项。如果你自己写了 100 张手写数字做测试集把准确率写进去比只引用 MNIST 官方准确率有说服力得多。6. 进一步提高的三种路径数据增强、模型融合与GUI封装6.1 数据增强让 KNN 在小数据集上不那么差KNN 的准确率天花板受训练集质量限制。MNIST 本身是规范样本但你的手写测试图像往往不是 28×28 的规范格式。一个高效的办法是对训练集做随机旋转和位移生成一批“变形样本”补充进来。OpenCV 的cv2.getRotationMatrix2D和cv2.warpAffine就能完成这个事。做法是对每张训练图随机旋转 10 度以内随机平移 2 个像素以内生成 3 到 5 个副本。这样训练集从 6 万膨胀到 30 万左右。KNN 训练是在内存里直接算距离的没有“训练时间”的概念但预测时会明显变慢因为要遍历所有样本。折中方案是先用 6 万原始样本训练预测出错时再把增强样本加入重新训练这个“按需增强”策略是个人的一点经验遇到多变的手写风格时比直接全量增强更实用。6.2 模型融合把 KNN 和 SVM 的结果做一个投票单个模型总会有盲区模型融合不需要深度学习就能显著提升准确率。OpenCV 自带 SVM训练 SVC 分类器后把 KNN 和 SVM 的预测结果联合投票。实现要点是KNN 和 SVM 的特征输入必须一致否则融合没有意义。我这里直接复用归一化后的 784 维像素特征。融合的正确做法不是“两个模型都预测同一个标签就输出否则随便猜”而是用置信度加权。KNN 的置信度是 1/(1d)SVM 在 OpenCV 里默认不输出概率可以用predict返回的距离值做信心度量。把两个置信度归一化到同一尺度再加权投票融合准确率通常比单个模型高 1 到 2 个百分点。虽然幅度不大但写进文档的“实验对比”章节很好看。# KNN 预测 ret, knn_result, neighbors, dist knn.findNearest(feature, k3) knn_conf 1.0 / (1.0 dist[0][0]) # SVM 预测返回的是 -1 到 1 的距离值用它做置信度 _, svm_result svm.predict(feature) svm_conf abs(svm_result[0][0]) # 加权投票KNN 权重 0.6SVM 权重 0.4经验值可以格子调 if knn_conf * 0.6 svm_conf * 0.4: final_result knn_result[0][0] else: final_result svm_result[0][0]融合时有一个坑SVM 默认是多分类一对多策略OpenCV 的 SVM 参数如果没调好比如核函数选 RBF 但 gamma 没设预测结果可能全是同一个标签。解决方案是先用一个小验证集跑一遍看结果分布是否均匀如果是平均分布那就需要调整 SVM 参数了。6.3 把整套流程封装成一个类复用性会高很多代码写到后面预处理、训练、预测的逻辑散落在各个函数里维护起来很痛苦。我在做这个项目时会把它封装成一个DigitRecognizer类核心接口就三个load_data、train、predict。这样在写 GUI 时只用调predict方法在写文档时只要说明类的接口设计。类的内部还要保留一个preprocess_params字典包含threshold、k、normalize等参数方便实验的时候批量调参。我用一个 JSON 文件存最佳参数组合比如{k:3, threshold:127, normalize:true}。每次跑实验就写一行记录包括参数和准确率。这样做的好处是你在答辩时能指着记录说“我试过 20 组参数最佳组合是这三项的取值”这句话在分数上的帮助比在代码里加一百行注释都大。最后分享一个个人习惯每跑完一组实验把输出结果包括准确率、错了哪几张图、错误预测成什么存到一个 resources 目录下文件名带日期。答辩前打包项目时这些实验记录直接拿去当实验报告的素材比当场回忆数据靠谱得多。做手写数字识别这个项目学到的不只是 KNN 和图像处理——顺利把完整流程跑通写清楚每一处参数的来龙去脉这个能力会跟着你走很远。希望这篇笔记能帮你少踩几个坑把这份期末大作业做扎实。本文还有配套的精品资源点击获取
返回列表