|
|
|
@ -21,6 +21,10 @@ AnnTrain::AnnTrain(const char* chars_folder, const char* xml)
|
|
|
|
|
kv_->load("resources/text/province_mapping");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是C++的类成员函数AnnTrain::AnnTrain的构造函数实现。
|
|
|
|
|
// 构造函数接受两个参数chars_folder和xml,并将其分别赋值给成员变量chars_folder_和ann_xml_。
|
|
|
|
|
// 然后使用cv::ml::ANN_MLP::create()创建了一个神经网络对象ann_。
|
|
|
|
|
// type被初始化为0,kv_被初始化为一个加载了"resources/text/province_mapping"的Kv对象。
|
|
|
|
|
void AnnTrain::train() {
|
|
|
|
|
|
|
|
|
|
int classNumber = 0;
|
|
|
|
@ -45,7 +49,9 @@ void AnnTrain::train() {
|
|
|
|
|
hidden_number = kNeurons;
|
|
|
|
|
output_number = classNumber;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是AnnTrain类的train方法,根据type的值选择不同的classNumber、input_number、hidden_number和output_number。
|
|
|
|
|
// 当type为0时,classNumber为kCharsTotalNumber,input_number为kAnnInput,hidden_number为kNeurons,output_number为classNumber。
|
|
|
|
|
// 当type为1时,classNumber为kChineseNumber,input_number为kAnnInput,hidden_number为kNeurons,output_number为classNumber。
|
|
|
|
|
int N = input_number;
|
|
|
|
|
int m = output_number;
|
|
|
|
|
int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2)));
|
|
|
|
@ -70,7 +76,9 @@ void AnnTrain::train() {
|
|
|
|
|
layers.at<int>(2) = second_hidden_neurons;
|
|
|
|
|
layers.at<int>(3) = output_number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//这段代码根据输入和输出的数量计算了两个隐藏层的神经元数量,
|
|
|
|
|
//并根据布尔变量useTLFN的值选择了创建三层或四层的神经网络层。如果useTLFN为false,则创建三层,否则创建四层。
|
|
|
|
|
//在创建四层时,输出了两个隐藏层的神经元数量。
|
|
|
|
|
ann_->setLayerSizes(layers);
|
|
|
|
|
ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
|
|
|
|
|
ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP);
|
|
|
|
@ -99,7 +107,11 @@ void AnnTrain::train() {
|
|
|
|
|
std::cout << "Your ANN Model was saved to " << ann_xml_ << std::endl;
|
|
|
|
|
std::cout << "Training done. Time elapse: " << (end - start) / (1000 * 60) << "minute" << std::endl;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//这段代码是一个C++类成员函数AnnTrain::train的实现。
|
|
|
|
|
//在这段代码中,神经网络ann_被训练并保存到ann_xml_文件中。
|
|
|
|
|
//训练数据通过sdata(350)函数获取,然后使用ann_->train(traindata)进行训练。
|
|
|
|
|
//训练完成后,会输出"Your ANN Model was saved to "以及训练所花费的时间。
|
|
|
|
|
//同时,还会调用test()函数进行测试。如果训练文件夹中没有文件,则会输出相应的提示信息。
|
|
|
|
|
std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
|
|
|
|
|
cv::Mat feature = charFeatures2(input, kPredictSize);
|
|
|
|
|
float maxVal = -2;
|
|
|
|
@ -124,7 +136,10 @@ std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
|
|
|
|
|
|
|
|
|
|
return std::make_pair(s, province);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是AnnTrain类的identifyChinese方法,接受一个cv::Mat类型的输入参数input。
|
|
|
|
|
// 首先调用charFeatures2函数提取特征,然后使用神经网络ann_对特征进行预测,得到输出output。
|
|
|
|
|
// 接着遍历output,找到最大值对应的索引result,并计算出最终的索引index。
|
|
|
|
|
// 最后根据index获取对应的字符key,再通过kv_获取对应的省份province,最终返回一个包含字符和省份的pair。
|
|
|
|
|
|
|
|
|
|
std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
|
|
|
|
|
cv::Mat feature = charFeatures2(input, kPredictSize);
|
|
|
|
@ -155,7 +170,10 @@ std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
|
|
|
|
|
return std::make_pair(s, province);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是AnnTrain类的identify方法,接受一个cv::Mat类型的输入参数input。
|
|
|
|
|
// 首先调用charFeatures2函数提取特征,然后使用神经网络ann_对特征进行预测,得到输出output。
|
|
|
|
|
// 接着遍历output,找到最大值对应的索引result,并计算出最终的索引index。
|
|
|
|
|
// 最后根据index判断返回的字符和省份信息,返回一个包含字符和省份的pair。
|
|
|
|
|
void AnnTrain::test() {
|
|
|
|
|
assert(chars_folder_);
|
|
|
|
|
|
|
|
|
@ -175,7 +193,10 @@ void AnnTrain::test() {
|
|
|
|
|
auto chars_files = utils::getFiles(sub_folder);
|
|
|
|
|
int corrects = 0, sum = 0;
|
|
|
|
|
std::vector<std::pair<std::string, std::string>> error_files;
|
|
|
|
|
|
|
|
|
|
// 这段代码是AnnTrain类的test方法,用于测试字符识别的准确率。
|
|
|
|
|
// 首先根据type的值确定classNumber,然后遍历每个字符的文件夹进行测试。
|
|
|
|
|
// 在测试过程中,会统计正确识别的字符数量和总测试字符数量,以及每个字符的识别准确率。
|
|
|
|
|
// 最后输出总的测试结果和平均准确率。
|
|
|
|
|
for (auto file : chars_files) {
|
|
|
|
|
auto img = cv::imread(file, 0); // a grayscale image
|
|
|
|
|
if (!img.data) {
|
|
|
|
@ -196,6 +217,11 @@ void AnnTrain::test() {
|
|
|
|
|
++sum;
|
|
|
|
|
++sum_all;
|
|
|
|
|
}
|
|
|
|
|
// 这段代码是一个循环,遍历chars_files中的文件,对每个文件进行处理。
|
|
|
|
|
// 首先使用OpenCV的imread函数读取文件为灰度图像img,然后判断img是否为空。
|
|
|
|
|
// 如果type为0,则调用identify函数对图像进行识别,否则调用identifyChinese函数。
|
|
|
|
|
// 如果识别结果与char_key相同,则将corrects和corrects_all加一,否则将错误信息加入error_files。
|
|
|
|
|
// 最后将sum和sum_all加一。
|
|
|
|
|
float rate = (float)corrects / (sum == 0 ? 1 : sum);
|
|
|
|
|
fprintf(stdout, ">> [sum: %d, correct: %d, rate: %.2f]\n", sum, corrects, rate);
|
|
|
|
|
rate_list.push_back(rate);
|
|
|
|
@ -217,6 +243,10 @@ void AnnTrain::test() {
|
|
|
|
|
}
|
|
|
|
|
fprintf(stdout, ">> [\n%s\n ]\n", error_string.c_str());
|
|
|
|
|
}
|
|
|
|
|
// 这段代码计算了识别准确率,并输出了每个字符的识别结果和错误信息。
|
|
|
|
|
// 首先计算了识别准确率rate,并将其加入rate_list中。
|
|
|
|
|
// 然后构建了错误信息字符串error_string,遍历error_files并将错误信息添加到字符串中。
|
|
|
|
|
// 最后使用fprintf输出了总的识别结果和错误信息。
|
|
|
|
|
fprintf(stdout, ">> [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all,
|
|
|
|
|
(float)corrects_all / (sum_all == 0 ? 1 : sum_all));
|
|
|
|
|
|
|
|
|
@ -226,6 +256,8 @@ void AnnTrain::test() {
|
|
|
|
|
fprintf(stdout, ">> [classNumber: %d, avg_rate: %.4f]\n", classNumber, rate_mean);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码用于输出总的测试结果和平均准确率。
|
|
|
|
|
// 首先输出总的测试结果和准确率,然后计算了每个字符的识别准确率的平均值并输出。
|
|
|
|
|
cv::Mat getSyntheticImage(const Mat& image) {
|
|
|
|
|
int rand_type = rand();
|
|
|
|
|
Mat result = image.clone();
|
|
|
|
@ -244,7 +276,11 @@ cv::Mat getSyntheticImage(const Mat& image) {
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 该代码定义了一个函数getSyntheticImage,接受一个cv::Mat类型的参数image。
|
|
|
|
|
//首先生成一个随机数rand_type,然后将result初始化为image的克隆。
|
|
|
|
|
//如果rand_type为偶数,则生成两个随机数ran_x和ran_y,然后调用translateImg函数对result进行平移操作。
|
|
|
|
|
// 如果rand_type为奇数,则生成一个随机角度angle,然后调用rotateImg函数对result进行旋转操作。
|
|
|
|
|
// 最后返回result。
|
|
|
|
|
cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
assert(chars_folder_);
|
|
|
|
|
|
|
|
|
@ -256,6 +292,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
if (type == 1) classNumber = kChineseNumber;
|
|
|
|
|
|
|
|
|
|
srand((unsigned)time(0));
|
|
|
|
|
// 这段代码是AnnTrain类的sdata方法,用于生成训练数据。
|
|
|
|
|
// 首先检查chars_folder_是否存在,然后初始化samples和labels。
|
|
|
|
|
// 根据type的值确定classNumber,然后使用srand函数初始化随机数种子。
|
|
|
|
|
for (int i = 0; i < classNumber; ++i) {
|
|
|
|
|
|
|
|
|
|
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
|
|
|
|
@ -274,7 +313,11 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
auto img = cv::imread(file, 0); // a grayscale image
|
|
|
|
|
matVec.push_back(img);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是一个循环,遍历每个字符文件夹中的文件,并将文件读取为灰度图像后存入matVec中。
|
|
|
|
|
// 首先根据循环变量i计算出当前字符的关键字char_key,并构建对应的子文件夹路径sub_folder。
|
|
|
|
|
// 然后使用utils::getFiles函数获取子文件夹中的文件列表chars_files,并统计文件数量char_size。
|
|
|
|
|
// 接着初始化了一个存储灰度图像的向量matVec,并预留了number_for_count个元素的空间。
|
|
|
|
|
// 遍历chars_files,使用cv::imread函数读取文件为灰度图像img,并将其存入matVec中。
|
|
|
|
|
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;
|
|
|
|
@ -287,7 +330,10 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
imwrite(ss.str(), simg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是一个循环,用于生成合成图像并保存到文件中。
|
|
|
|
|
// 首先循环变量t从0到number_for_count - char_size,然后生成一个随机数ran_num。
|
|
|
|
|
// 接着从matVec中获取对应索引的图像img,并调用getSyntheticImage函数生成合成图像simg。
|
|
|
|
|
// 将simg添加到matVec中,并使用imwrite函数将simg保存为文件。
|
|
|
|
|
fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size());
|
|
|
|
|
|
|
|
|
|
for (auto img : matVec) {
|
|
|
|
@ -297,7 +343,8 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
labels.push_back(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码用于统计字符数量并将特征和标签添加到训练数据中。
|
|
|
|
|
// 首先使用fprintf输出字符数量,然后遍历matVec中的图像,对每个图像提取特征并将特征和标签添加到训练数据中。
|
|
|
|
|
cv::Mat samples_;
|
|
|
|
|
samples.convertTo(samples_, CV_32F);
|
|
|
|
|
cv::Mat train_classes =
|
|
|
|
@ -310,7 +357,10 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
|
|
|
|
|
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
|
|
|
|
|
train_classes);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 该部分代码是AnnTrain类的tdata方法,用于生成训练数据。
|
|
|
|
|
// 首先将samples转换为CV_32F类型的samples_,然后初始化train_classes为全零矩阵。
|
|
|
|
|
// 接着遍历train_classes的每一行,将对应位置的值设为1。
|
|
|
|
|
// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。
|
|
|
|
|
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
|
|
|
|
|
assert(chars_folder_);
|
|
|
|
|
|
|
|
|
@ -322,7 +372,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
|
|
|
|
|
int classNumber = 0;
|
|
|
|
|
if (type == 0) classNumber = kCharsTotalNumber;
|
|
|
|
|
if (type == 1) classNumber = kChineseNumber;
|
|
|
|
|
|
|
|
|
|
// 这段代码是AnnTrain类的tdata方法,用于生成训练数据。
|
|
|
|
|
// 首先检查chars_folder_是否存在,然后初始化samples和labels。
|
|
|
|
|
// 根据type的值确定classNumber。
|
|
|
|
|
for (int i = 0; i < classNumber; ++i) {
|
|
|
|
|
auto char_key = kChars[i + kCharsTotalNumber - classNumber];
|
|
|
|
|
char sub_folder[512] = {0};
|
|
|
|
@ -340,7 +392,11 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
|
|
|
|
|
labels.push_back(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 这段代码是一个循环,遍历每个字符文件夹中的文件,并将文件读取为灰度图像后提取特征并添加到训练数据中。
|
|
|
|
|
// 首先根据循环变量i计算出当前字符的关键字char_key,并构建对应的子文件夹路径sub_folder。
|
|
|
|
|
// 然后使用utils::getFiles函数获取子文件夹中的文件列表chars_files,并遍历每个文件。
|
|
|
|
|
// 对每个文件使用cv::imread函数读取为灰度图像img,然后调用charFeatures2函数提取特征fps,并将其添加到samples中。
|
|
|
|
|
// 同时将当前字符的标签i添加到labels中。
|
|
|
|
|
cv::Mat samples_;
|
|
|
|
|
samples.convertTo(samples_, CV_32F);
|
|
|
|
|
cv::Mat train_classes =
|
|
|
|
@ -354,22 +410,6 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
|
|
|
|
|
train_classes);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*这段代码是一个开源项目EasyPR中的一个类AnnTrain的实现。
|
|
|
|
|
|
|
|
|
|
AnnTrain类用于训练一个基于人工神经网络(ANN)的字符识别模型。它接受一个包含字符图像的文件夹路径和一个输出的XML文件路径作为参数。
|
|
|
|
|
|
|
|
|
|
在构造函数中,它创建了一个ANN_MLP类对象ann_。type变量用于指定训练类型,0表示所有字符,1表示只有中文字符。kv_是一个Kv类的对象,用于加载一个映射文件。
|
|
|
|
|
|
|
|
|
|
train()函数是训练模型的核心函数。它根据type的值,确定输入层、隐藏层和输出层的节点数量,以及神经网络的层数和结构。然后设置神经网络的各种参数,如激活函数、训练方法、停止条件等。接着,它加载训练数据并开始训练模型。训练完成后,将模型保存到指定的XML文件中,并调用test()函数评估模型的准确率。
|
|
|
|
|
|
|
|
|
|
identifyChinese()函数和identify()函数分别用于识别中文字符和非中文字符。它们接受一个字符图像作为输入,提取特征并使用训练好的模型进行预测,返回一个包含字符和所属省份信息的pair。
|
|
|
|
|
|
|
|
|
|
test()函数用于评估模型的准确率。它遍历每个字符类别,读取所有字符图像文件,使用训练好的模型进行识别,并统计预测正确的数量。最后计算整体的准确率。
|
|
|
|
|
|
|
|
|
|
getSyntheticImage()函数用于生成合成图像,通过随机平移和旋转原字符图像来增加训练数据的多样性。
|
|
|
|
|
|
|
|
|
|
sdata()函数用于生成训练数据,读取字符图像文件夹中的所有图像,对于每个字符,根据指定数量生成一些合成图像,并提取特征。
|
|
|
|
|
|
|
|
|
|
tdata()函数和sdata()函数类似,用于生成训练数据,只是不生成合成图像。
|
|
|
|
|
|
|
|
|
|
该类还依赖其他一些辅助函数和常量定义,没有在这段代码中给出。*/
|
|
|
|
|
// 该部分代码是用于生成训练数据的一部分,首先将samples转换为CV_32F类型的samples_,
|
|
|
|
|
// 然后初始化train_classes为全零矩阵,接着遍历train_classes的每一行,将对应位置的值设为1。
|
|
|
|
|
// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。
|