opencv 3.4.16的四种训练方式: KNN(最近邻算法)、SVM(支持向量机)、BP(反向传播)、Bayes(正态贝叶斯)

发布时间:2024-07-10 15:23:35 作者:yexindonglai@163.com 阅读(376)

1、前言

本次训练图为opencv自带的数字图,在opencv目录下的路径:opencv\sources\samples\data\digits.png;

2、最近邻居/k-近邻算法 (K-Nearest Neighbors,KNN)

KNN算法是一种基于实例的学习,或者是局部近似和将所有计算推迟到分类之后的惰性学习。用最近的邻居(k)来预测未知数据点。k 值是预测精度的一个关键因素,无论是分类还是回归,衡量邻居的权重都非常有用,较近邻居的权重比较远邻居的权重大。

KNN 算法的缺点是对数据的局部结构非常敏感。计算量大,需要对数据进行规范化处理,使每个数据点都在相同的范围。

新建文件:KNN.h

  1. //
  2. // Created by W9033927 on 2024/7/10.
  3. //
  4. #ifndef FACE_RECOGNITION_KNN_H
  5. #define FACE_RECOGNITION_KNN_H
  6. #include <iostream>
  7. #include<opencv2/opencv.hpp>
  8. #include <random>
  9. #include <dirent.h>
  10. #include <sys/stat.h>
  11. using namespace std;
  12. using namespace cv;
  13. /**
  14. * 训练CNN(K最近邻)模型, 代码来源: https://www.cnblogs.com/denny402/p/5033898.html
  15. * @param numberPng 图片路径
  16. * @param kNNModelXmlPath 模型保存路径
  17. */
  18. void trainKNNModel(const string & numberPng,const string & kNNModelXmlPath) {
  19. Mat img = imread(numberPng);
  20. Mat gray;
  21. // 转为灰度图(黑白图)
  22. cvtColor(img, gray, CV_BGR2GRAY);
  23. int b = 20;
  24. int m = gray.rows / b; //原图为1000*2000
  25. int n = gray.cols / b; //裁剪为5000个20*20的小图块
  26. Mat data, labels; //特征矩阵
  27. for (int i = 0; i < n; i++) {
  28. // 按列截取
  29. int offsetCol = i * b; //列上的偏移量
  30. for (int j = 0; j < m; j++) {
  31. int offsetRow = j * b; //行上的偏移量
  32. //截取20*20的小块
  33. Mat tmp;
  34. gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
  35. // imshow("转换前",tmp);
  36. // waitKey(0);
  37. //序列化,改变行数,将20*20的居中转为 1 * 400, 序列化后放入特征矩阵
  38. const Mat &reshapeMat = tmp.reshape(0, 1);
  39. // imshow("转换后",reshapeMat);
  40. // cout << "原行数:" << tmp.rows<< ", 原列数:"<<tmp.cols<<", 原通道数:"<< tmp.channels()<<endl;
  41. // cout << "转换后行数:" << reshapeMat.rows<< ", 转换后列数:"<<reshapeMat.cols<<", 转换后通道数:"<< reshapeMat.channels()<<endl;
  42. // waitKey(0);
  43. data.push_back(reshapeMat);
  44. labels.push_back((int) j / 5); //对应的标注, 就是图片中的数字对应的值
  45. // cout << "label :" << (int) j / 5 << endl;
  46. }
  47. }
  48. data.convertTo(data, CV_32F); //uchar型转换为cv_32f
  49. int samplesNum = data.rows;
  50. //前3000个样本为训练数据
  51. int trainNum = 3000;
  52. Mat trainData, trainLabels;
  53. // 将前3000个样本提取出来
  54. trainData = data(Range(0, trainNum), Range::all());
  55. // 标注,就是数字值
  56. trainLabels = labels(Range(0, trainNum), Range::all());
  57. //使用KNN算法训练
  58. int K = 5;
  59. Ptr<cv::ml::TrainData> tData = cv::ml::TrainData::create(trainData, cv::ml::ROW_SAMPLE, trainLabels);
  60. Ptr<cv::ml::KNearest> model = cv::ml::KNearest::create();
  61. model->setDefaultK(K);
  62. model->setIsClassifier(true);
  63. cout << trainData.size<<endl;
  64. cout << trainLabels.size<<endl;
  65. imshow("1",trainData);
  66. waitKey(0);
  67. // 开始训练
  68. model->train(tData);
  69. // 保存模型
  70. model->save(kNNModelXmlPath);
  71. //预测分类
  72. double train_hr = 0, test_hr = 0;
  73. Mat response;
  74. //计算训练和测试数据的预测误差
  75. for (int i = 0; i < samplesNum; i++) {
  76. Mat sample = data.row(i);
  77. float r = model->predict(sample); //对所有行进行预测
  78. //预测结果与原结果相比,相等为1,不等为0
  79. // cout << "predict result: " << r << ", " << std::abs(r - labels.at<int>(i)) << " <= " << FLT_EPSILON << endl;
  80. r = std::abs(r - labels.at<int>(i)) <= FLT_EPSILON ? 1.f : 0.f;
  81. if (i < trainNum)
  82. train_hr += r; //累积正确数
  83. else
  84. test_hr += r;
  85. }
  86. test_hr /= samplesNum - trainNum;
  87. train_hr = trainNum > 0 ? train_hr / trainNum : 1.;
  88. printf("accuracy: train = %.1f%%, test = %.1f%%\n",
  89. train_hr * 100., test_hr * 100.);
  90. }
  91. /**
  92. * 使用训练后的KNN模型进行预测
  93. * @param numberPng 数字图片路径
  94. * @param kNNModelXmlPath 模型路径
  95. */
  96. void predictionKNN(const string & numberPng,const string &kNNModelXmlPath) {
  97. Mat img = imread(numberPng);
  98. Mat gray;
  99. // 转为灰度图(黑白图)
  100. cvtColor(img, gray, CV_BGR2GRAY);
  101. int b = 20;
  102. int m = gray.rows / b; //原图为1000*2000
  103. int n = gray.cols / b; //裁剪为5000个20*20的小图块
  104. Mat data; //特征矩阵
  105. for (int i = 0; i < n; i++) {
  106. // 按列截取
  107. int offsetCol = i * b; //列上的偏移量
  108. for (int j = 0; j < m; j++) {
  109. int offsetRow = j * b; //行上的偏移量
  110. //截取20*20的小块
  111. Mat tmp;
  112. gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
  113. //序列化,改变行数,将20*20的图片转为 1 * 400, 序列化后放入特征矩阵
  114. const Mat &reshapeMat = tmp.reshape(0, 1);
  115. data.push_back(reshapeMat);
  116. }
  117. }
  118. data.convertTo(data, CV_32F); //uchar型转换为cv_32f
  119. // 上面是使用knn算法进行训练的,预测也用knn
  120. int k = 5;
  121. cv::Ptr<cv::ml::KNearest> model = Algorithm::load<cv::ml::KNearest>(kNNModelXmlPath);
  122. // data.total()返回的总像素长度, 因为每张图片是20 * 20 像素, 就是400像素,
  123. for (int i = 0; i < data.rows; ++i) {
  124. const Mat &mat = data.row(i);
  125. // cout << "width: "<< mat.cols << "height: "<< mat.rows<<endl;
  126. Mat result;
  127. // float d = model->predict(mat,result);
  128. float d = model->findNearest(mat,k,result);
  129. cout << "检测到的数字数量: " << d << endl;
  130. }
  131. }
  132. #endif //FACE_RECOGNITION_KNN_H

