diff --git a/src/src/train/ann_train.cpp b/src/src/train/ann_train.cpp index 54c4bce..09525c4 100644 --- a/src/src/train/ann_train.cpp +++ b/src/src/train/ann_train.cpp @@ -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(2) = second_hidden_neurons; layers.at(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 AnnTrain::identifyChinese(cv::Mat input) { cv::Mat feature = charFeatures2(input, kPredictSize); float maxVal = -2; @@ -124,7 +136,10 @@ std::pair 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 AnnTrain::identify(cv::Mat input) { cv::Mat feature = charFeatures2(input, kPredictSize); @@ -155,7 +170,10 @@ std::pair 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> 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 AnnTrain::sdata(size_t number_for_count) { assert(chars_folder_); @@ -256,6 +292,9 @@ cv::Ptr 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 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 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 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 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 AnnTrain::tdata() { assert(chars_folder_); @@ -322,7 +372,9 @@ cv::Ptr 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 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 AnnTrain::tdata() { train_classes); } } -/*δһԴĿEasyPRеһAnnTrainʵ֡ - -AnnTrainѵһ˹磨ANNַʶģ͡һַͼļ·һXMLļ·Ϊ - -ڹ캯УһANN_MLPann_typeָѵͣ0ʾַ1ʾַֻkv_һKvĶڼһӳļ - -train()ѵģ͵ĺĺtypeֵȷ㡢زĽڵԼIJͽṹȻĸֲ缤ѵֹͣȡţѵݲʼѵģ͡ѵɺ󣬽ģͱ浽ָXMLļУtest()ģ͵׼ȷʡ - -identifyChinese()identify()ֱʶַͷַǽһַͼΪ룬ȡʹѵõģͽԤ⣬һַʡϢpair - -test()ģ͵׼ȷʡÿַ𣬶ȡַͼļʹѵõģͽʶ𣬲ͳԤȷ׼ȷʡ - -getSyntheticImage()ɺϳͼͨƽƺתԭַͼѵݵĶԡ - -sdata()ѵݣȡַͼļеͼ񣬶ÿַָһЩϳͼ񣬲ȡ - -tdata()sdata()ƣѵݣֻDzɺϳͼ - -໹һЩͳ壬ûδи*/ \ No newline at end of file +// 该部分代码是用于生成训练数据的一部分,首先将samples转换为CV_32F类型的samples_, +// 然后初始化train_classes为全零矩阵,接着遍历train_classes的每一行,将对应位置的值设为1。 +// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。 \ No newline at end of file diff --git a/src/src/train/create_data.cpp b/src/src/train/create_data.cpp index 5eb713a..dd8c5b8 100644 --- a/src/src/train/create_data.cpp +++ b/src/src/train/create_data.cpp @@ -7,7 +7,10 @@ namespace easypr { assert(img.type() == CV_8UC1); int w = img.cols; int h = img.rows; - +// 该部分代码定义了一个名为getBorderColor的函数,用于获取图像边界的颜色值 +// 函数首先确保输入图像是单通道灰度图像,然后获取图像的宽度和高度 +// 接下来通过遍历图像边界像素的方式计算边界颜色的平均值,并返回整数类型的平均值 +// 该文件还包含了一些其他图像处理函数,如平移、旋转、裁剪和生成合成图像的函数 float sum = 0; for (int i = 0; i < h; ++i) { sum += img.at(i, 0); @@ -21,7 +24,9 @@ namespace easypr { float avg = sum / float(w + w + h + h); return int(avg); } - +// 该部分代码计算了图像边界像素的平均值作为边界颜色值,并返回整数类型的平均值 +// 首先对图像边界像素进行遍历,累加像素值到sum中 +// 然后计算平均值avg,并返回整数类型的平均值 // shift an image Mat translateImg(Mat img, int offsetx, int offsety, int bk){ Mat dst; @@ -31,7 +36,12 @@ namespace easypr { warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk)); return dst; } - +// 该部分代码定义了一个名为translateImg的函数,用于对图像进行平移操作 +// 函数接受输入图像img以及x和y方向的偏移量offsetx和offsety,以及背景颜色bk +// 在函数内部,首先创建了一个空的目标图像dst +// 然后定义了一个2x3的变换矩阵trans_mat,用于表示平移操作 +// 最后调用warpAffine函数对输入图像进行平移操作,并将结果存储在目标图像dst中 +// 最终返回平移后的目标图像dst // rotate an image Mat rotateImg(Mat source, float angle, int bk){ Point2f src_center(source.cols / 2.0F, source.rows / 2.0F); @@ -40,7 +50,12 @@ namespace easypr { warpAffine(source, dst, rot_mat, source.size(), 1, 0, Scalar(bk)); return dst; } - +// 该部分代码定义了一个名为rotateImg的函数,用于对图像进行旋转操作 +// 函数接受输入图像source以及旋转角度angle和背景颜色bk作为参数 +// 在函数内部,首先计算了输入图像的中心点坐标src_center +// 然后利用getRotationMatrix2D函数计算了旋转矩阵rot_mat +// 最后调用warpAffine函数对输入图像进行旋转操作,并将结果存储在目标图像dst中 +// 最终返回旋转后的目标图像dst // crop the image Mat cropImg(Mat src, int x, int y, int shift, int bk){ int width = src.cols; @@ -61,7 +76,13 @@ namespace easypr { resize(dst, dst, Size(width, height)); return dst; } - +// 该部分代码定义了一个名为cropImg的函数,用于对图像进行裁剪操作 +// 函数接受输入图像src以及裁剪起始点的x和y坐标,裁剪尺寸shift和背景颜色bk作为参数 +// 在函数内部,首先获取输入图像的宽度和高度 +// 然后计算裁剪后的图像宽度和高度 +// 接着根据裁剪起始点和裁剪尺寸计算裁剪区域的矩形rect +// 将裁剪后的图像存储在目标图像dst中,并进行大小调整 +// 最终返回裁剪后的目标图像dst Mat generateSyntheticImage(const Mat& image, int use_swap) { int rd = rand(); int bkColor = getBoderColor(image); @@ -85,4 +106,10 @@ namespace easypr { return result; } } - +// 该部分代码定义了一个名为generateSyntheticImage的函数,用于生成合成图像 +// 函数接受输入图像image以及一个整数参数use_swap +// 首先通过rand函数生成一个随机数rd +// 然后调用getBorderColor函数获取输入图像的边界颜色值,并存储在bkColor中 +// 接着对输入图像进行克隆,存储在result中 +// 之后根据随机数rd的不同位进行裁剪、平移和旋转操作,并将结果存储在result中 +// 最终返回合成后的目标图像result