Opencv学习笔记 - 使用opencvsharp和knn进行手写识别训练和预测

Opencv学习笔记 - 使用opencvsharp和knn进行手写识别训练和预测,第1张

Opencv学习笔记 - 使用opencvsharp和knn进行手写识别训练和预测 一、KNN概述

        在所有的机器学习算法中,K近邻(KNN)几乎是最简单的,而且它既可用于分类(预测离散的类别标签),也可用于回归(预测连续的实数值)。尽管简单,但事实证明在某些任务中KNN非常有效。因为KNN需要存储所有的训练样本,占用内存很大,所以速度相对较慢。因此在使用KNN之前,常常需要通过聚类降低训练集大小。        

        KNN是最简单的分类算法,也可以用于回归。对于待分类的样本,计算待预测样本和训练集中所有数据点的距离,将距离从小到大取前K个,哪个类别在前K个数据点中的数据最多,就认为待预测样本属于该类别。

1、样本的特征空间

        在训练样本集中,每个样本x都是一个由nx个特征值构成的特征向量x=(x1, …,xnx),因此可以认为每个样本都在一个nx维特征空间内分布。例如,日常生活中所说的三维空间,通常是指由长、宽、高三个维度所构成的空间。三维空间中的每个样本x都可以用包含长、宽、高三个特征的特征向量x=(x1, x2, x3)来唯一描述,显然样本x的分布就是在长、宽、高三个维度的特征空间内的分布。

        对于监督学习来说,每个样本都有一个标签y, 分类算法或回归算法的目的就是找到一个函数f,y=f(x),使得以足够高的精度逼近y。这样,当有一个新样本x-test时,就可以使用该函数确定其标签了。

2、向量之间的距离

        样本在特征空间中的分布是用特征向量描述的,因此可以使用向量之间的距离来衡量样本与样本之间的相似程度。两个向量之间距离的计算,在数学上称为向量距离(Distance),也称为向量之间的相似性度量(Similarity Measurement)。它反映为某类事物在距离上接近或远离的程度。距离越近就越相似,越容易归为一类;距离越远越不同。常用的向量距离计算公式有欧氏距离、曼哈顿距离、闵可夫斯基距离、汉明距离、切比雪夫距离、夹角余弦、杰卡德相似系数等。

        在这里就不详述这些距离的计算方式了。

3、KNN原理

        对于KNN而言,最重要的参数K需要事先确定。不同的K值将直接影响KNN的最终预测结果。若K值过小,则相同类的训练样本未被纳入算法计算,会引入误差;若K值过大,虽然更准确,但会使在特征空间内明确的分类边界变得模糊。目前K值常通过交叉验证(Cross Validation)来选择。

        在上述计算中,通过排序找到K个与测试样本最相似的训练样本后,并没有考虑这K个训练样本与测试样本的距离仍然有大小差异,而只是一视同仁地表决或者求均值。更科学的做法是在这K个样本中,进一步使用距离加权的方式进行投票或者加权求平均,距离越近的权值越高,距离越远的权值越低。需要说明的是,OpenCV并没有采用交叉验证或距离加权的方式来实现KNN算法,如果需要,读者可尝试自行改进。

二、opencvsharp手写识别的例子 1、准备数据

        在这里用了全量的数据,mnist有60000张训练数据图片,10000章测试数据图片,已经上传到百度网盘,下载地址。

链接:https://pan.baidu.com/s/1FYwSSZwrkg7G7jvpmyZRvA 
提取码:3nej

