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

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

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

假设我们要让程序识别图 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.mfbz.cn/a/77042.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SharkTeam:Worldcoin运营数据及业务安全分析

Worldcoin的白皮书中声明,Worldcoin旨在构建一个连接全球人类的新型数字经济系统,由OpenAI创始人Sam Altman于2020年发起。通过区块链技术在Web3世界中实现更加公平、开放和包容的经济体系,并将所有权赋予每个人。并且希望让全世界每一个人都…

【iMessage频發软件苹果群发技术开源原创】当 APNs 发送通知到一个离线设备时,APNs 会把通知存储起来(一定的时间内),当设备上线时再递送给设备。

推荐内容IMESSGAE相关 作者✈️IMEAE推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAE推荐内容3.日历推 *** …

ATTCK覆盖度97.1%!360终端安全管理系统获赛可达认证

近日,国际知名第三方网络安全检测服务机构——赛可达实验室(SKD Labs)发布最新测试报告,360终端安全管理系统以ATT&CK V12框架攻击技术覆盖面377个、覆盖度97.1%,勒索病毒、挖矿病毒检出率100%,误报率0…

数据分析 | 随机森林如何确定参数空间的搜索范围

1. 随机森林超参数 极其重要的三个超参数是必须要调整的,一般再加上两到三个其他超参数进行优化即可。 2. 学习曲线确定n_estimators搜索范围 首先导入必要的库,使用sklearn自带的房价预测数据集: import numpy as np import pandas as pd f…

Java数字化智慧工地管理云平台源码(人工智能、物联网、大数据)

智慧工地优势:"智慧工地”将施工企业现场视频管理、建筑起重机械安全监控、现场从业人员管理、物料管理、进度管理、扬尘噪声监测等现场设备有机、高效、科学、规范的结合起来真正实现工程项目业务流与现场各类监控源数据流的有效结合与深度配合,实…

css3-grid:grid 布局 / 基础使用

一、理解 grid 二、理解 css grid 布局 CSS Grid布局是一个二维的布局系统,它允许我们通过定义网格和网格中每个元素的位置和尺寸来进行页面布局。CSS Grid是一个非常强大的布局系统,它不仅可以用于构建网格布局,还可以用于定位元素&#xf…

解锁编程的新契机:深入探讨Kotlin Symbol Processor (KSP)的编写

解锁编程的新契机:深入探讨Kotlin Symbol Processor (KSP)的编写 1. 引言 随着软件开发领域的不断发展,新的工具和技术不断涌现,以满足开发者在构建高效、可维护和创新性的代码方面的需求。Kotlin Symbol Processor(KSP&#xf…

Actuator微服务信息完善-Eureka—SpringCloud(版)微服务学习教程(11)

一、Actuator是什么? Actuator是Springboot提供的用来对应用系统进行自省和监控的功能模块,借助于Actuator开发者可以很方便地对应用系统某些监控指标进行查看、统计等。 在Springboot中使用Actuator监控非常简单,只需要在工程POM文件中引入…

VMware虚拟安装Ubuntu,然后切换Ubuntu内核版本

无论你选择哪种方法,一旦进入 GRUB 引导菜单,你应该能够选择需要的内核版本并启动系统。 打开终端:你可以通过按下 Ctrl Alt T 快捷键来打开终端。 使用 sudo:切换内核需要管理员权限,因此你需要使用 sudo 命令。首…

STM32存储左右互搏 I2C总线FATS读写EEPROM ZD24C1MA

STM32存储左右互搏 I2C总线FATS读写EEPROM ZD24C1MA 在较低容量存储领域,EEPROM是常用的存储介质,可以通过直接或者文件操作方式进行读写。不同容量的EEPROM的地址对应位数不同,在发送字节的格式上有所区别。EEPROM是非快速访问存储&#xf…

[C初阶笔记]P2

Git 1、Git是Linus为了帮助管理Linux内核开发 而开发的一个开放源码的分布式版本控制软件。 2、Git和TortoiseGit的作用。 Git中有各种命令行操作,来维护代码,可以将代码推送到代码托管平台。 TortoiseGit是将Git中各自命令行操作转化为图形化操作。 …

C语言好题解析(一)

目录 选择题1选择题2选择题3选择题4编程题一 选择题1 执行下面程序,正确的输出是( )int x 5, y 7; void swap() {int z;z x;x y;y z; } int main() {int x 3, y 8;swap();printf("%d,%d\n",x, y);return 0; }A: 5,7 B: …

Threejs学习03——实现随机多个三角形随机位置随机颜色展示效果

实现随机多个三角形随机位置随机颜色展示效果 这是一个非常简单基础的threejs的学习应用!本节主要介绍的是随机,随机位置以及随机颜色,我们使用的物体是三角形,通过一个三角形三个顶点每一个顶点通过xyz坐标来确定,则…

Java算法_ 检查对称树(LeetCode_Hot100)

题目描述:给你一个二叉树的根节点 , 检查它是否轴对称。root 获得更多?算法思路:代码文档,算法解析的私得。 运行效果 完整代码 /*** 2 * Author: LJJ* 3 * Date: 2023/8/17 8:47* 4*/ public class SymmetricTree {static class…

智慧水利利用4G物联网技术实现远程监测、控制、管理

智慧水利工业路由器是集合数据采集、实时监控、远程管理的4G物联网通讯设备,能够让传统水利系统实现智能化的实时监控和远程管理。工业路由器利用4G无线网络技术,能够实时传输数据和终端信息,为水利系统的运维提供有效的支持。 智慧水利系统是…

Linux知识点 -- Linux多线程(一)

Linux知识点 – Linux多线程(一) 文章目录 Linux知识点 -- Linux多线程(一)一、理解线程1.从资源角度理解线程2.执行流3.多线程编程4.线程的资源5.线程切换的成本更低6.线程的优缺点7.线程异常 二、线程控制1.clone函数2.线程异常…

Unity如何把游戏导出成手机安装包

文章目录 前言使用环境步骤添加场景构建APK 前言 本文章主要演示了,如何将制作好的游戏,导出成APK,安装到手机上。 使用环境 Unity2022。 步骤 首先打开你的项目,然后选择菜单栏的“File” > “Build Settings…”&#xf…

时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)

时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价) 目录 时序预测 | MATLAB实现基于CNN-BiLSTM卷积双向长短期记忆神经网络的时间序列预测-递归预测未来(多指标评价)预测结果基本介绍程序设计参考资料 预测结果 基本介绍…

【使用 k 折叠交叉验证的卷积神经网络(CNN)】基于卷积神经网络的无特征EMG模式识别研究(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

使用GraphQL在Postman中进行API测试

GraphQL 是一种用于API的开源数据查询和操作语言,用于API的查询语言和运行时。它使客户端能够精确地指定其数据需求,并获得预测性地结果。GraphQL旨在提高API的效率、灵活性和可靠性。 Postman 是一款用于API开发的强大工具,它支持REST和Gra…