autojs-KNN算法手写数字识别的OpenCV实现

邻近算法,或者说K最近邻(KNN,K-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。

autojs-KNN算法手写数字识别的OpenCV实现

上面这张图片网上说是opencv自带的, 我下载的4.5.2的opencv的安卓版本, 就没找到

knn简介

百科简介

邻近算法,或者说K最近邻(KNN,K-NearestNeighbor)分类算法是数据挖掘分类技术中最简单的方法之一。所谓K最近邻,就是K个最近的邻居的意思,说的是每个样本都可以用它最接近的K个邻近值来代表。近邻算法就是将数据集合中每一个记录进行分类的方法 。

网友大奸猫对knn的算法描述

S1 算距离
给定未知样本点A,计算它与训练集中的每个样本点的距离
S2 找邻居
将S1计算好的距离升序排列,取前k个最近的样本点作为A的邻居
S3 确定分类
这k个邻居中,包含邻居数最多的类别作为A的类别

knn示意图

目标是黄色多边形, 距离黄色多边形最近的3个邻居, 有两个是红色, 一个是蓝色,

按照近朱者赤近墨者黑少数服从多数来推断, 就认为黄色多边形是红色

也可以说是, 你身边那种人多, 你就会成为那种人

autojs-KNN算法手写数字识别的OpenCV实现

算法常用的距离

曼哈顿距离(城市街区距离)

欧式距离

马氏距离(闵可夫斯基距离)

余弦距离

切比雪夫距离

海明距离

autojs版本

8.8.16-0

本版本自带3.4.3的opencv

knn简要流程

  1. 图片是一大张, 所以第一步是切割图片, 每个图片只包含一个数字
  2. 训练
  3. 预测

训练

训练就是把特征和标签一一对应起来, 再对数据做一些处理,

这里主要说一下特征提取,

特征: 取图片每个像素点的数值, 一张图片是20X20, 就是400个像素, 并且图片是一个通道

关键代码:

let tempData = Imgcodecs.imread(filePath, 0); // 一个通道
tempData = tempData.reshape(0, 1); // 矩阵变为一行
tempData.convertTo(tempData, CvType.CV_32F); // 数据变为浮点数
tempData.copyTo(tmp); // 保存特征
trainlabel.put(0, 0, trainClasses); // 打标签
knn.train(trainData, Ml.ROW_SAMPLE, trainlabel); // 训练

预测

预测就是拿一小部分数据测试,

训练的时候留一小部分数据, 不参加训练, 而是用于预测

预测, 和训练一样,

提取图片特征, 拿去和训练好的数据计算距离,

然后返回匹配度最高的值

关键代码

let tempData = Imgcodecs.imread(filePath, 0); // 一个通道
tempData = tempData.reshape(0, 1); // 矩阵变为一行
tempData.convertTo(tempData, CvType.CV_32F); // 数据变为浮点数
let response = knn.findNearest(tempData, k, nearests); // 计算最佳匹配

统计

log("测试总数: " + testNum);
log("正确分类数: --> " + trueNum);
log("准确率:" + (trueNum / testNum) * 100 + "%");

Mat

mat是opencv常用的数据类型, 理解Mat的格式后, 对理解opencv代码很有帮助

修改Mat

autojs的Mat没有at方法, 那么修改数据就用get和put

runtime.images.initOpenCvIfNeeded();
log(new org.opencv.core.Mat().getClass());
delete org.opencv.core.Mat;
log(new org.opencv.core.Mat().getClass());
importClass(org.opencv.core.Mat);
importClass(org.opencv.core.CvType);

//32位浮点数 1个channel
let trainlabel = Mat.ones(100, 1, CvType.CV_32FC1);

for (var i = 0; i < 100; i++) {
  log("修改前" + i + ": ", trainlabel.get(i, 0));
  let item = util.java.array("float", 1);
  item[0] = i;
  log(trainlabel.put(i, 0, item));
  log("修改后" + i + ": ", trainlabel.get(i, 0));
}

打印Mat宽高行列

let trainlabel = Mat.ones(100, 1, CvType.CV_32FC1);
let infoList = [
  "n",
  "row: " + trainlabel.rows(),
  "col: " + trainlabel.cols(),
  "height: " + trainlabel.height(),
  "width: " + trainlabel.width(),
];
log(infoList.join("n"));
// row: 100
// col: 1
// height: 100
// width: 1

把Mat想象成一堵墙就可以了,

横着的是row, 竖着的是col,

有多少row, 就有多height,

有多少col, 就有多width

autojs-KNN算法手写数字识别的OpenCV实现

Mat参数一般是row前, col后

先看数据在第几行, 再看数据在第几列

也符合人的思维

我们读书也是从左往右, 从上往下

古人的话估计是先看第几列, 再看第几行,

因为古人的书是竖着写的

autojs-KNN算法手写数字识别的OpenCV实现

打印Mat具体的数据

runtime.images.initOpenCvIfNeeded();
importClass(org.opencv.core.Mat);
importClass(org.opencv.core.CvType);

// 3行2列
let trainlabel = Mat.ones(3, 2, CvType.CV_32FC1);
let width = trainlabel.width();
let height = trainlabel.height();
let arr = [];
// height和行数 数值一样
// 可以认为 hegith ⇔ 行数
for (var i = 0; i < height; i++) {
  let childArr = [];
  for (var j = 0; j < width; j++) {
    let item = trainlabel.get(i, j);
    childArr.push(item);
  }
  arr.push(childArr);
}
log(JSON.stringify(arr, null, "    "));
// [
//   [[1.0], [1.0]],
//   [[1.0], [1.0]],
//   [[1.0], [1.0]]
// ]

声明

部分内容来自网络

本教程仅用于学习, 禁止用于其他用途

内容出处:,

声明:本网站所收集的部分公开资料来源于互联网,转载的目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。如果您发现网站上有侵犯您的知识产权的作品,请与我们取得联系,我们会及时修改或删除。文章链接:http://www.yixao.net/procedure/24893.html

发表评论

登录后才能评论