opencv-进阶05 手写数字识别原理及示例

news2024/10/5 17:26:22

前面我们仅仅取了两个特征维度进行说明。在实际应用中,可能存在着更多特征维度需要计算。

下面以手写数字识别为例进行简单的介绍。

假设我们要让程序识别图 20-2 中上方的数字(当然,你一眼就知道是“8”,但是现在要让计算机识别出来)。识别的方式是,依次计算该数字图像(即写有数字的图像)与下方数字图像的距离,与哪个数字图像的距离最近(此时 k =1),就认为它与哪幅图像最像,从而确定这幅图像中的数字是多少。

在这里插入图片描述

下面分别从特征值提取和数字识别两方面展开介绍。

1. 特征值提取

步骤 1:我们把数字图像划分成很多小块,如图 20-3 所示。该图中每个数字被分成 5 行 4列,共计 5×4 = 20 个小块。此时,每个小块是由很多个像素点构成的。当然,也可以将每一个像素点理解为一个更小的子块。
为了叙述上的方便,将这些小块表示为 B(Bigger),将 B 内的像素点,记为 S(Smaller)。
因此,待识别的数字“8”的图像可以理解为:

  • 由 5 行 4 列,共计 5×4=20 个小块 B 构成。
  • 每个小块 B 内其实是由 M×N 个像素(更小块 S)构成的。为了描述上的方便,假设每个小块大小为 10×10 =100 个像素。

在这里插入图片描述
步骤 2:计算每个小块 B 内,有多少个黑色的像素点。或者这样说,计算每个小块 B 内有
多少个更小块 S 是黑色的。
仍以数字“8”的图像为例,其第 1 行中:

  • 第 1 个小块 B 共有 0 个像素点(更小块 S)是黑色的,记为 0。
  • 第 2 个小块 B 共有 28 个像素点(更小块 S)是黑色的,记为 28。
  • 第 3 个小块 B 共有 10 个像素点(更小块 S)是黑色的,记为 10。
  • 第 4 个小块 B 共有 0 个像素点(更小块 S)是黑色的,记为 0。

以此类推,计算出数字“8”的图像中每一个小块 B 中有多少个像素点是黑色的,如图 20-4 所示。我们观察后会发现,不同的数字图像中每个小块 B 内黑色像素点的数量是不一样的。正是这种不同,使我们能用该数量(每个小块 B 内黑色像素点的个数)作为特征来表示每一个数字。

在这里插入图片描述
步骤 3:有时,为了处理上的方便,我们会把得到的特征值排成一行(写为数组形式),如图 20-5 所示。

在这里插入图片描述
当然,在 Python 里完全没有必要这样做,因为 Python 可以非常方便地直接处理图 20-5 中上方数组(array)形式的数据。这里为了说明上的方便,仍将其特征值处理为一行数字的形式。

经过上述处理,数字“8”图像的特征值变为一行数字,如图 20-6 所示。

在这里插入图片描述
步骤 4:与数字“8”的图像类似,每个数字图像的特征值都可以用一行数字来表示。从某种意义上来说,这一行数字类似于我们的身份证号码,一般来说,具有唯一性。

按照同样的方式,获取每个数字图像的特征值,如图 20-7 所示。

在这里插入图片描述

2. 数字识别

数字识别要做的就是比较待识别图像与图像集中的哪个图像最近。这里,最近指的是二者之间的欧氏距离最短。

本例中为了便于说明和理解进行了简化,将原来下方的 10 个数字减少为 2 个(也即将分类从 10 个减少为 2 个)。

假设要识别的图像为图 20-8 中上方的数字“8”图像,需要判断该图像到底属于图 20-8 中下方的数字“8” 图像的分类还是数字“7”图像的分类。

在这里插入图片描述
步骤 1:提取特征值,分别提取待识别图像的特征值和特征图像的特征值。

