图像识别之KNN算法的理解与应用

2023-10-27

KNN是最经典的机器学习算法之一。该算法既可以用于数据分类,也可以用于数据回归预测,其核心思路是在训练样本中寻找距离最接近待分类样本的K个样本。然后,如果目的是分类,则统计这K个样本中的各个类别数量,数量最多的类别即认为是待分类样本的类别;如果目的是回归预测,则计算这K个样本的平均值作为预测值。

图像识别,本质上也是数据分类,也即把每一张图像归类,因此KNN算法可以应用于图像识别。本文首先讲解KNN算法的原理,接着讲解Opencv中的KNN算法模块,然后再使用Opencv中的KNN算法模块对手写数字图像进行识别。

KNN算法主要包含以下几个要素:

1. 训练样本。训练样本就是预先准备好的数据集,该数据集必须包含所有可能的数据类别,而且数据集中的每个数据都有一个唯一的标签,用来标识该数据所属的类别。比如,待分类的图像有可能属于的类别包括鸟、狗、马、飞机、班车这5种类别,给5种类别依次编号0、1、2、3、4,那么训练样本必须包含这5种类别的图像,且训练样本中每一张图像都有一个范围在0~4之间的类别标签。

2. 待分类样本。也就是待分类的数据,比如一张图像,把该图像输入KNN算法之中,KNN算法对其进行分类,然后输出类别标签。

3. 样本距离。通常每一个样本都是一维向量的形式(二维、三维、多维数据都可以转换为一维向量)。衡量一维向量之间的距离,通常有欧式距离,余弦距离、汉明距离等,其中欧式距离又是最常用的距离度量方法。

假设样本A和样本B:

A=[a0 a1 a2 ... an]

B=[b0 b1 b2 ... bn]

那么样本A与样本B的欧式距离为:

4. K值。K值决定寻找训练样本中最接近待分类样本的样本个数,比如K值取5,那么对于每一个待分类样本,都从训练样本中寻找5个与其距离最接近的样本,然后统计这5个样本中各个类别的数量,数量最多的类别则认为是待分类样本的类别。K值没有固定的取值,通常在一开始取5~10,然后多尝试几次,根据识别的正确率来调整K值。

举一个简单的例子来说明KNN的分类思路,如下图所示,样本0为待分类样本,其可能的分类为矩形、圆形、菱形,取K=6,然后在所有训练样本中寻找与样本0最接近的6个样本:样本1、样本2、样本3、样本4、样本5、样本6。然后对这6个样本进行分类统计(每个训练样本是有标签的,所以知道其类别):3个矩形、2个菱形、1个圆形。矩形的数量最多,因此判定样本0为矩形。

讲完原理,下面我们开始讲Opencv3.4.1中KNN算法模块的应用。Opencv3.4.1中已经实现了该算法,并封装成类,我们只需要调用类的相关接口,并且把输入参数传入接口即可得到分类结果。调用接口的步骤如下:

1. 创建KNN类并设置参数。

  const int K = 3;   //取K值为3
  //创建KNN类
  cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();
  knn->setDefaultK(K);   //设置KNN类的K值
  knn->setIsClassifier(true);  //设置KNN用于分类 
  knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);   //设置寻找距离最近的K个样本的方式为遍历所有训练样本,也即暴力破解的方式

2. 输入训练数据及其标签:

Mat traindata;   //训练数据的矩阵
Mat trainlabel;  //训练数据的标签


traindata.push_back(srcimage);  //srcimage为Mat类型的1行n列的一维矩阵,将该矩阵保存到训练矩阵中
trainlabel.push_back(i);      //i为srcimage的标签,同时将i保存到标签矩阵中


traindata.convertTo(traindata, CV_32F);  //重要:训练矩阵必须是浮点型数据

3. 将训练数据与标签输入KNN模块中进行训练:

knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);

4. 训练之后,开始对待分类图像进行分类:

Mat result;
//testdata为待分类图像,被转换为1行n列的浮点型数据
//response为最终得到的分类标签
int  response = knn->findNearest(testdata, K, result); 

下面,我们将Opencv的KNN模块应用于手写数字识别。在Opencv3.4.1的samples/data目录有一张1000*2000的手写数字图像,该图像包含了5000个20*20的小图像块,每个小图像块就是一个手写数字,如下图所示:

