用KNN实现手写数字识别:基于 OpenCV 和 scikit-learn 的实战教学

在这篇文章中,我们将使用 KNN(K-Nearest Neighbors)算法对手写数字进行分类识别。我们会用 OpenCV 读取图像并预处理数据,用 scikit-learn 构建并训练模型,最终识别新的数字图像。

为什么像素可以被代码读取为数据:

图像的本质:像素的数字矩阵

任何数字图像(如照片、截图、手写数字图片)都是由无数个微小的 “像素点”(Pixel)组成的

  • 每个像素点的数值含义

    • 对于灰度图(如代码中的手写数字),每个像素用一个 0-255 的整数表示亮度:0 代表纯黑,255 代表纯白,中间值表示不同深浅的灰色。
    • 对于彩色图(如 RGB 格式),每个像素由三个数值(R、G、B)组成,分别对应红、绿、蓝三种颜色的亮度,组合后呈现出各种颜色。

(可以在调试时看一看代码里的‘gray’参数里面 单个数字图像的矩阵)

此20×20 像素的数字图像就是一个数字矩阵显示出的一个大大的 0

使用的数据集

我们使用的是一个包含 5000 个手写数字(0-9) 的图像文件(digits5000.png),每种数字500个,总共10类。图像被排布成了一个 50 行 × 100 列 的网格,每个小格是一个 20×20 像素的数字图像

数据图像:

保存下面代码所需的三张图片:

 

上面的 ‘3’,‘6’ 图片可在‘开始’里的‘画图’中,可以创建我们想要自定义的数字图片:


然后使用画笔写一个数字后(笔粗一点):

把比例调到   20×20 像素

即可得到。

 完整代码:

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import cv2# 读取包含5000个手写数字的大图,每个数字为20x20像素
img = cv2.imread('digits5000.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # 转换为灰度图# 将大图分割为50行100列的小单元格,每个单元格包含一个手写数字
cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]# 将单元格列表转换为四维数组: (50行, 100列, 20像素高, 20像素宽)
x = np.array(cells)# 准备训练集和测试集数据
# 前50列作为训练集,后50列作为测试集
# 数据重塑为: (样本数, 特征数),每个样本包含400个像素值
train = x[:,:50].reshape(-1,400)
test = x[:,50:].reshape(-1,400)# 创建标签数据
n = np.arange(10)  # 创建数字0-9的数组
# 每个数字对应250个样本(50行×5列),生成训练标签
tags = np.repeat(n,250)
tag = tags[:,np.newaxis]  # 转换为二维数组,形状为(2500, 1)# 创建并训练KNN分类器,使用k=5最近邻
knn = KNeighborsClassifier(n_neighbors=5)    #给初学者的建议:在此处设置断点 或 直接在import处设置断点,开启调试一行一行地运行,更好的看到每一个参数的变化
knn.fit(train, tag)# 评估模型在训练集上的准确率
predictions_train = knn.predict(train)    #此行结果没有运行,建议在开启调试,可看到
accuracy_train = knn.score(train, tag)
print(f"训练集准确率: {accuracy_train:.4f}")# 评估模型在测试集上的准确率
predictions_test = knn.predict(test)
accuracy_test = knn.score(test, tag)
print(f"测试集准确率: {accuracy_test:.4f}")# 预测外部数字3的图像
digit3 = cv2.imread('digit3.png')
digit3gray = cv2.cvtColor(digit3, cv2.COLOR_BGR2GRAY)
digit3test = digit3gray.reshape(-1,400)  # 重塑为模型期望的输入格式
predictions_digit3 = knn.predict(digit3test)
print(f"预测数字3的结果: {predictions_digit3}")# 预测外部数字6的图像
digit6 = cv2.imread('digit6.png')
digit6gray = cv2.cvtColor(digit6, cv2.COLOR_BGR2GRAY)
digit6test = digit6gray.reshape(-1,400)  # 重塑为模型期望的输入格式
predictions_digit6 = knn.predict(digit6test)
print(f"预测数字6的结果: {predictions_digit6}")

红色断点:

调试:

点击 单步执行 或 其他   ,多尝试尝试

点击“作为...查看”即可清晰查看


所需库导入

import numpy as np
from sklearn.neighbors import KNeighborsClassifier
import cv2
  • numpy: 用于矩阵操作。

  • sklearn.neighbors.KNeighborsClassifier: 实现KNN分类器。

  • cv2: OpenCV库,用于图像读取与处理。


图像加载与预处理

