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]]
// ]
複製代碼

聲明

部份內容來自網絡 本教程僅用於學習, 禁止用於其餘用途

相關文章
相關標籤/搜索