首先,我们写个程序把上图分割成5000个20*20的图像块。把相同的数字保存到同名文件夹中,比如数字0的小图像块保存到文件夹0中、数字1的小图像块保存到文件夹1中、数字2的小图像块保存到文件夹2中。

分割图像块的代码如下:

void read_digital_img(void)
{
  char ad[128] = { 0 };
  int  filename = 0, filenum = 0;
  Mat img = imread("digits.png");
  Mat gray;
  cvtColor(img, gray, CV_BGR2GRAY);
  int b = 20;
  int m = gray.rows / b;   //原图为1000*2000
  int n = gray.cols / b;   //裁剪为5000个20*20的小图块


  for (int i = 0; i < m; i++)
  {
    int offsetRow = i*b;  //行上的偏移量
    if (i % 5 == 0 && i != 0)   //原图中每5行存储相同数字,因此过了5行要递增文件名
    {
      filename++;   //递增文件名
      filenum = 0;   //清零文件计数器
    }


    for (int j = 0; j < n; j++)
    {
      int offsetCol = j*b; //列上的偏移量
      sprintf_s(ad, "%d/%d.jpg", filename, filenum++);
      //截取20*20的小块
      Mat tmp;
      gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
      imwrite(ad, tmp);   //将对应的数字图像块保存到对应名字的文件夹中
    }
  }
}

运行上述代码之后,从0~9的文件夹都保存有对应数字的小图像块啦~不同数字的小图像块如下图所示。这时我们有0~9这10个文件夹,每个文件夹都有对应数字的500张小图像块,比如文件夹0中有500张不同手写风格的0数字图像。

下面我们使用每个文件夹下前400张图像作为训练图像,对KNN模型进行训练,然后使用该KNN模型对每个文件夹下后100张图像进行分类,并统计分类结果的准确率。

上代码:

void KNN_test(void)
{
  char ad[128] = { 0 };
  int testnum = 0, truenum = 0;
  const int K = 3;   //设置K值为3
  cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();  //创建KNN类
  knn->setDefaultK(K);
  knn->setIsClassifier(true);   //设置KNN用于分类 
  knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);   //设置寻找距离最近的K个样本的方式为遍历所有训练样本,也即暴力破解的方式  


  Mat traindata, trainlabel;


  for (int i = 0; i < 10; i++)
  {
    for (int j = 0; j < 400; j++)
    {
      sprintf_s(ad, "%d/%d.jpg", i, j);
      Mat srcimage = imread(ad);
      //把二维数据转换为一维数据
      srcimage = srcimage.reshape(1, 1);    //Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数
      traindata.push_back(srcimage);   //srcimage为Mat类型的1行n列的一维矩阵,将该矩阵保存到训练矩阵中
      trainlabel.push_back(i);  //i为srcimage的标签,同时将i保存到标签矩阵中
    }
  }


  traindata.convertTo(traindata, CV_32F);   //重要:训练矩阵必须是浮点型数据
  knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel);  //导入训练数据和标签


  for (int i = 0; i < 10; i++)   //标签
  {
    for (int j = 400; j < 500; j++)
    {
      testnum++;    //统计总的分类次数
      sprintf_s(ad, "%d/%d.jpg", i, j);
      Mat testdata = imread(ad);
      testdata = testdata.reshape(1, 1);  //将二维数据转换成一维数据
      testdata.convertTo(testdata, CV_32F);  //将数据转换成浮点数据


      Mat result;
      int  response = knn->findNearest(testdata, K, result);   //寻找K个最邻近样本,并统计K个样本的分类数量,返回数量最多的分类的标签 
      if (response == i)    //如果得到的分类标签与真实标签一致,则分类正确
      {
        truenum++;
      }
    }
  }
  cout << "测试总数" << testnum << endl;
  cout << "正确分类数" << truenum << endl;
  cout << "准确率:" << (float)truenum / testnum * 100 << "%" << endl;
}

运行上述代码,得到结果如下,可以看到,KNN算法对手写数字图像的识别(分类)的准确率还是比较高的。