为了说明和理解上的方便,将特征进行简化,每个数字图像只提取 4 个特征值(划分为 2×2 = 4 个子块 B),如图 20-9 所示。此时,提取到的特征值分别为:

  • 待识别的数字“8”图像:[3, 7, 8, 13]
  • 数字“8”特征图像:[3, 6, 9, 12]
  • 数字“7”特征图像:[8, 1, 2, 98]

在这里插入图片描述
步骤 2:计算距离。按照 20.1 节介绍的欧氏距离计算方法,计算待识别图像与特征图像之间的距离。

首先,计算待识别的数字“8”图像与下方的数字“8”特征图像之间的距离,如图 20-10所示。计算二者之间的距离:

在这里插入图片描述
接下来,计算待识别的数字“8”图像与数字“7”特征图像之间的距离,如图 20-11 所示。二者之间的距离为:

在这里插入图片描述
通过计算可知,待识别的数字“8”图像:

  • 与数字“8”特征图像的距离为根号3=1.732050807568877。
  • 与数字“7”特征图像的距离为根号7322=85.56868586112562。

步骤 3:识别。
根据计算的距离,待识别的数字“8”图像与数字“8”特征图像的距离更近。所以,将待识别的数字“8”图像识别为数字“8”特征图像所代表的数字“8”。

上面介绍的是 K 近邻算法只考虑最近的一个邻居的情况,相当于 K 近邻中 k =1 的情况。在实际操作中,为了提高可靠性,需要选用大量的特征值。例如,每个数字都选用不同的形态的手写体 100 个,对于 0 ~ 9 这 10
个数字,共需要 100×10 =1000 幅特征图像。在识别数字时, 分别计算待识别的数字图像与这些特征图像之间的距离。这时,可以将 k
调整为稍大的值,例如 k =11,然后看看其最近的 11 个邻居分属于哪些特征图像。

例如,其中:

  • 有 8 个属于数字“6”特征图像。
  • 有 2 个属于数字“8”特征图像。
  • 有 1 个属于数字“9”特征图像。
    通过判断,当前待识别的数字为数字“6”特征图像所代表的数字“6”。

自定义函数手写数字识别

在本例中,0~9 的每个数字都有 10 个特征值。例如,数字“0”的特征值如图 20-12 所示。
为了便于描述,将所有这些用于判断分类的图像称为特征图像。

在这里插入图片描述
下面分步骤实现手写数字的识别。

1. 数据初始化
对程序中要用到的数据进行初始化。涉及的数据主要有路径信息、图像大小、特征值数量、用来存储所有特征值的数据等。

本例中:

  • 特征图像存储在当前路径的“image_number”文件夹下。
  • 用于判断分类的特征值有 100 个(对应 100 幅特征图像)。
  • 特征图像的行数(高度)、列数(宽度)可以通过程序读取。也可以在图像上单击鼠标右键后通过查找属性值来获取。这里采用设置好的特征图像集,每个特征图像都是高240 行、宽 240 列。

根据上述已知条件,对要用到的数据初始化:

s='image_number\\' # 图像所在的路径
num=100 # 共有特征值的数量
row=240 # 特征图像的行数
col=240 # 特征图像的列数
a=np.zeros((num,row,col)) # a 用来存储所有特征的值

2. 读取特征图像
本步骤将所有的特征图像读入到 a 中。共有 10 个数字,每个数字有 10 个特征图像,采用嵌套循环语句完成读取。具体代码如下:

n=0 # n 用来存储当前图像的编号。
for i in range(0,10):
 for j in range(1,11):
 a[n,:,:]=cv2.imread(s+str(i)+'\\'+str(i)+'-'+str(j)+'.bmp',0)
 n=n+1

3. 提取特征图像的特征值
在提取特征值时,可以计算每个子块内黑色像素点的个数,也可以计算每个子块内白色像素点的个数。这里我们选择计算白色像素点(像素值为 255)的个数。按照上述思路,图像映射到特征值的关系如图 20-13 所示。