2、支持向量机算法(Support Vector Machine,SVM)

支持向量机是一种有监督学习算法,通过找到能够将不同类别的数据点最大化分隔的决策边界来实现分类。在图像分类中,SVM可以用于训练分类器,将图像分为不同的类别。

支持向量机/网络算法(SVM)属于分类型算法。SVM模型将实例表示为空间中的点,将使用一条直线分隔数据点。需要注意的是,支持向量机需要对输入数据进行完全标记,仅直接适用于两类任务,应用将多类任务需要减少到几个二元问题。

新建文件SVM.h

  1. //
  2. // Created by W9033927 on 2024/7/10.
  3. //
  4. #ifndef TRAIN_CLASS_SVM_H
  5. #define TRAIN_CLASS_SVM_H
  6. #include <iostream>
  7. #include<opencv2/opencv.hpp>
  8. #include <random>
  9. #include <dirent.h>
  10. #include <sys/stat.h>
  11. using namespace std;
  12. using namespace cv;
  13. /**
  14. * 训练svm(支持向量机)
  15. * @param numberPng 数字图片路径
  16. * @param SVMModelXmlPath 保存的模型路径
  17. */
  18. void trainSVM(const string &numberPng,const string &SVMModelXmlPath) {
  19. Mat img = imread(numberPng);
  20. Mat gray;
  21. // 转为灰度图(黑白图)
  22. cvtColor(img, gray, CV_BGR2GRAY);
  23. int b = 20;
  24. int m = gray.rows / b; //原图为1000*2000
  25. int n = gray.cols / b; //裁剪为5000个20*20的小图块
  26. Mat data, labels; //特征矩阵
  27. for (int i = 0; i < n; i++) {
  28. // 按列截取
  29. int offsetCol = i * b; //列上的偏移量
  30. for (int j = 0; j < m; j++) {
  31. int offsetRow = j * b; //行上的偏移量
  32. //截取20*20的小块
  33. Mat tmp;
  34. gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
  35. //序列化,改变行数,将20*20的居中转为 1 * 400, 序列化后放入特征矩阵
  36. const Mat &reshapeMat = tmp.reshape(0, 1);
  37. data.push_back(reshapeMat);
  38. labels.push_back((int) j / 5); //对应的标注, 就是图片中的数字对应的值
  39. cout << "label :" << (int) j / 5 << endl;
  40. }
  41. }
  42. data.convertTo(data, CV_32F); //uchar型转换为cv_32f
  43. int samplesNum = data.rows;
  44. //前3000个样本为训练数据
  45. int trainNum = 3000;
  46. Mat trainData, trainLabels;
  47. // 将前3000个样本提取出来
  48. trainData = data(Range(0, trainNum), Range::all());
  49. // 标注,就是数字值
  50. trainLabels = labels(Range(0, trainNum), Range::all());
  51. Ptr<cv::ml::SVM> model = cv::ml::SVM::create();
  52. // 设置 SVM 的类型为 C-Support Vector Classification (C-SVC)。这是一种常用的 SVM 算法,用于处理二分类问题。
  53. model->setType(cv::ml::SVM::C_SVC);
  54. // 设置 SVM 使用线性核函数
  55. model->setKernel(cv::ml::SVM::LINEAR);
  56. /**
  57. * 设置 SVM 的迭代停止条件:
  58. - 最大迭代次数为 10000
  59. - 精度阈值为 0.001
  60. 当满足这两个条件之一时,SVM 的训练过程将会停止。
  61. */
  62. model->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 10000, 0.001));
  63. // 开始训练
  64. // cv::ml::ROW_SAMPLE: 这是一个常量,表示训练数据集中的每一行代表一个样本;trainData中每个元素就表示一行
  65. model->train(trainData, cv::ml::ROW_SAMPLE, trainLabels);
  66. // 保存模型
  67. model->save(SVMModelXmlPath);
  68. //预测分类
  69. double train_hr = 0, test_hr = 0;
  70. Mat response;
  71. //计算训练和测试数据的预测误差
  72. for (int i = 0; i < samplesNum; i++) {
  73. Mat sample = data.row(i);
  74. float r = model->predict(sample); //对所有行进行预测
  75. //预测结果与原结果相比,相等为1,不等为0
  76. cout << "predict result: " << r << ", " << std::abs(r - labels.at<int>(i)) << " <= " << FLT_EPSILON << endl;
  77. r = std::abs(r - labels.at<int>(i)) <= FLT_EPSILON ? 1.f : 0.f;
  78. if (i < trainNum)
  79. train_hr += r; //累积正确数
  80. else
  81. test_hr += r;
  82. }
  83. test_hr /= samplesNum - trainNum;
  84. train_hr = trainNum > 0 ? train_hr / trainNum : 1.;
  85. printf("accuracy: train = %.1f%%, test = %.1f%%\n",
  86. train_hr * 100., test_hr * 100.);
  87. }
  88. /**
  89. * 使用训练后的SVM模型进行预测
  90. * @param numberPng 数字图片路径
  91. * @param kNNModelXmlPath 模型路径
  92. */
  93. void predictionSVM(const string & numberPng,const string & SVMModelXmlPath) {
  94. Mat img = imread(numberPng);
  95. Mat gray;
  96. // 转为灰度图(黑白图)
  97. cvtColor(img, gray, CV_BGR2GRAY);
  98. int b = 20;
  99. int m = gray.rows / b; //原图为1000*2000
  100. int n = gray.cols / b; //裁剪为5000个20*20的小图块
  101. Mat data; //特征矩阵
  102. for (int i = 0; i < n; i++) {
  103. // 按列截取
  104. int offsetCol = i * b; //列上的偏移量
  105. for (int j = 0; j < m; j++) {
  106. int offsetRow = j * b; //行上的偏移量
  107. //截取20*20的小块
  108. Mat tmp;
  109. gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
  110. //序列化,改变行数,将20*20的图片转为 1 * 400, 序列化后放入特征矩阵
  111. const Mat &reshapeMat = tmp.reshape(0, 1);
  112. data.push_back(reshapeMat);
  113. }
  114. }
  115. data.convertTo(data, CV_32F); //uchar型转换为cv_32f
  116. // 上面是使用SVM算法进行训练的,预测也用SVM
  117. cv::Ptr<cv::ml::SVM> model = Algorithm::load<cv::ml::SVM>(SVMModelXmlPath);
  118. // 遍历数组
  119. for (int i = 0; i < data.rows; ++i) {
  120. const Mat &mat = data.row(i);
  121. Mat result;
  122. float d = model->predict(mat,result);
  123. cout << result<<endl;
  124. cout << "检测到的数字数量: " << d << endl;
  125. }
  126. }
  127. #endif //TRAIN_CLASS_SVM_H

