一、训练模型

using OpenCvSharp;
using OpenCvSharp.Extensions;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.IO;
namespace app
{
public partial class FrmMain : Form
{
public FrmMain()
{
InitializeComponent();
}
/// <summary>
/// knn手写数字识别
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnKNN_Click(object sender, EventArgs e)
{
// 训练数据数量
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 = @"img\mnist\train_images";
string testPath = @"img\mnist\test_images";
// 组织训练数据,循环训练文件夹内所有图片。
int trainNum = 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);
// 转换CV_32FC1,因为下面训练函数需要这个格式
temp.ConvertTo(temp, MatType.CV_32FC1);
// 写入到训练数据集合的mat内,注意reshape的用法。
/*
reshape有两个参数:
其中,参数:cn为新的通道数,如果cn = 0,表示通道数不会改变。
参数rows为新的行数,如果rows = 0,表示行数不会改变。
注意:新的行* 列必须与原来的行*列相等。就是说,如果原来是5行3列,新的行和列可以是1行15列,3行5列,5行3列,15行1列。
设置行数后,列数会自动调整。比如此处 调整为 1行784列。
*/
temp.Reshape(0, 1).CopyTo(trainData.Row(trainNum));
// 写入到训练标签集合的mat内
trainLabel.Set<float>(trainNum, i);
trainNum++;
}
}
// 组织测试数据
int testNum = 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(testNum));
testLabel.Set<float>(testNum, i);
testNum++;
}
}
// 创建knn模型
OpenCvSharp.ML.KNearest knn = OpenCvSharp.ML.KNearest.Create();
// k 可以根据需要自行调整
int k = 3;
// 设置K值
knn.DefaultK = k;
// 设置KNN是进行分类还是回归
knn.IsClassifier = true;
// 设置算法类型 BruteForce 或 KdTree
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 < test_sample_count; i++)
{
int predict = (int)result.At<float>(i);
int actual = (int)testLabel.At<float>(i);
if (predict == actual)
{
System.Console.WriteLine("正确:" + predict + "-" + actual);
t++;
}
else
{
System.Console.WriteLine("错误------:" + predict + "-" + actual);
f++;
}
}
double accuracy = (t * 1.0) / (t + f);
System.Console.WriteLine("准确率:" + accuracy);
}
}
}
二、保存模型
训练完成之后,可以用knn.Save()保存模型文件。
// 训练 knn.Train(trainData, OpenCvSharp.ML.SampleTypes.RowSample, trainLabel); // 保存模型 knn.Save(@"D:\opencv\mnist\knn_digits_model2023.yml");
三、加载模型 【2023-03-18 更新】
近期咨询模型加载的同学比较多,因此增加了示范代码。测试数据可以另外指定数据。
关于图片目录 0、1、2、3、4、5、6、7、8、9,是为了分析预测值与真实值是否一致,即预测的准确性。
模型训练完成之后,可以指定任意文件夹的图片进行预测(也可以自定义手写数字,根据需要修改示范代码)。
/// <summary>
/// 测试
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnKNNTest_Click(object sender, EventArgs e)
{
// 测试数据数量
int test_sample_count = 10000;
// 声明测试数据集合 mat,10000行,784列
Mat testData = new Mat(test_sample_count, 28 * 28, MatType.CV_32FC1);
// 声明测试数据标签 mat,10000行,1列
Mat testLabel = new Mat(test_sample_count, 1, MatType.CV_32FC1);
string testPath = @"D:\opencv\mnist\test_images";
// 创建knn模型
OpenCvSharp.ML.KNearest knn = OpenCvSharp.ML.KNearest.Load(@"D:\opencv\mnist\knn_digits_model2023.yml");
// 组织测试数据
int testNum = 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(testNum));
testLabel.Set<float>(testNum, i);
testNum++;
}
}
int k = 3;
// 测试
Mat result = new Mat(test_sample_count, 1, MatType.CV_32FC1);
// 在训练样本中寻找最接近待分类样本的前K个样本,并返回K个样本中数量最多类型的标签。
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<float>(i);
int actual = (int)testLabel.At<float>(i);
if (predict == actual)
{
System.Console.WriteLine("正确:" + predict + "-" + actual);
t++;
}
else
{
System.Console.WriteLine("错误------:" + predict + "-" + actual);
f++;
}
}
double accuracy = (t * 1.0) / (t + f);
System.Console.WriteLine("准确率:" + accuracy);
}
请问训练结果保存在哪里
训练完成之后,可以用knn.Save()保存模型文件。
// 训练
knn.Train(trainData, OpenCvSharp.ML.SampleTypes.RowSample, trainLabel);
// 保存模型
knn.Save(@”img\knn_digits_model.yml”);
能不能分享一下训练好的模型文件呀
参考
c++ OpenCV knn 手写数字识别 模型保存及加载
http://chanpinxue.cn/archives/5511.html
里面从训练、模型保存、模型加载的整个过程写的相对全面。
c++ 虽然与 c# 语法不一样,但思路是一样的。
另外模型的准确性,跟训练参数、训练图片库很有关系的。别人给的模型,不一定有用。
谢谢指导
有一处不太理解,看代码待测试数据也分为0-9存放于文件夹里,这样的话,待检测内容是不是已经假定已知了,如果存放0的文件夹里放上数字1的图片,那识别率不就为0了?
1、训练、测试数据,是为了分析 模型的准确率。
2、实际模型训练完成之后,保存模型。调用模型,再用实际的待检测数据进行检测。
我想问下,如果多次训练,叠加到模型文件中,譬如,第一次用100个图片训练,然后再用新的10个图片训练,保存的两个模型文件怎么叠加呢。
其中一种思路:加载第一次训练完的模型,继续用新的数据训练,生成新的模型。
我想要的效果是一张图一张图去训练,场景是先识别,识别不出或不准时再加载模型文件后再独立训练当前的图片,希望大佬指导下实现方式,谢谢!!
建议查阅一下Yolov5相关的资料。
请教一下,// 声明训练数据集合 mat,60000行,784列,这里的行列和训练图片的数量有什么关系呢?
1、60000行 指的是 60000张 图片。
2、784列 指的是 单张图片【行(28)* 列(28)】,reshape 为 1行 784列。