在这里插入图片描述
这里需要注意,特征值的行和列的大小都是原图像的 1/5。所以,在设计程序时,如果原始图像内位于(row, col)位置的像素点是白色,则要把对应特征值内位于(row/5, col/5)处的值加 1。

根据上述分析,编写代码如下:

feature=np.zeros((num,round(row/5),round(col/5))) # feature 存储所有样本的特征值
#print(feature.shape) # 在必要时查看 feature 的形状是什么样子
#print(row) # 在必要时查看 row 的值,有多少个特征值(100 个)
for ni in range(0,num):
 for nr in range(0,row):
 for nc in range(0,col):
 if a[ni,nr,nc]==255:
 feature[ni,int(nr/5),int(nc/5)]+=1
f=feature #简化变量名称

4. 计算待识别图像的特征值

读取待识别图像,然后计算该图像的特征值。编写代码如下:

o=cv2.imread('image\\test\\9.bmp',0) # 读取待识别图像
# 读取图像的值
of=np.zeros((round(row/5),round(col/5))) # 用来存储待识别图像的特征值
for nr in range(0,row):
 for nc in range(0,col):
 if o[nr,nc]==255:
 of[int(nr/5),int(nc/5)]+=1

5. 计算待识别图像与特征图像之间的距离

依次计算待识别图像与特征图像之间的距离。编写代码如下:

d=np.zeros(100)
for i in range(0,100):
d[i]=np.sum((of-f[i,:,:])*(of-f[i,:,:]))

数组 d 通过依次计算待识别图像特征值 of 与数据集 f 中各个特征值的欧氏距离得到。数据集 f 中依次存储的是数字 0~9 的共计 100 个特征图像的特征值。所以,数组 d 中的索引号对应着各特征图像的编号。例如,d[mn]表示待识别图像与数字“m”的第 n 个特征图像的距离。数组 d 的索引与特征图像之间的对应关系如表 20-2 所示。

在这里插入图片描述
如果将索引号整除 10,得到的值正好是其对应的特征图像上的数字。例如 d[34]对应着待识别图像到数字“3”的第 4 个特征图像的欧式距离。而将 34 整除 10,得到 int(34/10) = 3,正好是其对应的特征图像上的数字。
确定了索引与特征图像的关系,下一步可以通过计算索引达到数字识别的目的。

6. 获取k个最短距离及其索引

从计算得到的所有距离中,选取 k 个最短距离,并计算出这 k 个最短距离对应的索引。具体实现方式是:

  • 每次找出最短的距离(最小值)及其索引(下标),然后将该最小值替换为最大值。
  • 重复上述过程 k 次,得到 k 个最短距离对应的索引。
    每次将最小值替换为最大值,是为了确保该最小值在下一次查找最小值的过程中不会再次被找到。

例如,要在数字序列“11, 6, 3, 9”内依次找到从小到大的值。

  • 第 1 次找到了最小值“3”,同时将“3”替换为“11”。此时,要查找的序列变为“11, 6,11, 9”。
  • 第 2 次查找最小值时,在序列“11, 6, 11, 9”内找到的最小值是数字“6”,同时将“6”替换为最大值“11”,得到序列“11,11,11,9”。

不断地重复上述过程,依次在第 3 次找到最小值“9”,在第 4 次找到最小值“11”。当然,
在本例中查找的是数值,具体实现时查找的是索引值。
根据上述思路,编写代码如下:

d=d.tolist()
temp=[]
Inf = max(d)
#print(Inf)
k=7
for i in range(k):
 temp.append(d.index(min(d)))
 d[d.index(min(d))]=Inf

7. 识别
根据计算出来的 k 个最小值的索引,结合表 20-2 就可以确定索引所对应的数字。
具体实现方法是将索引值整除 10,得到对应的数字。

例如,在 k =11 时,得到最小的 11 个值所对应的索引依次为:66、60、65、63、68、69、67、78、89、96、32。它们所对应的特征图像如表 20-3 所示。