3、BP(反向传播)

反向传播(BP)

反向传播是一种有监督学习算法,通过不断调整神经网络的权重和偏置项来最小化输出层和目标值之间的误差。在图像分类中,BP神经网络可以用于训练分类器,将图像分为不同的类别。

新建文件:BP.h

  1. //
  2. // Created by W9033927 on 2024/7/10.
  3. //
  4. #ifndef TRAIN_CLASS_BP_H
  5. #define TRAIN_CLASS_BP_H
  6. #include <iostream>
  7. #include<opencv2/opencv.hpp>
  8. #include <random>
  9. #include <dirent.h>
  10. #include <sys/stat.h>
  11. #include "Common.h"
  12. using namespace std;
  13. using namespace cv;
  14. /**
  15. * BP神经网络,训练部分
  16. * @param numberPng 数字图
  17. * @param BPModelXmlPath 模型保存地址
  18. */
  19. void trainBPModel(const string &numberPng, const string &BPModelXmlPath) {
  20. Mat sampleTmp;// = Mat::zeros(5000, 400, CV_32FC1);
  21. Mat labelTmp;// = Mat::zeros(5000, 1, CV_32FC1);
  22. getImgs(numberPng, sampleTmp, labelTmp);
  23. sampleTmp.convertTo(sampleTmp, CV_32FC1); //uchar型转换为 CV_32FC1
  24. // 单张图的尺寸为20 * 20 ,所以输入神经元个数为400
  25. int samcol = 20 * 20;
  26. // 将标签数据改为one-hot型,因为总归训练的数字0-9之间有10个数字,所以第二个参数为10
  27. labelTmp = one_hot(labelTmp, 10);
  28. // ANN_MLP 是 OpenCV 中的一个机器学习类, 称为:(人工神经网络-多层感知机),它实现了前馈神经网络的训练和预测功能
  29. Ptr<ml::ANN_MLP> bp = ml::ANN_MLP::create();
  30. // 以下表示神经网络的层数为3,输入层神经元个数为 samcol个,隐含层为64,输出层为10(因为有10个数字)
  31. Mat layers_size = (Mat_<int>(1, 3) << samcol, 64, 10); // samcol维点,10维输出
  32. // 设置神经网络的层数和神经元数量
  33. bp->setLayerSizes(layers_size);
  34. // ml::ANN_MLP::BACKPROP 是 ANN_MLP 类中的一个常量, 它表示采用反向传播算法(Back-Propagation)来训练前馈神经网络。
  35. // 第一个 0.1 表示学习速率,学习速率通常取值在 0 到 1 之间,较小的值能够确保算法稳定收敛,但收敛速度会较慢。较大的值可以加快收敛,但可能会导致算法发散。
  36. // 第二个 0.1 表示动量系数, 动量系数用于改善标准反向传播算法的收敛特性。动量系数控制了当前梯度与上一次梯度的相对权重,取值范围也在 0 到 1 之间, 适当的动量系数可以加快算法收敛,平滑权重更新,并防止算法陷入局部极小值。
  37. bp->setTrainMethod(ml::ANN_MLP::BACKPROP, 0.001, 0.1);
  38. // 设置激活函数为SIGMOID(逻辑斯蒂)
  39. bp->setActivationFunction(ml::ANN_MLP::SIGMOID_SYM, 1.0, 1.0);
  40. // 设置训练终止条件为最大迭代次数 1000 次,或误差小于 1e-6,即 0.000001
  41. // TermCriteria::MAX_ITER 表示使用最大迭代次数作为终止条件。
  42. bp->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER | TermCriteria::EPS, 100, 0.0001));
  43. // 使用训练样本 sample 和标签 labelsMat 进行 BP 神经网络的训练
  44. // cout << sampleTmp.size<<endl;
  45. // cout << labelTmp.size<<endl;
  46. bp->train(sampleTmp, ml::ROW_SAMPLE, labelTmp);
  47. std::cout << "训练完成" << std::endl;
  48. bp->save(BPModelXmlPath);
  49. std::cout << "保存完毕" << std::endl;
  50. }
  51. //BP神经网络,测试部分
  52. void testBP(const string &numberPng, const string &BPModelXmlPath) {
  53. Mat sampleTmp;// = Mat::zeros(5000, 400, CV_32FC1);
  54. Mat labelTmp;// = Mat::zeros(5000, 1, CV_32FC1);
  55. // imshow("q", sampleTmp);
  56. // waitKey(0);
  57. getImgs(numberPng, sampleTmp, labelTmp);
  58. // imshow("q1", sampleTmp);
  59. // waitKey(0);
  60. sampleTmp.convertTo(sampleTmp, CV_32FC1);
  61. // labelTmp.convertTo(labelTmp, CV_32FC1);
  62. // 将标签数据改为one-hot型,因为总归训练的数字0-9之间有10个数字,所以第二个参数为10
  63. // labelTmp = one_hot(labelTmp,10);
  64. // imshow("q1", sampleTmp);
  65. // waitKey(0);
  66. // imshow("q2", labelTmp);
  67. // waitKey(0);
  68. Mat responseMat;
  69. Ptr<ml::ANN_MLP> bp = ml::ANN_MLP::load(BPModelXmlPath);
  70. // double foldAccuracy0, foldAccuracy1, True0 = 0.0, True1 = 0.0;
  71. int testResNum = 0;
  72. // *******************单个识别*******************
  73. for (int i = 0; i < sampleTmp.rows; ++i) {
  74. const Mat &e = sampleTmp.row(i);
  75. bp->predict(e, responseMat);
  76. double maxVal = 0;
  77. cv::Point maxPoint;
  78. cv::minMaxLoc(responseMat, NULL, &maxVal, NULL, &maxPoint);
  79. int max_index = maxPoint.x;
  80. int test_index = labelTmp.row(i).at<int32_t>(0, 0);
  81. cout << "识别结果:" << max_index << ", 真实值:" << test_index << endl;
  82. if(max_index == test_index) testResNum ++;
  83. }
  84. cout << "测试成功的数量:"<< testResNum <<", 成功率:"<< ((double)testResNum/(double)sampleTmp.rows*100)<<endl;
  85. // *******************批量识别*****************
  86. // bp->predict(sampleTmp, responseMat);
  87. // for (int i = 0; i < responseMat.rows; i++) {
  88. // // 获取每一个结果的最大值所在下标
  89. // Mat temp = responseMat.rowRange(i, i + 1);
  90. // double maxVal = 0;
  91. // cv::Point maxPoint;
  92. // cv::minMaxLoc(temp, NULL, &maxVal, NULL, &maxPoint);
  93. // int max_index = maxPoint.x;
  94. // int test_index = labelTmp.at<int32_t>(i, 0);
  95. // cout << "识别结果:" << max_index <<", 真实值:"<<test_index<< endl;
  96. //
  97. // }
  98. }
  99. #endif //TRAIN_CLASS_BP_H

