基于OpenCV的KNN手写数字识别全解析

一、环境准备与数据加载

1.1 库导入

import numpy as np
import cv2
  • numpy:用于高效处理多维数组运算
  • cv2:OpenCV计算机视觉库,提供图像处理和机器学习功能

1.2 数据加载与预处理

img = cv2.imread('digits.png')  # 加载包含手写数字的图像
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转换为灰度图像
  • digits.png 是经典的手写数字数据集图像,尺寸为2000x1000像素
  • 转换为灰度图的必要性:
    • 减少计算复杂度(3通道→1通道)
    • 保留足够的特征信息
    • 符合数字识别任务的需求

基于OpenCV的KNN手写数字识别全解析_第1张图片

二、数据预处理详解

2.1 数字分割

cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
  • vsplit:垂直切分图像为50行,每行高度=2000/50=40像素
  • hsplit:水平切分每行为100列,每列宽度=1000/100=10像素
  • 最终得到50x100的单元格矩阵,每个单元格为20x20像素(实际计算:40x10=400像素,需要确认原图尺寸)

2.2 数据重构

x = np.array(cells)  # 转换为NumPy数组,形状(50,100,20,20)
  • 维度解析
    • 第1维:50行数字(0-9每个数字5行)
    • 第2维:100列数字(每个数字10列)
    • 第3/4维:20x20像素的单个数字图像

2.3 数据集划分

train = x[:, :50]    # 前50列作为训练集
test = x[:, 50:100]  # 后50列作为测试集
  • 划分策略
    • 训练集和测试集各包含2500个样本(50行x50列)
    • 保证每个数字在训练/测试集中都有250个样本(0-9各250个)

2.4 数据格式转换

train_new = train.reshape(-1,400).astype(np.float32)  # (2500,400)
test_new = test.reshape(-1,400).astype(np.float32)    # (2500,400)
  • reshape操作:将20x20的二维图像展平为1x400的一维向量
  • astype转换:转换为32位浮点数,满足OpenCV的数据类型要求
  • 数学意义:将图像空间转换为400维特征空间

基于OpenCV的KNN手写数字识别全解析_第2张图片

三、标签系统构建

3.1 标签生成原理

k = np.arange(10)            # 创建基础标签数组[0-9]
labels = np.repeat(k, 250)   # 每个数字重复250次
  • np.repeat工作机制
    输入:[0,1,2,...,9]
    输出:[0,0,...,0,1,1,...,1,...,9,9,...,9]
            250次   250次     250次
    
  • 总标签数量:10个数字×250次=2500个,与训练/测试样本数一致

3.2 标签维度调整

train_labels = labels[:, np.newaxis]  # 形状(2500,1)
test_labels = np.repeat(k,250)[:, np.newaxis]
  • np.newaxis作用:增加一个新维度,将一维数组变为二维列向量
  • OpenCV要求标签为二维数组格式(样本数×1)

四、KNN模型构建与训练

4.1 模型初始化

knn = cv2.ml.KNearest_create()  # 创建KNN分类器
  • OpenCV的机器学习模块位于cv2.ml命名空间
  • 创建时会自动初始化默认参数(k=1,权重均匀)

4.2 模型训练

knn.train(train_new, cv2.ml.ROW_SAMPLE, train_labels)
  • 参数解析

    • train_new:训练数据矩阵(2500×400)
    • cv2.ml.ROW_SAMPLE:指示数据按行组织(每行一个样本)
    • train_labels:对应的标签数据(2500×1)
  • 底层实现

    • 自动构建KD-Tree索引结构
    • 存储所有训练样本和标签
    • 准备快速近邻搜索的数据结构

五、模型预测与评估

5.1 预测执行

ret, result, neighbours, dist = knn.findNearest(test_new, k=3)
  • 参数说明

    • test_new:测试数据集(2500×400)
    • k=3:选择3个最近邻进行投票
  • 返回值解析

    • ret:操作状态标志(True/False)
    • result:预测结果数组(2500×1)
    • neighbours:最近邻索引矩阵(2500×3)
    • dist:对应距离矩阵(2500×3)

5.2 准确率计算