在这里插入图片描述
这说明,当前待识别图像与数字“6”的第 6 个特征图像距离最近;接下来,距离最近的第 2 个特征图像是数字“6”的第 0 个特征图像(序号从 0 开始);

以此类推,距离最近的第 11个特征图像是数字“3”的第 2 个特征图像。

上述结果说明,与待识别图像距离最近的特征图像中,有 7 个是数字“6”的特征图像。所以,待识别图像是数字“6”。

下面讨论如何通过程序识别数字。已知将索引整除 10,就能得到对应特征图像上的数字,因此对于上述索引整除 10:

(66, 60, 65, 63, 68, 69, 67, 78, 89, 96, 32)整除 10 = (6, 6, 6, 6, 6, 6,
6, 7, 8, 9, 3)

为了叙述上的方便,将上述整除结果标记为 dr,在 dr 中出现次数最多的数字,就是识别结果。对于上例,dr 中“6”的个数最多,所以识别结果就是数字“6”。

这里我们借助索引判断一组数字中哪个数字出现的次数最多:

  • 建立一个数组 r,让其元素的初始值都是 0。
  • 依次从 dr 中取数字 n,将数组 r 索引位置为 n 的值加 1。

例如,从 dr 中取到的第 1 个数字为“6”,将 r[6]加上 1;从 dr 中取到第 2 个数字也为“6”,将 r[6]加上 1;以此类推,对于 dr=[6, 6, 6, 6, 6, 6, 6, 7, 8, 9, 3],得到数组 r 的值为[0, 0, 0, 1, 0, 0, 7, 1, 1, 1]。
在数组 r 中:

  • r[0]=0,表示在 dr 中不存在值为 0 的元素。
  • r[3]=1,表示在 dr 中有 1 个“3”。
  • r[6]=7,表示在 dr 中有 7 个“6”。
  • r[7]=1,表示在 dr 中有 1 个“7”。

根据上述思路,编写代码如下:

temp=[i/10 for i in temp]
# 数组 r 用来存储结果,r[0]表示 K 近邻中“0”的个数,r[n]表示 K 近邻中“n”的个数
r=np.zeros(10)
for i in temp:
 r[int(i)]+=1
print('当前的数字可能为:'+str(np.argmax(r)))

上述过程是分步骤的分析结果,以下是全部源代码:

import time

import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取样本(特征)图像的值

start_time = time.time();

s='image_number\\' # 图像所在路径
num=100 # 样本总数
row=240 # 特征图像的行数
col=240 # 特征图像的列数
a=np.zeros((num,row,col)) # 存储所有样本的数值
#print(a.shape)
n=0 # 存储当前图像的编号
for i in range(0,10):
 for j in range(1,11):
    a[n,:,:]=cv2.imread(s+str(i)+'\\'+str(i)+'-'+str(j)+'.bmp',0)
    n=n+1
#提采样本图像的特征
feature=np.zeros((num,round(row/5),round(col/5))) # 用来存储所有样本的特征值
#print(feature.shape) # 看看特征值的形状是什么样子
#print(row) # 看看 row 的值,有多少个特征值(100
for ni in range(0,num):
 for nr in range(0,row):
    for nc in range(0,col):
        if a[ni,nr,nc]==255:
            feature[ni,int(nr/5),int(nc/5)]+=1
f=feature # 简化变量名称
#####计算当前待识别图像的特征值
o=cv2.imread('image_number\\test\\5.bmp',0) # 读取待识别图像

##读取图像值
of=np.zeros((round(row/5),round(col/5))) # 存储待识别图像的特征值
for nr in range(0,row):
 for nc in range(0,col):
    if o[nr,nc]==255:
        of[int(nr/5),int(nc/5)]+=1

##计算待识别图像与样本图像的距离
d=np.zeros((num,1)) # 存储待识别图像与样本图像的距离