由于手写数字图像包含的特征比较简单,因此KNN的识别准确率很高。实际上,对于一些复杂的图像,KNN的识别准确率是很低的。在下一篇文章中,我们将尝试使用KNN算法来识别更复杂的图像,并想办法提高识别的准确率,敬请期待!

尊敬的读者,您的关注是我继续更新的强大动力,如果您对我写的东西感兴趣,请长按并识别以下二维码,关注我的公众号,多谢多谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

图像识别之KNN算法的理解与应用 的相关文章

  • python - 如何删除每行中的重复列表(pandas)?

    我的每一行中都包含一个列表 我想通过保留分数中的最高值来删除重复元素 这是我的数据框 df1 中的数据 pair score 0 A A 1 0000 1 A F 0 9990 2 A G 0 9985 3 A G 0 9975 4 A H
  • celery任务eta已关闭,使用rabbitmq

    我使用教程中的默认设置和在 ubuntu 上运行的rabbitmq 使 Celery 任务正常进行 当我毫不延迟地安排任务时 一切都很好 但是当我给他们一个预计时间时 他们会被安排在未来 就好像我的时钟在某个地方关闭了一样 下面是一些请求任
  • 函数名称未定义

    我有一段代码 看起来像这样 if name main main def main print hello 但是 当我尝试运行此代码时 出现错误 NameError 名称 main 未定义 我是否没有在函数 def main 的第一行定义名称
  • 按每个元素中出现的数字对字符串列表进行排序[重复]

    这个问题在这里已经有答案了 我有一个脚本 其目的是对不断下载到服务器上的空间数据集文件进行排序和处理 我的列表目前大致如下 list file t00Z wrff02 grib2 file t00Z wrff03 grib2 file t0
  • 有什么好的适用于 Google App Engine 应用程序的 AJAX 框架吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在尝试在我的 Google App Engine 应用程序中实现 AJAX 因此我正在寻找一个好的
  • pandas read_csv 之前预处理数据文件

    我使用 SAP 的数据输出 但它既不是 CSV 因为它不引用包含其分隔符的字符串 也不是固定宽度 因为它具有多字节字符 它是一种 固定宽度 字符 为了将其放入 pandas 我当前读取文件 获取分隔符位置 对分隔符周围的每一行进行切片 然后
  • Discord.py 斜线命令在 cogs 中不起作用

    我正在构建一个不和谐的机器人 并且想要在 cogs 内使用斜杠命令 但这些命令不显示或工作 这是代码 cog guild ids 858573429787066368 861507832934563851 class Slash comma
  • cxfreeze virtualenv 中缺少 distutils 模块

    从 python3 2 项目运行 cxfreeze 二进制文件时 我收到以下运行时错误 project dist project distutils init py 13 UserWarning The virtualenv distuti
  • argparse 不检查位置参数

    我正在创建一个脚本 它使用 argparse 接受位置参数和可选参数 我已经阅读了 Doug 的教程和 python 文档 但找不到答案 parser argparse ArgumentParser description script t
  • Pandas 中每列的曲线拟合 + 外推值

    我有一个包含大约 300 列的数据集 每一列都与深度相关 Pandas DataFrame 的简化版本看起来像这样 import matplotlib pyplot as plt import numpy as np import pand
  • Pygame:有人可以帮我实现双跳吗?

    我知道已经有其他关于此问题的帖子了 但我的运动系统与我发现的有点不同 所以随后我问这个问题 我的运动系统基于一个名为的命名元组Move up left right down 然后就是这个 def update self move block
  • “KMeans”对象没有属性“k”

    我使用 Yellowbrick 包绘制数据集的肘部曲线 以使用 KMeans 作为模型找到数据集的最佳簇数 我正在使用 Scikit learn KMeans 和 Yellowbrick kelbowvisualizer 函数 生成了肘部曲
  • 增强迪基-富勒测试中的 BIC 在 Python 中到底是如何工作的?

    这个问题是关于 statsmodels tsa stattools python 库 adfuller 中的增强迪基 富勒测试实现 原则上 AIC 和 BIC 应该计算一组可用模型的信息标准 并选择最好的模型 信息损失最低的模型 但它们在增
  • 如何将 pandas DataFrame 转换为 TimeSeries?

    我正在寻找一种将 DataFrame 转换为 TimeSeries 而不拆分索引和值列的方法 有任何想法吗 谢谢 In 20 import pandas as pd In 21 import numpy as np In 22 dates
  • 解析整数集的字符串并列出间隔

    I have 2 5 7 9 12 string 我想从中获取 2 5 7 8 9 12 列表 python中有没有内置的函数 Thanks UPD 我想 直接的答案是No 不管怎样 谢谢你的 片段 使用一个 建议者斯文 马尔纳克 s 2
  • 为数据集生成随机 JSON 结构排列

    我想生成 JSON 结构的许多不同排列作为同一数据集的表示 最好不需要对实现进行硬编码 例如 给定以下 JSON name smith occupation agent enemy humanity nemesis neo 应该产生许多不同
  • 在自定义 keras 层的调用函数中传递附加参数

    我创建了一个自定义 keras 层 目的是在推理过程中手动更改前一层的激活 以下是基本层 它只是将激活值乘以一个数字 import numpy as np from keras import backend as K from keras
  • 如何仅读取 CSV 文件每行的第一列 [重复]

    这个问题在这里已经有答案了 如何在Python中读取CSV文件每行的第一列 我的数据是这样的 1 abc 2 bcd 3 cde 我只需要循环第一列的值 另外 当我在 calc 中打开 csv 文件时 每行中的数据都在同一个单元格中 这正常
  • 将函数按元素应用于两个 DataFrame

    如何应用函数z ij f x ij y ij 来自数据框X and Y相同大小并将结果保存到 DataFrameZ 这取决于你有什么样的功能 很多功能已经被矢量化为数据框 例如 等等 所以对于这些功能 你可以简单地做Z X Y or Z X
  • 将二进制数转换为包含每个二进制数的数组

    我试图将二进制值转换为每个 1 0 的列表 但我得到默认的二进制值而不是列表 我有一个字符串 我将每个字符转换为二进制 它给了我一个列表 其中每个字符都有一个字符串 现在我试图将每个字符串拆分为值为 0 1 的整数 但我什么也得不到 if