img = cv2.imread('digits5000.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

  • cv2.imread() 读取图像。

  • cv2.cvtColor() 将图像从彩色(BGR)转换为灰度(GRAY),便于处理。


图像分割成小数字图块

cells = [np.hsplit(row,100) for row in np.vsplit(gray,50)]
x = np.array(cells)
  • 使用 np.vsplit() 将图像竖直切成50行(每行包含100个数字)。

  • 对每行使用 np.hsplit() 水平切分成100列,最终每个小格是一个20x20的数字图像。

  • 得到的 x 是一个形状为 (50, 100, 20, 20) 的数组。


构建训练集与测试集

train = x[:,:50].reshape(-1,400)
test = x[:,50:].reshape(-1,400)
  • 将每个 20×20 图像展开为 1×400 的一维向量。

  • 前 50 列作为训练集,后 50 列作为测试集。

  • traintest 的形状均为 (2500, 400)


构造标签

n = np.arange(10)                   #(0123456789)
tags = np.repeat(n,250)            #每个数字重复250次 -> [0,...0,1,...,1,...9,...9]
tag = tags[:,np.newaxis]           #添加新维度,变成列向量
# test_tag = np.repeat(n,250)[:np.newaxis]   
  • tags 是长度为 2500 的一维标签数组。

  • tag 是形状为 (2500, 1) 的列向量,作为训练和测试的真实标签。

为什么重复250次,因为我们把数据从中间对半切开,每个数字有500个,左二百五十为训练集,右二百五十个为测试集。

所以其实训练集和测试集的标签是一样的,标签就是每一个20x20数字图像所显示的数字。


训练模型并评估准确率

knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(train, tag)predictions_train = knn.predict(train)
accuracy_train = knn.score(train, tag)
print(accuracy_train)predictions_test = knn.predict(test)
accuracy_test = knn.score(test, tag)
print(accuracy_test)
  • 初始化一个 KNN 分类器,选择 k=5

  • 使用 .fit() 训练模型。

  • 使用 .predict().score() 对训练集和测试集进行预测和评分。

  • 打印准确率:理论上训练集精度应很高,测试集略低(但通常也超过 97%)。


识别自定义手写数字

digit3 = cv2.imread('digit3.png')   #图片中的数字是3
digit3gray = cv2.cvtColor(digit3, cv2.COLOR_BGR2GRAY)
digit3test = digit3gray.reshape(-1,400)
predictions_digit3 = knn.predict(digit3test)
print(predictions_digit3)digit6 = cv2.imread('digit6.png')   #图片中的数字是6
digit6gray = cv2.cvtColor(digit6, cv2.COLOR_BGR2GRAY)
digit6test = digit6gray.reshape(-1,400)
predictions_digit6 = knn.predict(digit6test)
print(predictions_digit6)
  • 读取两张额外图像 digit3.pngdigit6.png

  • 将其转换为灰度、再reshape成与训练数据一致的 (1, 400) 形状。

  • 使用模型预测数字类别。


总结

我们用 KNN 成功实现了手写数字的分类识别,关键步骤包括:

  1. 图像预处理和切分

  2. 标签构造与数据 reshape

  3. 使用 KNeighborsClassifier 建模

  4. 预测未知图像的数字类别

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/90964.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/90964.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/90964.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构自学Day15 -- 非比较排序--计数排序

一、计数排序(Counting Sort)计数排序是一种非比较型的排序算法,它的核心思想是:利用“元素的值”来确定它在结果数组中的位置,通过“统计每个数出现的次数”来完成排序。二、如何实现计数排序(核心步骤&am…

k8s的权限

来自博客:25-k8s集群中-RBAC用户角色资源权限_权限 资源 角色-CSDN博客 一.RBAC概述(基于角色的访问控制) 1.图解 用户: 1.user 2.serviceAccount 3.Group 用户角色 1.Role:局部资源角色 2.clusterRole:全局资源角色额 角色绑…

C++ - 仿 RabbitMQ 实现消息队列--服务端核心模块实现(三)

目录 队列数据管理 代码实现 测试代码 绑定信息(交换机-队列)管理 代码实现 测试代码 队列数据管理 当前队列数据的管理,本质上是队列描述信息的管理,描述当前服务器上有哪些队列。 定义队列描述数据类 队列名称是否持久化标志是否独占标志是否自…

51c自动驾驶~合集9

自己的原文哦~ https://blog.51cto.com/whaosoft/11627386 #端到端1 说起端到端,每个从业者可能都觉得会是下一代自动驾驶量产方案绕不开的点!特斯拉率先吹响了方案更新的号角,无论是完全端到端,还是专注于planner的模…

时间长了忘记jupyter的环境是哪个了

有这些但是忘记是哪个了jupyter kernelspec list查看内核路径,这个内核是用来告诉jupyter 去哪找内核配置的到这个路径下打开json文件查看使用的python环境从而确定是哪个conda环境为jupyter使用的python环境jupyter的工作原理:在创建conda环境后会安装j…

PYTHON从入门到实践-15数据可视化

数据可视化是数据分析中不可或缺的一环,它能够将抽象的数据转化为直观的图形,帮助我们更好地理解数据特征和发现潜在规律。本文将介绍如何使用Python中的Matplotlib和Plotly库进行数据可视化,并通过掷骰子的概率模拟案例展示可视化的实际应用…

Spring IOC 容器 **默认注册 Bean** 的 8 条规则

Spring IOC 容器 默认注册 Bean 的 8 条规则 (Spring Framework 6.x 源码级总结)阅读提示:把下面 8 条规则背下来,再读 Spring 源码时,你会在任何一行代码里立刻知道「这个 BeanDefinition 是从哪儿来的」。1️⃣ 环境…

29.【.NET8 实战--孢子记账--从单体到微服务--转向微服务】--单体转微服务--用户配置服务

用户配置服务是孢子记账中最简单的部分。简单说,用户配置服务就是用户自定义的配置项存储服务,用于我们的APP根据用户的配置实现指定的功能。它提供了一个简单的接口,允许用户存储和检索他们的配置数据。就目前来说,用户配置只有一…

Python实现PDF按页分割:灵活拆分文档的技术指南

Python实现PDF按页分割:灵活拆分文档的技术指南 PDF文件处理是日常工作中的常见需求,特别是当我们需要将大型PDF文档拆分为多个部分时。本文将介绍如何使用Python创建一个灵活的PDF分割工具,能够根据用户指定的页数范围任意分割文档。 需求分…

「iOS」——GCD其他方法详解

GCD学习GCD其他方法dispatch_semaphore (信号量)**什么是信号量**dispatch_semaphore主要作用dispatch_semaphore主要作用异步转同步设置一个最大开辟的线程数加锁机制dispatch_time_t 两种形式GCD一次性代码(只执行一次)dispatch_barrier_async/sync栅栏…

【图像处理基石】如何实现一个车辆检测算法?

基于AI的车牌检测和识别算法 问题描述、应用场景与难点 问题描述 车牌检测和识别是计算机视觉领域的一个特定任务,主要包含两个核心步骤: 车牌检测:从图像中准确定位车牌的位置和区域车牌识别:对检测到的车牌区域进行字符识别&…

计算机学报 2025年 区块链论文 录用汇总 附pdf下载

计算机学报 Year:2025 2024请看 1 Title: 基于区块链的动态多云多副本数据完整性审计方法研究 Authors: Key words: 区块链;云存储;多云多副本存储;数据完整性审计 Abstract: 随着云计算技术的快速发展和云存储服务的日益…

计算机网络-UDP协议

UDP(用户数据报协议)是传输层的一种无连接、不可靠、轻量级的协议,适用于对实时性要求高、能容忍少量数据丢失的场景(如视频流、DNS查询等)。以下是UDP的详细解析:1. UDP的核心特点特性说明无连接通信前无需…

子域名收集和c段查询

子域名收集方法一、sitesite: 要查询的域名可以查到相关网站二、oneforall (子域名查找工具)下载后解压的文件夹在当前文件夹打开终端然后运行命令 python oneforall.py --target xxxxxxxx(这里放你要查的网址) run最…

计网-TCP拥塞控制

TCP的拥塞控制(Congestion Control)是核心机制之一,用于动态调整发送方的数据传输速率,避免网络因过载而出现性能急剧下降(如丢包、延迟激增)。其核心思想是探测网络可用带宽,并在拥塞发生时主动…

依赖倒置原则 Dependency Inversion Principle - DIP

基本知识 1.依赖倒置原则(DIP)是面向对象设计(OOD)中的五个基本原则之一,通常被称为 SOLID 原则中的 D 2.核心思想: 高层模块不应该依赖低层模块,两者都应该依赖抽象。 (High-level modules sho…

原生input添加删除图标类似vue里面移入显示删除[jquery]

<input type"text" id"servicer-search" class"form-control" autocomplete"off" />上面是刚开始的input <div class"servicer-search-box"><input type"text" id"servicer-search" cla…

整理分享 | Photoshop 2025 (v26.5) 安装记录

导语&#xff1a; 最近整理资源时&#xff0c;发现有朋友在找新版 Photoshop。正好手边有 Photoshop 2025年7月的版本&#xff08;v26.5&#xff09;&#xff0c;就记录下来分享给大家&#xff0c;供有需要的朋友参考。关于这个版本&#xff1a;这个 Photoshop v26.5 安装包&am…

【Redis】Redis 数据存储原理和结构

一、Redis 存储结构 1.1 KV结构 Redis 本质上是一个 Key-Value&#xff08;键值对&#xff0c;KV&#xff09;数据库&#xff0c;在它丰富多样的数据结构底层&#xff0c;都基于一种统一的键值对存储结构来进行数据的管理和操作 Redis 使用一个全局的哈希表来管理所有的键值对…

【RAG优化】深度剖析OCR错误,从根源修复RAG应用的识别问题

1. 引言:OCR——RAG系统中的关键问题 当我们将一个包含扫描页面的PDF或一张报告截图扔给RAG系统时,我们期望它能“读懂”里面的内容。这个“读懂”的第一步,就是OCR。然而,OCR过程并非100%准确,它受到图像质量、文字布局、字体、语言等多种因素的影响。 一个看似微不足道…