for i in range(0,100):
 d[i]=np.sum((of-f[i,:,:])*(of-f[i,:,:]))
#print(d)
d=d.tolist()
temp=[]
Inf = max(d)
#print(Inf)
k=7
for i in range(k):
 temp.append(d.index(min(d)))
 d[d.index(min(d))]=Inf
#print(temp) #看看都被识别为哪些特征值
temp=[i/10 for i in temp]
# 也可以返回去处理为 array,使用函数处理
#temp=np.array(temp)
#temp=np.trunc(temp/10)
#print(temp)
# 数组 r 用来存储结果,r[0]表示 K 近邻中“0”的个数,r[n]表示 K 近邻中“n”的个数
r=np.zeros(10)
for i in temp:
 r[int(i)]+=1
#print(r)
print('当前的数字可能为:'+str(np.argmax(r)))
print('识别所用时间为:'+str(time.time()-start_time)+'秒')

运行结果:

当前的数字可能为:5
识别所用时间为:4.173201560974121

测试图片下载地址 点击下载

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

lvs负载均衡集群(NAT模式)

lvs负载均衡集群: 1.什么是集群(含义):就是将多台主机作为一个整体,对外提供相同的服务 2.集群使用在哪一个场景:高并发 并发量过大时候加服务器的方式就是向外扩展(横向扩展),就是集群。 3…

HoudiniVex笔记_P24_ForceBasics力基础

原视频:https://www.youtube.com/playlist?listPLzRzqTjuGIDhiXsP0hN3qBxAZ6lkVfGDI Bili:Houdini最强VEX算法教程 - VEX for Algorithmic Design_哔哩哔哩_bilibili Houdini版本:19.5 1、什么是Force 本章主要讲重力、弹力、速度与质量、…

强训第33天

选择 C A ping是TCP/IP协议族的一部分,使用ICMP协议,ICMP底层使用IP协议。如果要ping其他网段,则需要设置网关。 如果是二层交换机故障,则ping同网段的也会不通。 C Dos攻击被称之为“拒绝服务攻击”,其目的是使计算机…

网页设计详解(一)-HTML简介

本文作为博主学习笔记:2023-05-04星期四 一、网页介绍 网页是构成网站的基本元素,它是一个包含HTML标签的纯文本文件,是超文本标记语言格式(文件扩展名为.html或.htm)。网页通常用图像档来提供图画,通过浏览器来阅读。 超文本介…

C#__Action和Func委托的基本用法

// 类 class DelegateC{// Action,内置的委托类型,引用了一个void返回值类型的方法,T表示方法参数public static void AText1(){Console.WriteLine("Atext1");}public static void AText2(int x){Console.WriteLine("Atext2&…

【LINUX相关】生成随机数(srand、/dev/random 和 /dev/urandom )

目录 一、问题背景二、修改方法2.1 修改种子2.2 使用linux中的 /dev/urandom 生成随机数 三、/dev/random 和 /dev/urandom 的原理3.1 参考连接3.2 重难点总结3.2.1 生成随机数的原理3.2.2 随机数生成器的结构3.2.3 二者的区别和选择 四、在代码的使用方法 一、问题背景 在一个…

2023一建各科考点归纳

一级建造师《建设工程项目管理》高频考点 1Z201000建设工程项目的组织与管理 1Z201010建设工程管理的内涵和任务 系统的目标决定了系统的组织,而组织是目标能否实现的决定性因素。 建设工程管理涉及工程项目全过程,包括: 决策阶段-开发管理: 实施阶…

《零基础7天入门Arduino物联网-01》学前必看

配套视频课程:《零基础学Arduino物联网,入门到进阶》 配套课件资料获取:微联实验室 配套学习套件购买:淘宝搜索店铺【微联实验室】 学前须知 注意事项 本系列课程主要针对零基础的物联网爱好者群体开发,非系统性讲解…

Java 中操作 Redis

