KNN算法即K最近邻算法(K-NearestNeighbors),是一种相对较为简单的机器学习算法。
对于KNN算法接口的使用我一直有疑问,train完之后存储的都是什么东西?
参考其他博客知道了KNN是一种懒惰算法,所谓懒惰算法就是,只有当新的样本出现时,该算法才会根据原始的训练样本对新样本进行预测处理工作,所以train完之后,保存的xml文件其实仍然保留了原始的训练样本。与懒惰学习算法相对应的是渴望学习算法,它会在测试之前利用训练样本建立一个统一的、与输入量相独立的目标函数,测试样本就是利用该函数进行预测,因此一旦目标函数创建完成,训练样本数据就可以完全抛弃掉。与渴望学习算法相比,懒惰学习算法所构建的目标函数能够更近似测试样本数据本身,但同时它需要更大的存储空间用于存储训练样本数据。总之,懒惰学习算法非常适用于那些需要处理具有较少特征属性的大型数据库的问题。参考:https://blog.csdn.net/zhaocj/article/details/50764093
KNN的算法原理其实很简单,就是计算样本到训练集所有数据的距离,然后取出距离最近的K个样本,然后统计K个样本中的标签,哪个标签多那么新的样本就会被打上标签。再opencv中封装好了KNN的函数,学会怎么使用和调参就可以了。不过KNN能够调整的参数还是比较少的,就是K值了。
上代码,说实话编写这样一段小代码让我对opencv中一些基础知识有了更深的理解。
1、KNN的基础使用方法
但是这边仍然存在一点疑问,在此列下,等问题解决了来填坑:
1、float response = knn->findNearest(sample, K, nearests); 这边的response返回的应该是预测值,就是标签0或1,那nearests中存储的是什么呢?我设置的nearest是1xK的,但是运行之后变成1x1了。既然这边的findNearest和predict的结果相同,那区别是什么呢?
答:这边的函数接口使用有问题,第三个参数是预测的值result,result的大小应该是number_sample,第4个参数是上面的nearest。
2、设置setIsClassifier作用是什么呢?设置为true的话,那就是分类,上面参数result中的值即为对应的标签值,这个变迁是样本中肯定存在的;如果设置为false的话,即为回归,那么result的值是一个连续值,这个连续值应该是由最近邻标签通过函数映射得到的,比如均值。
3、find_nearest和predict的使用有什么区别呢?除了调用上有区别,其他应该没有区别。
// ex10-1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <opencv2\opencv.hpp>
#include <string>
using namespace std;
using namespace cv;
using namespace cv::ml;
int train_sample_nums = 200; //训练样本个数
const int K = 12; //最近邻参数K
const float AccuracyCritical = 0.8f;
int main()
{
/*
(1)、cv::ml::Knearest类:继承自cv::ml::StateModel,而cv::ml::StateModel又继承自cv::Algorithm;
(2)、create函数:为static,new一个KNearestImpl用来创建一个KNearest对象;
(3)、setDefaultK/getDefaultK函数:在预测时,设置/获取的K值;
(4)、setIsClassifier/getIsClassifier函数:设置/获取应用KNN是进行分类还是回归;
(5)、setEmax/getEmax函数:在使用KDTree算法时,设置/获取Emax参数值;
(6)、setAlgorithmType/getAlgorithmType函数:设置/获取KNN算法类型,目前支持两种:brute_force和KDTree;
(7)、findNearest函数:根据输入预测分类/回归结果。
*/
/*****************************数据集制作*********************************/
Mat trainData(train_sample_nums, 2, CV_32FC1);
Mat trainClassess(train_sample_nums, 1, CV_32FC1);
//创建可视化图像
Mat img(500, 500, CV_8UC3, Scalar::all(0));
//样本点
Mat sample(1, 2, CV_32FC1);
Mat trainData1, trainData2, trainClasses1, trainClasses2;
RNG rng = RNG(-1);
//生成均值为(200,200),方差为(40,40)的随机数据
trainData1 = trainData.rowRange(0, train_sample_nums / 2);
rng.fill(trainData1, CV_RAND_NORMAL, Scalar(200, 200), Scalar(40, 40));
trainData2 = trainData.rowRange(train_sample_nums / 2, train_sample_nums);
rng.fill(trainData2, CV_RAND_NORMAL, Scalar(300, 300), Scalar(40, 40));
//trainClasses1和trainClassess的前100行绑定
trainClasses1 = trainClassess.rowRange(0, train_sample_nums / 2);
trainClasses1 = Scalar::all(1);
trainClasses2 = trainClassess.rowRange(train_sample_nums / 2, train_sample_nums);
trainClasses2 = Scalar::all(2);
/**************************训练********************************/
cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create();
knn->setIsClassifier(true);
knn->setAlgorithmType(KNearest::Types::BRUTE_FORCE);
//设置k值
knn->setDefaultK(12);
//制作数据集
cv::Ptr<TrainData> Traindata = TrainData::create(trainData, SampleTypes::ROW_SAMPLE, trainClassess);
//训练
knn->train(Traindata);
/**************************分类******************************/
Mat nearests(1, K, CV_32FC1); //存放结果, 1行k列
//遍历图像
for(int i = 0; i<img.rows;i++)
for (int j = 0; j < img.rows; j++)
{
//每个像素作为一个样本,样本值为坐标的x,y
sample.at<float>(0, 0) = (float)i;
sample.at<float>(0, 1) = (float)j;
float accuracy = 0.0;
float response = knn->findNearest(sample, K, nearests); //函数接口使用有问题,不过这里没有造成影响是因为第4个参数本身可以不设置 返回值是什么??????
for (int k = 0; k < k; k++) //有问题
{
if (nearests.at<float>(0, k) == response)
accuracy += 1.0f;
}
accuracy /= K;
这个怎么赋值
img.at<Vec3b>(i, j)[0] = response == 1 ? (accuracy > AccuracyCritical ? 160 : 180) :
(accuracy > AccuracyCritical ? 0 : 120);
img.at<Vec3b>(i, j)[1] = response == 1 ? (accuracy > AccuracyCritical ? 0 : 120) :
(accuracy > AccuracyCritical ? 160 : 180);
img.at<Vec3b>(i, j)[2] = response == 1 ? (accuracy > AccuracyCritical ? 0 : 0) :
(accuracy > AccuracyCritical ? 0 : 0);
}
//显示结果
for (int i = 0; i < train_sample_nums / 2; i++)
{
Point pt;
pt.x = round(trainData1.at<float>(i, 0));
pt.y = round(trainData1.at<float>(i, 1));
circle(img, pt, 1, Scalar(255, 255, 0));
pt.x = round(trainData2.at<float>(i, 0));
pt.y = round(trainData2.at<float>(i, 1));
circle(img, pt, 1, Scalar(0, 255, 255));
}
imshow("分类结果", img);
waitKey();
}
KNN做手写数字识别
数据集如下:是一张2000x1000大小的图片,其中0-9总共10个数字,每个数字样本都是20x20,所以每个数字一共有5x100个。
使用时需要将每个数字分割出来制作成数据集。这基础上,学习了别人的博客,自己制作手写数据集,并将其应用到本次识别中。
原始的识别代码如下:
结果:accuracy: train = 95.9%, test = 92.6%
#include "pch.h"
#include <iostream>
#include <opencv2\opencv.hpp>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main()
{
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; //一行100个,一列50个
// m * n = 50 * 100
Mat data, labels; //特征矩阵
for (int i = 0; i < n; i++)
{
int offsetCol = i * b;//向右偏移
for (int j = 0; j < m; j++)
{
int offsetRow = j * b;//向下偏移
//截取20 * 20的小块
Mat tmp;
gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp); //一列一列取数据
data.push_back(tmp.reshape(0, 1)); //序列化后放入特征矩阵
labels.push_back((int)j / 5); //标注
}
}
data.convertTo(data, CV_32F); //转成float类型
int samplesNum = data.rows; //样本的个数等于data的行数
int trainNum = 3000;
Mat trainData, trainLabels;
trainData = data(Range(0, trainNum), Range::all());
trainLabels = labels(Range(0, trainNum), Range::all());
/***********模型***********/
int K = 5;
cv::Ptr<TrainData> tData = TrainData::create(trainData, ROW_SAMPLE, trainLabels);
cv::Ptr<cv::ml::KNearest> model = cv::ml::KNearest::create();
model->setDefaultK(K);
model->setIsClassifier(true);
model->train(tData);
model->save("KnnTest.xml");
//如果要载入该模型
//cv::Ptr<cv::ml::KNearest> testModel = StatModel::load<ml::KNearest>("KnnTest.xml");
/*********预测**************/
double train_hr = 0, test_hr = 0;
Mat response;
for (int i = 0; i < samplesNum; i++)
{
Mat sample = data.row(i);
float r = model->predict(sample); //预测出来的是什么东西 ,上面的训练步骤是在做什么 model predict出来的是什么
//r = abs(r - labels.at<int>(i)) <= 1 ? 1.f : 0.f;
r = r == labels.at<int>(i) ? 1.f : 0.f;
if (i < trainNum)
train_hr += r;
else
{
test_hr += r;
}
}
test_hr /= samplesNum - trainNum;
train_hr = trainNum > 0 ? train_hr / trainNum : 1.;
printf("accuracy: train = %.1f%%, test = %.1f%%\n",
train_hr*100., test_hr*100.);
waitKey();
return 0;
}
接下来添加自己制作的手工数据集并测试。
参考:https://blog.csdn.net/xiexu911/article/details/79685165
参考:https://blog.csdn.net/akadiao/article/details/79243651
既然KNN可以用作分类,那么图像分割应该也可以用上。还没做,等填坑......