一、原图
opencv4.7\sources\samples\data\digits.png
//读取OpenCV自带的一张手写体数字图,尺寸为Size(2000,1000),其中每个数字为(20,20)的区域,总共有 [(1000/20)x(2000/20)] 共5000个数字。
二、分割图片
// 使用math库里的宏常量 #define _USE_MATH_DEFINES #include <iostream> #include <filesystem> #include <string> #include <windows.h> #include <io.h> #include <direct.h> #include <opencv2/opencv.hpp> namespace fs = std::filesystem; using namespace cv; using namespace std; // 分割数字图片 void split_digital_img() { int filename = 0, filenum = 0; Mat img = imread("D:/opencv/opencv4.7/sources/samples/data/digits.png"); Mat gray; cvtColor(img, gray, COLOR_BGR2GRAY); int b = 20; int m = gray.rows / b; // 原图为1000*2000 int n = gray.cols / b; // 裁剪为5000个20*20的小图块 // 创建文件夹 for (int i = 0; i <= 9; i++) { // 文件夹路径 string dir = "D:/opencv/mnist/pic/" + to_string(i); // 判断该文件夹是否存在 if (_access(dir.c_str(), 0) == -1) { // Windows 创建文件夹 int flag = _mkdir(dir.c_str()); } } for (int i = 0; i < m; i++) { // 行上的偏移量 int offsetRow = i * b; // 原图中每5行存储相同数字,因此过了5行要递增文件名 if (i % 5 == 0 && i != 0) { filename++; // 递增文件名 filenum = 0; // 清零文件计数器 } for (int j = 0; j < n; j++) { int offsetCol = j * b; // 列上的偏移量 string file_savepath = "D:/opencv/mnist/pic/" + to_string(filename) + "/" + to_string(filenum++) + ".png"; // 截取20*20的小块 Mat tmp; gray(Range(offsetRow, offsetRow + b), Range(offsetCol, offsetCol + b)).copyTo(tmp); imwrite(file_savepath, tmp); // 将对应的数字图像块保存到对应名字的文件夹中 } } }
三、训练、测试
// knn手写数字识别 void test_knn_train() { string file_savepath; int testnum = 0, truenum = 0; const int K = 3; //设置K值为3 cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create(); // 创建KNN类 knn->setDefaultK(K); knn->setIsClassifier(true); // 设置KNN用于分类 knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE); // 设置寻找距离最近的K个样本的方式为遍历所有训练样本,即暴力破解的方式。 Mat traindata, trainlabel; for (int i = 0; i < 10; i++) { for (int j = 0; j < 400; j++) { file_savepath = "D:/opencv/mnist/pic/" + to_string(i) + "/" + to_string(j) + ".png"; Mat srcimage = imread(file_savepath); // 把二维数据转换为一维数据 srcimage = srcimage.reshape(1, 1); // Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数。 traindata.push_back(srcimage); // srcimage为Mat类型的1行n列的一维矩阵,将该矩阵保存到训练矩阵中。 trainlabel.push_back(i); // i为srcimage的标签,同时将i保存到标签矩阵中。 } } traindata.convertTo(traindata, CV_32F); //重要:训练矩阵必须是浮点型数据 knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel); // 导入训练数据和标签 // 标签 for (int i = 0; i < 10; i++) { for (int j = 400; j < 500; j++) { testnum++; //统计总的分类次数 file_savepath = "D:/opencv/mnist/pic/" + to_string(i) + "/" + to_string(j) + ".png"; Mat testdata = imread(file_savepath); testdata = testdata.reshape(1, 1); // 将二维数据转换成一维数据 testdata.convertTo(testdata, CV_32F); // 将数据转换成浮点数据 Mat result; // 寻找K个最邻近样本,并统计K个样本的分类数量,返回数量最多的分类的标签。 int response = knn->findNearest(testdata, K, result); // 如果得到的分类标签与真实标签一致,则分类正确 if (response == i) { truenum++; } } } cout << "测试总数" << testnum << endl; cout << "正确分类数" << truenum << endl; cout << "准确率:" << (float)truenum / testnum * 100 << "%" << endl; }
(用时7秒左右)
四、保存模型
// knn手写数字识别 void test_knn_train() { string file_savepath; int testnum = 0, truenum = 0; const int K = 3; //设置K值为3 cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::create(); // 创建KNN类 knn->setDefaultK(K); knn->setIsClassifier(true); // 设置KNN用于分类 knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE); // 设置寻找距离最近的K个样本的方式为遍历所有训练样本,即暴力破解的方式。 Mat traindata, trainlabel; for (int i = 0; i < 10; i++) { for (int j = 0; j < 400; j++) { file_savepath = "D:/opencv/mnist/pic/" + to_string(i) + "/" + to_string(j) + ".png"; Mat srcimage = imread(file_savepath); // 把二维数据转换为一维数据 srcimage = srcimage.reshape(1, 1); // Mat Mat::reshape(int cn, int rows = 0),cn表示通道数,为0则通道不变,rows表示矩阵行数。 traindata.push_back(srcimage); // srcimage为Mat类型的1行n列的一维矩阵,将该矩阵保存到训练矩阵中。 trainlabel.push_back(i); // i为srcimage的标签,同时将i保存到标签矩阵中。 } } traindata.convertTo(traindata, CV_32F); //重要:训练矩阵必须是浮点型数据 knn->train(traindata, cv::ml::ROW_SAMPLE, trainlabel); // 导入训练数据和标签 knn->save("D:/opencv/mnist/knn_digits_model.yml"); // 保存模型 }
五、加载模型
// knn手写数字识别 void test_knn_model() { string file_savepath; int testnum = 0, truenum = 0; const int k = 3; // 设置k值为3 // 模型加载 cv::Ptr<cv::ml::KNearest> knn = cv::ml::KNearest::load("D:/opencv/mnist/knn_digits_model.yml"); // 创建KNN类 knn->setDefaultK(k); knn->setIsClassifier(true); // 设置KNN用于分类 knn->setAlgorithmType(cv::ml::KNearest::BRUTE_FORCE); // 设置寻找距离最近的K个样本的方式为遍历所有训练样本,即暴力破解的方式。 Mat traindata, trainlabel; // 标签 for (int i = 0; i < 10; i++) { for (int j = 400; j < 500; j++) { testnum++; //统计总的分类次数 file_savepath = "D:/opencv/mnist/pic/" + to_string(i) + "/" + to_string(j) + ".png"; Mat testdata = imread(file_savepath); testdata = testdata.reshape(1, 1); // 将二维数据转换成一维数据 testdata.convertTo(testdata, CV_32F); // 将数据转换成浮点数据 Mat result; // 寻找k个最邻近样本,并统计k个样本的分类数量,返回数量最多的分类的标签。 int response = knn->findNearest(testdata, k, result); // 如果得到的分类标签与真实标签一致,则分类正确 if (response == i) { truenum++; } } } cout << "测试总数" << testnum << endl; cout << "正确分类数" << truenum << endl; cout << "准确率:" << (float)truenum / testnum * 100 << "%" << endl; }
int main() { // knn手写数字识别 //split_digital_img(); //test_knn_train(); test_knn_model(); waitKey(0); destroyAllWindows(); return 0; }