matches = result == test_labels
correct = np.count_nonzero(matches)
accuracy = correct * 100.0 / result.size
  • 计算过程

    1. 生成布尔掩码数组(预测正确的位置为True)
    2. 统计True的数量(np.count_nonzero计算非零元素)
    3. 计算正确率百分比
  • 典型输出

    当前使用KNN识别手写数字的准确率为93.6%
    

六、关键问题分析与优化建议

6.1 参数调优方向

  1. k值优化

    for k in range(1,10):
        ret, result, _, _ = knn.findNearest(test_new, k=k)
        # 计算并比较不同k值的准确率
    
  2. 距离权重优化

    # OpenCV不支持直接设置权重,可改用sklearn实现
    from sklearn.neighbors import KNeighborsClassifier
    knn_sk = KNeighborsClassifier(weights='distance')
    

6.2 算法改进建议

  1. 特征工程

    • 提取HOG(方向梯度直方图)特征代替原始像素
    • 添加数字的宽高比、重心位置等几何特征
  2. 数据增强

    # 示例:添加随机旋转(±15度)
    angle = np.random.uniform(-15,15)
    M = cv2.getRotationMatrix2D((10,10), angle, 1)
    digit_rot = cv2.warpAffine(digit, M, (20,20))
    

七、完整代码

import numpy as np
import cv2 #3.1本身就自带了机器学的函数  sklearn

img = cv2.imread('digits.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)#转换为灰度图
# 将原始图像划分成独立的数字,每个数字大小20*20,共计5000个
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]#按照顺序来看代码,python列表
# 装进array,形状(50,100,20,20),50行,100列,每个图像20*20大小
x = np.array(cells)

train = x[ : ,  :50]# 划分为训练集和测试集:比例各占一半
test = x[:,50:100]

# 将数据构造为符合KNN的输入,将每个数字的尺寸由20*20调整为1*400(一行400个像素)
train_new =train.reshape(-1,400).astype(np.float32) # Size = (2500,400)
test_new = test.reshape(-1,400).astype(np.float32) # Size = (2500,400)

# 分配标签:分别为训练数据、测试数据分配标签(图像对应的实际值)
k = np.arange(10)#(0123456789)
labels = np.repeat(k,250)#repeat重复数组中的元素,每个元素重复250次
train_labels = labels[:,np.newaxis]#np.newaxis是NumPy库中的一个特殊对象,用于在数组中增加一个新的维度。
test_labels = np.repeat(k,250)[:,np.newaxis]

# 模型构建+训练,sklearn  knn,,opencv里面也有knn
knn = cv2.ml.KNearest_create()#通过cv2创建一个knn模型
knn.train(train_new, cv2.ml.ROW_SAMPLE, train_labels)#cv2.ml.ROW_SAMPLE: 这是一个标志,告诉OpenCV训练数据是按行组织的,即每一行是一个样本。
ret,result,neighbours,dist = knn.findNearest(test_new,k=3)
# ret: 表示查找操作是否成功。
# result: 浮点数数组,表示测试样本的预测标签。
# neighbours: 这是一个整数数组,表示与测试样本最近的k个邻居的索引。这些索引对应于训练集中的样本,可以用来检查哪些训练样本对预测结果产生了影响。
# dist: 这是一个浮点数数组,表示测试样本与每个最近邻居之间的距离。这些距离可以帮助理解预测结果的置信度;距离越近,预测通常越可靠。

# 通过测试集校验准确率
matches = result==test_labels
correct = np.count_nonzero(matches)#
accuracy = correct*100.0/result.size
print( "当前使用KNN识别手写数字的准确率为{}%:".format(accuracy))
#1、自己创建一个测试集来测试正确率
#2、改成sklearn库来实现

八、延伸思考

  1. 实时识别扩展

    # 摄像头实时识别
    cap = cv2.VideoCapture(0)
    while True:
        ret, frame = cap.read()
        # 添加数字区域检测和识别代码
        cv2.imshow('Digit Recognition', frame)
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break
    
  2. 模型部署优化

    • 使用FLASK搭建API服务
    • 转换为ONNX格式加速推理
    • 实现Web端手写画板交互

通过本案例,我们不仅实现了基础的手写数字识别,还探讨了计算机视觉与机器学习结合的典型工作流程。KNN算法虽简单,但配合适当的数据处理和参数调优,仍能在特定场景下发挥出色效果。

你可能感兴趣的:(机器学习,opencv,人工智能,计算机视觉)