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

牙叔教程 简单易懂javascript

digits.png

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

knn简介

百科简介git

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

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

S1 算距离网络

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

knn示意图

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

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

也能够说是, 你身边那种人多, 你就会成为那种人 knn示意图.pngspa

算法经常使用的距离

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

欧式距离

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

余弦距离

切比雪夫距离

海明距离

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

砖墙.jpg

Mat参数通常是row前, col后

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

也符合人的思惟

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

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

由于古人的书是竖着写的 古书.jpg

打印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]]
// ]
复制代码

声明

部份内容来自网络 本教程仅用于学习, 禁止用于其余用途

相关文章
相关标签/搜索