在用字符识别上进行测试
训练集3000张图片
测试集2000张图片
这3000和2000的图像没有重复的
Knn:
目标:将待测物分类成多个类别
输入:待测物(已知类别集合D,其中包含j个已知类别)
输出:项目可能的类别。
优点:
算法简单,易于实现,不需要参数统计,不需要事先训练
缺点:
KNN计算量特别大,而且训练样本必须存储在本地,内存开销也特别大
K的取值(一般不大于20)
opencv提供了一张图片,在路径C:/opencv/sources/samples/data/digits.png下
如下图所示:
仔细看,其中都是手写的数字,是0~9之间,数字每个大小一致,宽高为20*20,
每个进行序列化,然后可以得到一共5000个样品,选择其中的3000个进行训练。
如果横着进行训练,那么最下面的9就训练不到了,所以竖着进行训练,那样0~9就都训练到了
代码如下:
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
using namespace cv::ml;
int main(int argc, char** argv)
{
//读取图片
Mat cvImg = imread("C:/opencv/sources/samples/data/digits.png");
//真正测试的灰度图片
Mat cvGrayImg;
//从原图转换成灰度图
cvtColor(cvImg, cvGrayImg, CV_BGR2GRAY);
//图像的宽高如下2000*1000
int nWidth = cvGrayImg.cols;
int nHeiht = cvGrayImg.rows;
//经过查看,分成20*20的小图片,将宽/20,高也/20
int nCrop = 20;
int nSmallWidth = nWidth / nCrop;
int nSmallHeight = nHeiht / nCrop;
Mat vImgData; //所有图像数据扁平化存储
Mat vImgLabels; //所有图像的标注数据
//共计100*50=5000个数据
for (int i = 0; i < nSmallWidth; i++)
{
//每一列的纵坐标需要偏移如下
int offsetCol = i*nCrop;
for (int j = 0; j < nSmallHeight; j++)
{
//每一行的横坐标需要偏移如下
int offsetRow = j*nCrop;
//在cvGrayImg上截取20*20的小图像,拷贝到tempImg上
Mat tempImg;
cvGrayImg(Range(offsetRow, offsetRow + nCrop), Range(offsetCol, offsetCol + nCrop)).copyTo(tempImg);
//tempImg图像本身是一个20*20的矩阵,进行变换,通道数不变,将矩阵序列化成1行N列的行向量。
//可以理解成这个vImgData数组中有所有小图像的数据,但是是以行向量的形式存储的.相当于图像数据在这里只有一行,但是有20*20=400列
vImgData.push_back(tempImg.reshape(0, 1));
//这次是因为里面的数据就是0,1,2,3,如果是字母的话,可能需要在这里人工的输入这些字母。
//数据的扫描。因为外面的循环是nSmallHeight,所以每次先扫描每一列,数据是0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3....
vImgLabels.push_back((int)j / 5);
}
}
//数据大小没有变化,但是类型由uchar变为了cv_32f位
vImgData.convertTo(vImgData, CV_32F);
//所有图像数量,即上述的5000
int nImgCount = vImgData.rows;
//选择其中的3000张作为训练样本
int nTrainCount = 3000;
Mat trainData, trainLabels;
//前4000个样本为训练数据
//理解为截取vImgData这个容器里面顺序0到nTrainCount的数据
trainData = vImgData(Range(0, nTrainCount), Range::all());
trainLabels = vImgLabels(Range(0, nTrainCount), Range::all());
//使用KNN算法
int K = 5;
//设置训练数据,固定用法
//训练数据为trainData,且已经标注好trainLabels。待测数据为tData.
Ptr<TrainData> tData = TrainData::create(trainData, ROW_SAMPLE, trainLabels);
创建knn分类器
Ptr<KNearest> model = KNearest::create();
// 设定k值
model->setDefaultK(K);
model->setIsClassifier(true);
// 设置训练数据,进行训练。就相当于将前面的标注数据和测试数据进行了一个训练绑定
model->train(tData);
//就上述的几行,就代码Knn训练完毕了
//预测分类
//训练Ok的图像数量
int nTrainOkCount = 0;
//测试OK的图片数量
int nTestOkCount = 0;
//所有样本进行测试
for (int i = 0; i < nImgCount; i++)
{
//选择待测图像
Mat imgForTest = vImgData.row(i);
//用KNN分类器对待测参数进行测试
//参数是测试样品,每行作为一个测试数据,即要将每一个样品处理成导入的模型同样维度大小的一行数据,
//返回值代表测试出的结果
int nResult =(int) model->predict(imgForTest);
//第i个数据,在vImgLabels中的值是多少,即这个数据实际结果是什么
int nTempLabel = vImgLabels.at<int>(i);
//测试结果和标注结果的差值,==0说明是正确的,!=0说明是错误的
int nOffset = nResult - nTempLabel;
bool bTempResult = false;
if (nOffset==0)
{
bTempResult = true;
}
if (i < nTrainCount)
{
//前3000张图跑都是训练集里面的图像
if (bTempResult)
{
//说明该次训练是ok的,训练正确的总数+1
nTrainOkCount++;
}
}
else
{
//剩余的是测试集里面的图像
if (bTempResult)
{
//说明该次测试是ok的,测试正确的总数+1
nTestOkCount++;
}
}
}
double dTrainOkPercent =(double) nTrainOkCount / nTrainCount;
double dTesrOkPercent = (double)nTestOkCount / (nImgCount-nTrainCount);
printf("统计: 训练合格率 = %.2f%%, 测试合格率 = %.2f%%\n", dTrainOkPercent*100., dTesrOkPercent*100.);
waitKey(0);
return 0;
}
测试出的结果如下:
对于这个合格率和KNN系数,做了一个对应的表格
所以K参数选择5
关于KNN的原理,可以查看下面的链接
https://blog.csdn.net/qq_36330643/article/details/77532161
关于距离的理解,可以看下面的链接
https://blog.csdn.net/saltriver/article/details/52502253