c# OpenCvSharp knn 手写数字识别

c# OpenCvSharp knn 手写数字识别

一、训练模型

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);

    }

 

13条评论

    1. 训练完成之后,可以用knn.Save()保存模型文件。
      // 训练
      knn.Train(trainData, OpenCvSharp.ML.SampleTypes.RowSample, trainLabel);
      // 保存模型
      knn.Save(@”img\knn_digits_model.yml”);

      蒋智昊
  1. 有一处不太理解,看代码待测试数据也分为0-9存放于文件夹里,这样的话,待检测内容是不是已经假定已知了,如果存放0的文件夹里放上数字1的图片,那识别率不就为0了?

    老妖
    1. 1、训练、测试数据,是为了分析 模型的准确率。
      2、实际模型训练完成之后,保存模型。调用模型,再用实际的待检测数据进行检测。

      蒋智昊
  2. 我想问下,如果多次训练,叠加到模型文件中,譬如,第一次用100个图片训练,然后再用新的10个图片训练,保存的两个模型文件怎么叠加呢。

    朱恒
      1. 我想要的效果是一张图一张图去训练,场景是先识别,识别不出或不准时再加载模型文件后再独立训练当前的图片,希望大佬指导下实现方式,谢谢!!

        朱恒

回复 蒋智昊 取消回复

您的电子邮箱地址不会被公开。