4、Bayes(正态贝叶斯)

贝叶斯分类器是一种概率模型,利用贝叶斯公式来解决分类问题。假设样本的特征向量服从一定的概率分布,我们就可以计算出该特征向量属于各个类的条件概率。分类结果是条件概率最大的分类结果。如果假设特征向量的每个分量彼此独立,则它是朴素贝叶斯分类器。如果假设特征向量服从多维正态分布,则它是正态贝叶斯分类器。

新建文件Bayes.h

  1. //
  2. // Created by W9033927 on 2024/7/10.
  3. //
  4. #ifndef TRAIN_CLASS_BAYES_H
  5. #define TRAIN_CLASS_BAYES_H
  6. #include <iostream>
  7. #include<opencv2/opencv.hpp>
  8. #include <random>
  9. #include <dirent.h>
  10. #include <sys/stat.h>
  11. #include "Common.h"
  12. using namespace std;
  13. using namespace cv;
  14. //正态贝叶斯分类器,训练部分
  15. void trainBayes(const string &imgPath,const string &modelXmlPath) {
  16. Mat samples;
  17. Mat labels;
  18. getImgs(imgPath, samples, labels);
  19. samples.convertTo(samples, CV_32FC1); //uchar型转换为 CV_32FC1
  20. labels.convertTo(labels, CV_32SC1); //uchar型转换为 CV_32FC1
  21. Ptr<cv::ml::NormalBayesClassifier> model = cv::ml::NormalBayesClassifier::create();
  22. //创建TrainData并进行分类器的训练
  23. Ptr<cv::ml::TrainData> tData = cv::ml::TrainData::create(samples, cv::ml::ROW_SAMPLE, labels);
  24. cout << "开始训练"<<endl;
  25. model->train(tData);
  26. std::cout << "训练完成" << std::endl;
  27. model->save(modelXmlPath);
  28. std::cout << "保存完毕" << std::endl;
  29. }
  30. //正态贝叶斯分类器,测试部分
  31. void testBayes(const string &imgPath,const string &modelXmlPath) {
  32. Mat samples; Mat labels;
  33. getImgs(imgPath,samples,labels);
  34. samples.convertTo(samples, CV_32FC1); //uchar型转换为 CV_32FC1
  35. labels.convertTo(labels, CV_32SC1);
  36. Mat responseMat;
  37. Ptr<ml::NormalBayesClassifier> model = ml::NormalBayesClassifier::load(modelXmlPath);
  38. double foldAccuracy0, foldAccuracy1, True0 = 0.0, True1 = 0.0;
  39. for (int i = 0; i < samples.rows; ++i) {
  40. Mat e = samples.row(i);
  41. float r = model->predict(e);
  42. cout << "预测结果:"<<r<<", 实际结果:"<<labels.at<int>(i, 0)<<endl;
  43. if (r == labels.at<int>(i, 0)) {
  44. True0++;
  45. }
  46. }
  47. foldAccuracy0 = True0 / samples.rows;
  48. std::cout << "训练数据正确率: " << foldAccuracy0 * 100<<"%"<< std::endl;
  49. }
  50. #endif //TRAIN_CLASS_BAYES_H