2、c++、opencv参考代码
//读取文件夹内的所有文件
void getFiles(string path, vector& files)
{
	// 文件句柄
	intptr_t hFile = 0;
	// 文件信息
	struct _finddata_t fileinfo;

	string p;

	if ((hFile = _findfirst(p.assign(path).append("\*").c_str(), &fileinfo)) != -1) {
		do {
			// 保存文件的全路径
			if (strcmp(fileinfo.name, ".") != 0 && strcmp(fileinfo.name, "..") != 0)
				files.push_back(p.assign(path).append("\").append(fileinfo.name));

		} while (_findnext(hFile, &fileinfo) == 0); //寻找下一个,成功返回0,否则-1

		_findclose(hFile);
	}
}

//knn手写数字识别
void knn()
{
	int train_sample_count = 60000;
	int test_sample_count = 10000;
	Mat trainData = Mat(train_sample_count, 28 * 28, CV_32FC1);
	Mat testData = Mat(test_sample_count, 28 * 28, CV_32FC1);

	Mat trainLabel = Mat(train_sample_count, 1, CV_32FC1);
	Mat testLabel = Mat(test_sample_count, 1, CV_32FC1);

	string trainPath = "D:\mnist\mnist_png_full\training";
	string testPath = "D:\mnist\mnist_png_full\testing";

	//组织训练数据
	int trainNums = 0;
	for (int i = 0; i < 10; i++)
	{
		stringstream ss;
		ss << trainPath << "\" << i;
		string path = ss.str();
		vector files;
		getFiles(path, files);
		int size = files.size();
		for (int a = 0; a < size; a++)
		{
			Mat temp = imread(files[a].c_str(), IMREAD_GRAYSCALE);
			temp.convertTo(temp, CV_32FC1);
			temp.reshape(0, 1).copyTo(trainData.row(trainNums));
			trainLabel.at(trainNums) = i;
			trainNums++;
		}
	}

	//组织测试数据
	int testNums = 0;
	for (int i = 0; i < 10; i++)
	{
		stringstream ss;
		ss << testPath << "\" << i;
		string path = ss.str();
		vector files;
		getFiles(path, files);
		int size = files.size();
		for (int a = 0; a < size; a++)
		{
			Mat temp = imread(files[a].c_str(), IMREAD_GRAYSCALE);
			temp.convertTo(temp, CV_32FC1);
			temp.reshape(0, 1).copyTo(testData.row(testNums));
			testLabel.at(testNums) = i;
			testNums++;
		}
	}

	cv::Ptr knn = cv::ml::KNearest::create();
	int k = 5;
	knn->setDefaultK(k);
	knn->setIsClassifier(true);
	knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE);


	//训练
	knn->train(trainData, cv::ml::ROW_SAMPLE, trainLabel);

	//测试
	Mat result = Mat(test_sample_count, 1, CV_32FC1);
	knn->findNearest(testData, k, result);
	int t = 0;
	int f = 0;
	for (int i = 0; i < test_sample_count; i++)
	{
		int predict = (int)result.at(i);
		int actual = (int)testLabel.at(i);

		if (predict == actual)
		{
			printf("label:%d, predict:%d n", actual, predict);
			t++;
		}
		else
		{
			printf("label:%d, predict:%d 错误 n", actual, predict);
			f++;
		}
	}

	double accuracy = (t * 1.0) / (t + f);
	printf("准确率:%.4fn" , accuracy);
	waitKey();
}

        运行结果, 准确率是0.9688,同下面的c#一样

3、c#、opencvsharp参考代码
//训练数据数量
int train_sample_count = 60000;
//测试数据数量
int test_sample_count = 10000;
//声明训练数据集合,这里自然就是mat,60000行,784列
Mat trainData = new Mat(train_sample_count, 28 * 28, MatType.CV_32FC1);
//声明测试数据集合,这里自然就是mat,10000行,784列
Mat testData = new Mat(test_sample_count, 28 * 28, MatType.CV_32FC1);
//声明训练数据标签,这里自然就是mat,60000行,1列
Mat trainLabel = new Mat(train_sample_count, 1, MatType.CV_32FC1);
//声明测试数据标签,这里自然就是mat,10000行,1列
Mat testLabel = new Mat(test_sample_count, 1, MatType.CV_32FC1);

string trainPath = "D:\mnist\mnist_png_full\training";
string testPath = "D:\mnist\mnist_png_full\testing";

//组织训练数据,循环训练文件夹内所有图片
int trainNums = 0;
for(int i=0; i<10; i++)
{
    string path = trainPath + "\" + i;
    DirectoryInfo TheFolder = new DirectoryInfo(path);
    foreach (FileInfo NextFile in TheFolder.GetFiles())
    {
        //读入单通道灰度图
        Mat temp = new Mat(NextFile.FullName, ImreadModes.Grayscale);
        //转32fc1,因为下面训练函数需要这个格式
        temp.ConvertTo(temp, MatType.CV_32FC1);
        //写入到训练数据集合的mat内,注意reshape的用法
        temp.Reshape(0,1).CopyTo(trainData.Row(trainNums));
        //写入到训练标签集合的mat内
        trainLabel.Set(trainNums, i);
        trainNums++;
    } 
}


//组织测试数据
int testNums = 0;
for (int i = 0; i < 10; i++)
{
    string path = testPath + "\" + i;
    DirectoryInfo TheFolder = new DirectoryInfo(path);
    foreach (FileInfo NextFile in TheFolder.GetFiles())
    {
        Mat temp = new Mat(NextFile.FullName, ImreadModes.Grayscale);
        temp.ConvertTo(temp, MatType.CV_32FC1);
        temp.Reshape(0, 1).CopyTo(testData.Row(testNums));
        testLabel.Set(testNums, i);
        testNums++;
    }
}


//创建名为knn的模型
OpenCvSharp.ML.KNearest knn = OpenCvSharp.ML.KNearest.Create();
//这里的k,可以自行更改进行测试
int k = 5;
knn.DefaultK = k;
knn.IsClassifier = true;
knn.AlgorithmType = OpenCvSharp.ML.KNearest.Types.BruteForce;

//训练
knn.Train(trainData, OpenCvSharp.ML.SampleTypes.RowSample, trainLabel);
            
//测试
Mat result = new Mat(test_sample_count, 1, MatType.CV_32FC1);
knn.FindNearest(testData, k, result);
int t = 0;
int f = 0;
for(int i=0; i(i);
    int actual = (int)testLabel.At(i);

    if(predict == actual)
    {
        Console.WriteLine("正确:" + predict + "-" + actual);
        t++;
    }
    else
    {
        Console.WriteLine("错误------:" + predict + "-" + actual);
        f++;
    }
}

double accuracy = (t * 1.0) / (t + f);
System.Console.WriteLine("准确率:" + accuracy);

        虽然图片小,但是数量也不少,所以需要运行一会,运行之后,可以看到k==5的时候,准确率是0.9688,可以自行更改k的值,进行测试。 

正确:9-9
错误------:7-9
正确:9-9

......
正确:9-9
错误------:4-9
正确:9-9
正确:9-9

......
正确:9-9
正确:9-9
准确率:0.9688

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5651988.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-16
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存