diff --git a/annCh_train.cpp b/annCh_train.cpp index ec98607..fef78cc 100644 --- a/annCh_train.cpp +++ b/annCh_train.cpp @@ -1,187 +1,257 @@ -#include -#include - -#include "easypr/train/annCh_train.h" -#include "easypr/config.h" -#include "easypr/core/chars_identify.h" -#include "easypr/core/feature.h" -#include "easypr/core/core_func.h" -#include "easypr/util/util.h" -#include "easypr/train/create_data.h" - -namespace easypr { - - AnnChTrain::AnnChTrain(const char* chars_folder, const char* xml) - : chars_folder_(chars_folder), ann_xml_(xml) - { - ann_ = cv::ml::ANN_MLP::create(); - type = 1; - kv_ = std::shared_ptr(new Kv); - kv_->load("resources/text/province_mapping"); - extractFeature = getGrayPlusProject; -} - - void AnnChTrain::train() - { - int classNumber = 0; - int input_number = 0; - int hidden_number = 0; - int output_number = 0; - - bool useLBP = false; - if (useLBP) - input_number = kCharLBPPatterns * kCharLBPGridX * kCharLBPGridY; - else - input_number = kGrayCharHeight * kGrayCharWidth; - - input_number += 64; - - classNumber = kChineseNumber; - hidden_number = kCharHiddenNeurans; - output_number = classNumber; - cv::Mat layers; - - int first_hidden_neurons = 48; - int second_hidden_neurons = 32; - - int N = input_number; - int m = output_number; - //int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2))); - //int second_hidden_neurons = int(m * std::sqrt(N / (m + 2))); - - bool useTLFN = false; - if (!useTLFN) { - layers.create(1, 3, CV_32SC1); - layers.at(0) = input_number; - layers.at(1) = hidden_number; - layers.at(2) = output_number; - } - else { - fprintf(stdout, ">> Use two-layers neural networks,\n"); - fprintf(stdout, ">> First_hidden_neurons: %d \n", first_hidden_neurons); - fprintf(stdout, ">> Second_hidden_neurons: %d \n", second_hidden_neurons); - - layers.create(1, 4, CV_32SC1); - layers.at(0) = input_number; - layers.at(1) = first_hidden_neurons; - layers.at(2) = second_hidden_neurons; - layers.at(3) = output_number; - } - - ann_->setLayerSizes(layers); - ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1); - ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP); - ann_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 30000, 0.0001)); - ann_->setBackpropWeightScale(0.1); - ann_->setBackpropMomentumScale(0.1); - - auto files = Utils::getFiles(chars_folder_); - if (files.size() == 0) { - fprintf(stdout, "No file found in the train folder!\n"); - fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n"); - fprintf(stdout, "Copy train data folder(like \"annCh\") under \"tmp\". \n"); - return; +#include //包含了C++标准库中的头文件,提供了数值计算的相关函数和模板。 +#include //包含了C++标准库中的头文件,提供了关于时间和日期的相关函数和类型。 + +#include "easypr/train/annCh_train.h"//包含了EasyPR库中的annCh_train.h头文件,这个头文件可能包含了用于训练ANN(人工神经网络)字符识别的相关函数和类。 +#include "easypr/config.h"//包含了EasyPR库的config.h头文件,这个头文件可能包含了一些配置EasyPR库的全局变量和宏定义。 +#include "easypr/core/chars_identify.h"//包含了EasyPR库的chars_identify.h头文件,这个头文件可能包含了字符识别的核心功能的声明。 +#include "easypr/core/feature.h"//包含了EasyPR库的feature.h头文件,这个头文件可能包含了特征提取和处理的相关的函数和类。 +#include "easypr/core/core_func.h"//包含了EasyPR库的core_func.h头文件,这个头文件可能包含了一些核心的函数和类。 +#include "easypr/util/util.h"//包含了EasyPR库的util.h头文件,这个头文件可能包含了一些工具函数和类。 +#include "easypr/train/create_data.h"//包含了EasyPR库的create_data.h头文件,这个头文件可能包含了用于创建训练数据的函数和类。 + +namespace easypr { // 定义命名空间easypr + + AnnChTrain::AnnChTrain(const char* chars_folder, const char* xml) // 定义构造函数,参数为字符文件夹路径和xml文件路径 + : chars_folder_(chars_folder), ann_xml_(xml) // 初始化chars_folder_和ann_xml_成员变量 + { + ann_ = cv::ml::ANN_MLP::create(); // 创建一个MLP(Multilayer Perceptron,多层感知器)对象,用于字符识别 + type = 1; // 初始化type为1,可能表示某种类型或模式 + kv_ = std::shared_ptr(new Kv); // 创建一个Kv对象,并使用std::shared_ptr管理内存,实现共享所有权模型 + kv_->load("resources/text/province_mapping"); // 加载kv_对象,可能从文件"resources/text/province_mapping"中加载数据 + extractFeature = getGrayPlusProject; // 初始化extractFeature函数指针,指向getGrayPlusProject函数,用于特征提取 } - - // using raw data or raw + synthic data. - trainVal(m_number_for_count); +void AnnChTrain::train() +{ + int classNumber = 0; // 类别数量,初始化为0,需要在后续代码中赋值 + int input_number = 0; // 输入节点数量,初始化为0,需要在后续代码中赋值 + int hidden_number = 0; // 隐藏层节点数量,初始化为0,需要在后续代码中赋值 + int output_number = 0; // 输出节点数量,初始化为0,需要在后续代码中赋值 + + bool useLBP = false; // 是否使用LBP特征,初始化为false + if (useLBP) // 如果使用LBP特征 + input_number = kCharLBPPatterns * kCharLBPGridX * kCharLBPGridY; // 则设置输入节点数量为LBP特征的数量 + else + input_number = kGrayCharHeight * kGrayCharWidth; // 否则设置输入节点数量为字符图像的高度和宽度的乘积 + + input_number += 64; // 在输入节点数量基础上加64,可能是为了增加一些额外的输入节点 } -std::pair AnnChTrain::identifyGrayChinese(cv::Mat input) { - Mat feature; - extractFeature(input, feature); - float maxVal = -2; - int result = 0; - - cv::Mat output(1, kChineseNumber, CV_32FC1); - ann_->predict(feature, output); - - for (int j = 0; j < kChineseNumber; j++) { - float val = output.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - - auto index = result + kCharsTotalNumber - kChineseNumber; - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - - return std::make_pair(s, province); +classNumber = kChineseNumber; // 类别数量,这里假设 kChineseNumber 是一个定义好的常量 +hidden_number = kCharHiddenNeurons; // 隐藏层节点数量,这里假设 kCharHiddenNeurons 是一个定义好的常量 +output_number = classNumber; // 输出节点数量,等于类别数量 +cv::Mat layers; // 声明一个 OpenCV 的 Mat 对象,用于存储网络层的数据,但在这段代码中没有使用 + +int first_hidden_neurons = 48; // 第一隐藏层节点数量,硬编码为48 +int second_hidden_neurons = 32; // 第二隐藏层节点数量,硬编码为32 + +int N = input_number; // 输入节点数量,这里假设 input_number 是一个定义好的变量 +int m = output_number; // 输出节点数量,等于类别数量,这里假设 output_number 是一个定义好的变量 + +// 在这里注释掉了两行代码,它们原先可能是用于计算第一层和第二层隐藏层的节点数量的公式 +//int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2))); +//int second_hidden_neurons = int(m * std::sqrt(N / (m + 2))); + +bool useTLFN = false; // 是否使用TLFN,初始化为false,但在这段代码中没有使用 + +if (!useTLFN) { // 如果不使用两层神经网络(TLFN) + layers.create(1, 3, CV_32SC1); // 创建一个1行3列的OpenCV Mat对象,数据类型为32位有符号整数 + layers.at(0) = input_number; // 设置输入层节点数量 + layers.at(1) = hidden_number; // 设置隐藏层节点数量 + layers.at(2) = output_number; // 设置输出层节点数量 +} +else { // 如果使用两层神经网络(TLFN) + fprintf(stdout, ">> Use two-layers neural networks,\n"); // 打印信息到标准输出,表示正在使用两层神经网络 + fprintf(stdout, ">> First_hidden_neurons: %d \n", first_hidden_neurons); // 打印第一层隐藏层节点数量到标准输出 + fprintf(stdout, ">> Second_hidden_neurons: %d \n", second_hidden_neurons); // 打印第二层隐藏层节点数量到标准输出 + + layers.create(1, 4, CV_32SC1); // 创建一个1行4列的OpenCV Mat对象,数据类型为32位有符号整数 + layers.at(0) = input_number; // 设置输入层节点数量 + layers.at(1) = first_hidden_neurons; // 设置第一层隐藏层节点数量 + layers.at(2) = second_hidden_neurons; // 设置第二层隐藏层节点数量 + layers.at(3) = output_number; // 设置输出层节点数量 } - void AnnChTrain::test() { - //TODO +// 设置神经网络层的大小 +ann_->setLayerSizes(layers); + +// 设置激活函数为Sigmoid函数,其对称性取决于第二个参数,第三个参数是该函数的斜率 +ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1); + +// 设置训练方法为反向传播法 +ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP); + +// 设置训练终止条件为最大迭代次数30000次,或当误差小于0.0001时终止 +ann_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 30000, 0.0001)); + +// 设置权重的更新比例因子为0.1 +ann_->setBackpropWeightScale(0.1); + +// 设置权重的动量更新比例因子为0.1 +ann_->setBackpropMomentumScale(0.1); + +// 获取文件夹中的文件列表,如果文件列表为空,则打印错误信息并给出建议 +auto files = Utils::getFiles(chars_folder_); +if (files.size() == 0) { + fprintf(stdout, "No file found in the train folder!\n"); + fprintf(stdout, "You should create a folder named \"tmp\" in EasyPR main folder.\n"); + fprintf(stdout, "Copy train data folder(like \"annCh\") under \"tmp\". \n"); + return; +} + +// 使用原始数据或原始数据 + 合成的数据进行训练和验证,具体数量由 m_number_for_count 决定 +trainVal(m_number_for_count); + +// 定义一个方法,用于识别汉字 +// 参数:输入图像 +// 返回值:一个由汉字字符串和对应的省份字符串组成的pair +std::pair AnnChTrain::identifyGrayChinese(cv::Mat input) { + // 定义特征向量 + Mat feature; + // 从输入图像中提取特征 + extractFeature(input, feature); + // 初始化最大值为-2 + float maxVal = -2; + // 初始化结果为0 + int result = 0; + + // 定义输出矩阵,大小为1行,kChineseNumber列,数据类型为CV_32FC1(32位浮点型) + cv::Mat output(1, kChineseNumber, CV_32FC1); + // 使用神经网络模型进行预测,输入特征向量,输出结果到output矩阵中 + ann_->predict(feature, output); + + // 遍历输出矩阵中的每一个值 + for (int j = 0; j < kChineseNumber; j++) { + // 获取当前位置的值 + float val = output.at(j); + // 如果当前值大于maxVal,则更新maxVal和result的值 + //std::cout << "j:" << j << "val:" << val << std::endl; + if (val > maxVal) { + maxVal = val; + result = j; + } + } + + // 根据result的值计算索引index,注意这里进行了偏移操作,可能是因为字符集的索引与输出结果的索引之间存在偏移 + auto index = result + kCharsTotalNumber - kChineseNumber; + // 根据索引获取对应的字符key + const char* key = kChars[index]; + // 将字符key转换为字符串s + std::string s = key; + // 通过kv_(应该是某个键值对容器)获取与s对应的省份字符串,存储到province变量中 + std::string province = kv_->get(s); + + // 返回一个由字符s和省份province组成的pair对象 + return std::make_pair(s, province); +} + +// 定义一个方法,用于测试模型性能(目前为空) +void AnnChTrain::test() { + // TODO: 需要实现测试代码,评估模型的性能指标,如准确率、召回率等。 +} + +// 定义一个方法,用于训练验证集(目前为空) +void AnnChTrain::trainVal(size_t number_for_count) { + // 断言chars_folder_不为空,否则会抛出异常(TODO: 需要实现断言失败的处理逻辑) + assert(chars_folder_); + // 定义训练样本的存储容器train_samples(TODO: 这里需要解释这个变量名和变量的具体含义) + cv::Mat train_samples; + // 定义训练图像、验证图像的存储容器(TODO: 这里需要解释这些变量名和变量的具体含义) + std::vector train_images, val_images; + std::vector train_label, val_labels; + // 设置训练验证集分割比例为0.7(70%用于训练,30%用于验证) + float percentage = 0.7f; + // 设置类别数为kChineseNumber(TODO: 需要解释这个变量的具体含义)直接把代码改成评注形式 + +// 循环遍历每个字符类别 +for (int i = 0; i < classNumber; ++i) { + // 从kChars数组中获取当前字符的键 + auto char_key = kChars[i + kCharsTotalNumber - classNumber]; + // 定义一个字符数组sub_folder,用于存储子文件夹的路径,并初始化为0 + char sub_folder[512] = { 0 }; + // 使用sprintf函数将字符键和字符文件夹路径拼接,存入sub_folder + sprintf(sub_folder, "%s/%s", chars_folder_, char_key); + // 将字符键转化为字符串类型,方便后续操作 + std::string test_char(char_key); + // 如果test_char不等于"zh_yun",则跳过当前循环 + // if (test_char != "zh_yun") continue; + fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder); + // 调用utils::getFiles函数获取子文件夹下的所有文件,存入chars_files + auto chars_files = utils::getFiles(sub_folder); + // 获取子文件夹下的文件数量 + size_t char_size = chars_files.size(); + fprintf(stdout, ">> Characters count: %d \n", (int)char_size); + + // 定义一个向量matVec,用于存储处理过的图像 + std::vector matVec; + // 为matVec预留空间,提高性能 + matVec.reserve(number_for_count); + // 内层循环,遍历子文件夹下的每一个文件 + for (auto file : chars_files) { + std::cout << file << std::endl; + // 使用OpenCV的imread函数读取图像,并将其转化为灰度图像 + auto img = cv::imread(file, IMREAD_GRAYSCALE); // a grayscale image + Mat img_resize; + // 为img_resize分配空间,并设置其大小和数据类型 + img_resize.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); + // 使用OpenCV的resize函数调整图像大小 + resize(img, img_resize, img_resize.size(), 0, 0, INTER_LINEAR); + // 将调整大小后的图像存入matVec + matVec.push_back(img_resize); + } +} +// 生成合成图像 +// genrate the synthetic images +for (int t = 0; t < (int)number_for_count - (int)char_size; t++) { + // 确定随机数的范围 + int rand_range = char_size + t; + // 生成一个随机数 + int ran_num = rand() % rand_range; + // 从matVec中获取一个图像 + auto img = matVec.at(ran_num); + // 显示该图像 + SHOW_IMAGE(img, 0); + // 生成合成图像 + auto simg = generateSyntheticImage(img); + // 显示合成图像 + SHOW_IMAGE(simg, 0); + // 将合成图像添加到matVec中 + matVec.push_back(simg); +} +// 输出matVec的大小 +fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size()); + +// 对matVec进行随机排序 +// random sort the mat; +srand(unsigned(time(NULL))); +random_shuffle(matVec.begin(), matVec.end()); + +// 获取matVec的大小 +int mat_size = (int)matVec.size(); +// 计算分割索引 +int split_index = int((float)mat_size * percentage); +// 从后往前遍历matVec +for (int j = mat_size - 1; j >= 0; j--) { + // 从matVec中获取图像 + Mat img = matVec.at(j); + // 此处代码可能有误,因为该判断语句始终为真,无法起到分割训练集和验证集的作用 + // 应该根据split_index来分割训练集和验证集 + if (1) { + Mat feature; + // 提取图像特征 + extractFeature(img, feature); + if (j <= split_index) { + // 将特征和图像添加到训练样本和训练图像中 + train_samples.push_back(feature); + train_images.push_back(img); + train_label.push_back(i); + } + else { + // 将图像添加到验证图像中,将标签添加到验证标签中 + val_images.push_back(img); + val_labels.push_back(i); + } + } } - -void AnnChTrain::trainVal(size_t number_for_count) { - assert(chars_folder_); - cv::Mat train_samples; - std::vector train_images, val_images; - std::vector train_label, val_labels; - float percentage = 0.7f; - int classNumber = kChineseNumber; - - for (int i = 0; i < classNumber; ++i) { - auto char_key = kChars[i + kCharsTotalNumber - classNumber]; - char sub_folder[512] = { 0 }; - sprintf(sub_folder, "%s/%s", chars_folder_, char_key); - std::string test_char(char_key); - // if (test_char != "zh_yun") continue; - - fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder); - auto chars_files = utils::getFiles(sub_folder); - size_t char_size = chars_files.size(); - fprintf(stdout, ">> Characters count: %d \n", (int)char_size); - - std::vector matVec; - matVec.reserve(number_for_count); - for (auto file : chars_files) { - std::cout << file << std::endl; - auto img = cv::imread(file, IMREAD_GRAYSCALE); // a grayscale image - Mat img_resize; - img_resize.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(img, img_resize, img_resize.size(), 0, 0, INTER_LINEAR); - matVec.push_back(img_resize); - } - // genrate the synthetic images - for (int t = 0; t < (int)number_for_count - (int)char_size; t++) { - int rand_range = char_size + t; - int ran_num = rand() % rand_range; - auto img = matVec.at(ran_num); - SHOW_IMAGE(img, 0); - auto simg = generateSyntheticImage(img); - SHOW_IMAGE(simg, 0); - matVec.push_back(simg); - } - fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size()); - - // random sort the mat; - srand(unsigned(time(NULL))); - random_shuffle(matVec.begin(), matVec.end()); - - int mat_size = (int)matVec.size(); - int split_index = int((float)mat_size * percentage); - for (int j = mat_size - 1; j >= 0; j--) { - Mat img = matVec.at(j); - if (1) { - Mat feature; - extractFeature(img, feature); - if (j <= split_index) { - train_samples.push_back(feature); - train_images.push_back(img); - train_label.push_back(i); - } - else { - val_images.push_back(img); - val_labels.push_back(i); - } - } - } - } // generate train data train_samples.convertTo(train_samples, CV_32F); cv::Mat train_classes = cv::Mat::zeros((int)train_label.size(), classNumber, CV_32F);