5、公共头文件 Common.h

  1. //
  2. // Created by W9033927 on 2024/7/12.
  3. //
  4. #ifndef TRAIN_CLASS_COMMON_H
  5. #define TRAIN_CLASS_COMMON_H
  6. #include <iostream>
  7. #include<opencv2/opencv.hpp>
  8. #include <random>
  9. #include <dirent.h>
  10. #include <sys/stat.h>
  11. using namespace std;
  12. using namespace cv;
  13. /**
  14. * 获取训练数据
  15. * @param numberPng 数字图地址
  16. * @param data 训练样本数据矩阵 ,每一行对应一个训练样本
  17. * @param labels 训练样本的标签矩阵 ,每一行对应一个样本的标签信息
  18. */
  19. void getImgs(const string &numberPng, Mat &sampleTmp, Mat &labelTmp) {
  20. Mat img = imread(numberPng);
  21. Mat gray;
  22. // 转为灰度图(黑白图)
  23. cvtColor(img, gray, CV_BGR2GRAY);
  24. int b = 20;
  25. int m = gray.rows / b; //原图为1000*2000
  26. int n = gray.cols / b; //裁剪为5000个20*20的小图块
  27. for (int i = 0; i < n; i++) {
  28. // 按列截取
  29. int offsetCol = i * b; //列上的偏移量
  30. for (int j = 0; j < m; j++) {
  31. int offsetRow = j * b; //行上的偏移量
  32. Mat tmp;
  33. //截取20*20的小块, 放到 tmp中
  34. gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp);
  35. // 二值化
  36. threshold(tmp, tmp, 50, 255, THRESH_BINARY);
  37. // 归一化
  38. tmp = tmp / 255.0;
  39. //序列化,改变行数,将20*20的居中转为 1 * 400, 序列化后放入特征矩阵
  40. const Mat &reshapeMat = tmp.reshape(0, 1);
  41. Mat m = Mat::zeros(1,400,CV_32FC1);
  42. reshapeMat.row(0).copyTo(m.row(0));
  43. // 加入特征矩阵
  44. sampleTmp.push_back(m);
  45. //对应的标注, 就是图片中的数字对应的值
  46. labelTmp.push_back((int) (j / 5));
  47. }
  48. }
  49. // imshow("22",sampleTmp);
  50. // waitKey(0);
  51. }
  52. //将标签数据改为one-hot型
  53. Mat one_hot(Mat label, int classes_num) {
  54. /**例如
  55. * [2]->[0 1 0 0 0 0 0 0 0 0]
  56. * [4]->[0 0 0 1 0 0 0 0 0 0]
  57. */
  58. int rows = label.rows;
  59. // imshow("33",label);
  60. // waitKey(0);
  61. Mat one_hot = Mat::zeros(rows, classes_num, CV_32FC1);
  62. for (int i = 0; i < label.rows; i++) {
  63. int index = label.at<int32_t>(i);
  64. // 将第i行的第index列设为1,
  65. one_hot.at<float>(i, index) = 1.0;
  66. // cout << "one_hot" << one_hot.at<float>(i, index) << endl;
  67. }
  68. return one_hot;
  69. }
  70. #endif //TRAIN_CLASS_COMMON_H