文章目录 一、Redis 常用数据类型二、Redis 常用操作命令1. 字符串命令2. 哈希命令3. 列表命令4. 集合命令5. 有序集合命令6. 通用命令 三、在 Java 中操作 Redis1. 导入 maven 坐标2. 配置 Redis 数据源3. 编写配置类 四、在代码中的具体使用 一、Redis 常用数据类型 Redis 存…

6.4 恶意代码

数据参考:CISP官方 目录 恶意代码概念及发展历程恶意代码的传播方式恶意代码防护 一、恶意代码概念及发展历程 1、什么是恶意代码 什么是恶意代码 《中华人民共和国计算机信息系统安全保护条例》第二十八条:“计算机病毒,是指编制或者…

VMware虚拟机下载与安装

VMware虚拟机下载与安装 目录 VMware虚拟机下载与安装[TOC](目录) 概述 1. 下载虚拟机1.1 访问官网1.2 点击产品菜单,然后选择产品 VMware Workstation Pro1.3 选择试用版下载 2.安装虚拟机2.1 右键点击安装包,选择以管理员身份运行2.2 按照说明进行安装…

玩转VS code 之 C/C++ 环境配置篇

PS:俺是菜鸟,整理和踩坑试错花了不少时间,如果这篇文章对您有用的话,请麻烦您留下免费的赞赞,赠人玫瑰,手留余香,码字踩坑不易,望三连支持 上一篇:玩转 VS code 之下载篇…

数字人如何赋能汽车品牌营销线下实时交互?

为了进一步巩固和拓展消费群体,扩大品牌影响力,别克汽车在线下开展了新品体验日活动,在品鉴会现场数字人影萱作为神秘嘉宾惊喜亮相,与现场嘉宾和众多媒体同屏实时互动,这虚拟与现实的碰撞互动形式,让现场嘉…

复合 类型

字符串和切片 切片 切片的作用是允许你引用集合中部分连续的元素序列,而不是引用整个集合。 例如: let s String::from("hello world");let hello &s[0..5]; // 切片 [0,5) 等效于&s[..5] let world &s[6..11]; // 切片…

Puppeteer vs Selenium的主要区别

Puppeteer vs. Selenium:他们来自哪里? Google Puppeteer是一个 Node.js 库和浏览器测试框架。该技术旨在提供高级应用程序编程接口,以通过 DevTools 协议控制无头 Chrome。Selenium 支持多种浏览器和语言,而 Puppeteer 只专注于 …

Leetcode-每日一题【剑指 Offer 32 - III. 从上到下打印二叉树 III】

题目 请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 20…

清风数学建模——拟合算法

拟合算法 文章目录 拟合算法概念 确定拟合曲线最小二乘法的几何解释求解最小二乘法matlab求解最小二乘法如何评价拟合的好坏计算拟合优度的代码 概念 在前面的篇幅中提到可以使用插值算法,通过给定的样本点推算出一定的曲线从而推算出一些想要的值。但存在一些问题…

设计模式之原型模式详解

前言 在设计模式的系列文章中,我们前面已经写了工厂模式、单列模式、建造者模式,在针对创建型模式中,今天想跟大家分享的是原型模式,我觉的这种模式叫克隆模式会更佳恰当。原型模式的目的就是通过复制一个现有的对象来生成一个新…

vite4+vue3+electron23.3+ts桌面应用bs端开发 打包windows、linux、max三个系统的安装包

vite4vue3electron23.3ts桌面应用bs端开发 打包windows、linux、max三个系统的安装包 主要包依赖 "electron-store": "^8.1.0", //全局数据状态管理,可选择性安装"electron": "23.3.8","electron-builder": &q…

驱动控制LED灯

编写驱动代码,初步实现串口输入逻辑控制开发板的LED灯的亮灭 代码示例 head.h #ifndef __HEAD_H__ #define __HEAD_H__typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPD;unsigned int IDR;unsigned int ODR; }gp…