OpenCV:Knn算法

在用字符识别上进行测试
训练集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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值