随机推荐

  • jmeter 获取全部响应_jmeter 获取响应头数据(Respones headers)

    最近遇到一个请求重定向 想要判断url 是否和预期一致 找了下有两种方法 一 用正则表达式提取器 1 想要提取如下图响应数据 正则表达是提取设置如下 二 beanshell 断言 该组件可以直接获取ResponseHeaders 底部显示如
  • 金山卫士开源软件之旅(十) KSafeMain工程的分析 1

    上一次看金山开源到现在已有一两个月了 期间看到QQ群里大家对它很是热情 最近有时间想看看金山的主界面工程KSafeMain 自己水平有限 总结的东西浅显 但还是愿意拿来与大家分享 希望对大家有帮助 转载请标明是引用于 http blog c
  • C#中的object类深入理解

    C 中所有的类都直接或间接继承自System Object类 这使得C 中的类得以单根继承 如果我们没有明确指定继承类 编译器缺省认为该类继承自System Object类 System Object类也可用小写的object关键字表示 两
  • 软工实习日记12

    今天的任务是新闻归档功能的实现 申请访问不存在的网站页面时使用准备的404界面隐藏错误信息 以及使用拦截器拦截管理员未登录时申请访问后台页面的请求 下面给出关键代码 service层 NewService java 接口文件 Map
  • LeetCode 2255. 统计是给定字符串前缀的字符串数目

    给你一个字符串数组 words 和一个字符串 s 其中 words i 和 s 只包含 小写英文字母 请你返回 words 中是字符串 s 前缀 的 字符串数目 一个字符串的 前缀 是出现在字符串开头的子字符串 子字符串 是一个字符串中的连
  • Android好用的轮播图控件

    于app开发 大部分都会涉及到轮播图 你也可以自己用viewpager自己写 这边介绍几种写好的库 1 ConvenientBanner 博客地址 2 infiniteindicatorlayout 点击打开链接 这是一个网上有人进行封装的
  • java form 上传文件_JAVA入门[16]-form表单,上传文件

    一 如何传递参数 RequestMapping detail public String detail RequestParam id int id Model model Category category new Category ca
  • nextcloud设置用户容量,设置用户配额

    先登录nextcloud管理员账号 然后找到用户 然后在左下角找到设置 然后在设置中设置对应的容量 设置完毕以后 所有用户 包括管理员 也会被设置为10g 自己手动改一改就好 嗯大概就是这样 然后如果你要设置某个用户的容量 点击一下笔 就能
  • threejs的dat.gui辅助工具的使用

    threejs的dat gui辅助工具的使用 安装 使用 安装 npm i dat gui S 使用 import dat from dat gui const controlData rotationSpeed 0 01 color 66
  • Tensorflow:介绍常见激活函数和池化,并用Tensorflow搭建前向神经网络

    Tensorflow 介绍常见激活函数和池化 并用Tensorflow搭建前向神经网络 一 常见激活函数和池化 1 激活函数 激活函数通常用于卷积层和全连接层的末端 为神经网络提供非线性变化 1 1 sigmoid S形曲线 早期用于卷积层
  • AN OVERVIEW OF LANGUAGE MODELS RECENT DEVELOPMENTS AND OUTLOOK

    LLM系列相关文章 针对 AN OVERVIEW OF LANGUAGE MODELS RECENT DEVELOPMENTS AND OUTLOOK 的翻译 语言模型综述 近年来的发展与展望 摘要 1 引言 2 语言模型的类型 2 1 结
  • 手动删除shmget创建的共享内存

    使用shmget创建或打开共享内存的时候 其中有一个参数是制定了权限的 这个权限类似于文件的权限 指定了什么身份可以打开已经存在的共享内存 在我的项目中出现了一个程序运行失败 经过调查发现在使用shmget打开一段共享内存的时候没有权限 造
  • win10家庭版计算机属性里没有远程桌面,win10家庭版不支持远程桌面怎么办_远程桌面无法连接的解决方法...

    众所周知 有些win系统的家庭版本是无法远程桌面连接的 而且在连接的时候会报错 如果重新安装系统十分麻烦 那么咱们要怎么解决这个部分Windows电脑无法远程桌面连接的问题呢 下面小编就来分享多种Win电脑系统无法远程桌面连接的正确解决方法
  • js页面初始化方法只调用一次_10个常见的JS语言错误总汇

    1 Uncaught TypeError Cannot Read Property这是 JavaScript 开发人员最常遇到的错误 当你读取一个属性或调用一个未定义对象的方法时 Chrome 中就会报出这样的错误 导致这个错误发生的原因有
  • 地图采集车的那些事

    一 背景 高精地图 高精采集车 是做地图和出行领域同学经常挂在嘴上的一些常用词儿 但是 圈外的同学可能会问 到底什么是高精 高精是指高精度定位 高精地图是指包含丰富地理信息数据 具有高精度坐标的地图 当然 高精采集车就是采集制作高精地图数据
  • Caffe源码中io文件分析

    Caffe源码 caffe version commit 09868ac date 2015 08 15 中有一些重要的头文件 这里介绍下include caffe util io hpp文件的内容 1 include文件 1
  • MySQL 的FLASHBACK 数据回滚

    数据库的里面的FLASHBACK 功能是一个让人刮目相看的功能 如果你做错了什么怎么能将那段时间的数据恢复 并且还让生产的应用不停止 这是一个数据库管理员都想拥有的功能 SQL SERVER 需要借助第三方软件的功能 可以完成数据的回滚和恢
  • Python selenium各个组件的操作

    一 操作文本输入框 常用方法 说明 sendkeys 设值 clear 清空文本框内容 get attribute 获取文本框中的值 is display 判断元素是否显示 案例演示 from time import sleep from
  • Spring 如何解决循环依赖的问题

    一 什么是循环依赖 是两个或两个以上对象互相引用 即A依赖B B依赖C C又依赖A 例如 service public class A private B b Autowired public void setB B b this b b
  • 图像识别之KNN算法的理解与应用

    KNN是最经典的机器学习算法之一 该算法既可以用于数据分类 也可以用于数据回归预测 其核心思路是在训练样本中寻找距离最接近待分类样本的K个样本 然后 如果目的是分类 则统计这K个样本中的各个类别数量 数量最多的类别即认为是待分类样本的类别