6、启动函数 和 cmake

main.cpp

  1. #include <iostream>
  2. #include "SVM.h"
  3. #include "BP.h"
  4. #include "KNN.h"
  5. #include "Bayes.h"
  6. // 获取分类器路径
  7. string getStaticPath(){
  8. // 获取当前工程目录
  9. string path(PRO_ROOT_DIR);
  10. // 找到上一级目录
  11. size_t i = path.find_last_of('/');
  12. path = path.substr(0, i);
  13. // 拼接static路径
  14. path.append("/static");
  15. return path;
  16. }
  17. int main() {
  18. string staticPath = getStaticPath();
  19. string numberPng = staticPath + "/img/digits.png";
  20. // KNN训练后的模型
  21. const string &kNNModelXmlPath = staticPath + "/train/KNN_number.xml";
  22. const string &SVMModelXmlPath = staticPath + "/train/SVM_number.xml";
  23. const string &BPModelXmlPath = staticPath + "/train/BP_number.xml";
  24. const string &BayesModelXmlPath = staticPath + "/train/Bayes_number.xml";
  25. // 训练KNN
  26. // trainKNNModel(numberPng,kNNModelXmlPath);
  27. // // 使用训练后的KNN模型进行预测
  28. // predictionKNN(numberPng,kNNModelXmlPath);
  29. // 训练SVM
  30. // trainSVM(numberPng,SVMModelXmlPath);
  31. // // 使用训练后的SVM模型进行预测
  32. // predictionSVM(numberPng,SVMModelXmlPath);
  33. // // 训练BP神经网络
  34. // trainBPModel(numberPng,BPModelXmlPath);
  35. // // 测试BP神经网络
  36. // testBP(numberPng,BPModelXmlPath);
  37. // // 训练正态贝叶斯
  38. trainBayes(numberPng,BayesModelXmlPath);
  39. // 测试正态贝叶斯
  40. testBayes(numberPng,BayesModelXmlPath);
  41. }

因为是cmake项目,需要 CMakeLists.txt

  1. cmake_minimum_required(VERSION 3.27)
  2. project(train_class)
  3. set(CMAKE_CXX_STANDARD 11)
  4. # 设置OpenCV的路径(根据你自己的安装路径进行更改)
  5. set(OpenCV_DIR "D:\\cpp\\opencv3.4.16_vc14\\sources\\build")
  6. # 定义宏,将项目路径传递给 C++ 代码
  7. # c++通过 代码 :std::cout << PRO_ROOT_DIR << std::endl; 获取
  8. add_definitions(-DPRO_ROOT_DIR="${CMAKE_SOURCE_DIR}")
  9. find_package(OpenCV REQUIRED)
  10. include_directories(${OpenCV_INCLUDE_DIRS})
  11. # 多目录编译,编译根目录和子目录下的文件
  12. file (GLOB files *.cpp *.h) # 创建变量files
  13. add_executable(${PROJECT_NAME} ${files})
  14. target_link_libraries(${PROJECT_NAME} ${OpenCV_LIBS})

关键字OpenCV