zhangtengyuan 10 months ago
parent 4d9a3513f7
commit 76ac41ebe0

@ -21,6 +21,10 @@ AnnTrain::AnnTrain(const char* chars_folder, const char* xml)
kv_->load("resources/text/province_mapping"); kv_->load("resources/text/province_mapping");
} }
// 这段代码是C++的类成员函数AnnTrain::AnnTrain的构造函数实现。
// 构造函数接受两个参数chars_folder和xml并将其分别赋值给成员变量chars_folder_和ann_xml_。
// 然后使用cv::ml::ANN_MLP::create()创建了一个神经网络对象ann_。
// type被初始化为0kv_被初始化为一个加载了"resources/text/province_mapping"的Kv对象。
void AnnTrain::train() { void AnnTrain::train() {
int classNumber = 0; int classNumber = 0;
@ -45,7 +49,9 @@ void AnnTrain::train() {
hidden_number = kNeurons; hidden_number = kNeurons;
output_number = classNumber; output_number = classNumber;
} }
// 这段代码是AnnTrain类的train方法根据type的值选择不同的classNumber、input_number、hidden_number和output_number。
// 当type为0时classNumber为kCharsTotalNumberinput_number为kAnnInputhidden_number为kNeuronsoutput_number为classNumber。
// 当type为1时classNumber为kChineseNumberinput_number为kAnnInputhidden_number为kNeuronsoutput_number为classNumber。
int N = input_number; int N = input_number;
int m = output_number; int m = output_number;
int first_hidden_neurons = int(std::sqrt((m + 2) * N) + 2 * std::sqrt(N / (m + 2))); 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>(2) = second_hidden_neurons;
layers.at<int>(3) = output_number; layers.at<int>(3) = output_number;
} }
//这段代码根据输入和输出的数量计算了两个隐藏层的神经元数量,
//并根据布尔变量useTLFN的值选择了创建三层或四层的神经网络层。如果useTLFN为false则创建三层否则创建四层。
//在创建四层时,输出了两个隐藏层的神经元数量。
ann_->setLayerSizes(layers); ann_->setLayerSizes(layers);
ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1); ann_->setActivationFunction(cv::ml::ANN_MLP::SIGMOID_SYM, 1, 1);
ann_->setTrainMethod(cv::ml::ANN_MLP::TrainingMethods::BACKPROP); 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 << "Your ANN Model was saved to " << ann_xml_ << std::endl;
std::cout << "Training done. Time elapse: " << (end - start) / (1000 * 60) << "minute" << 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) { std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize); cv::Mat feature = charFeatures2(input, kPredictSize);
float maxVal = -2; float maxVal = -2;
@ -124,7 +136,10 @@ std::pair<std::string, std::string> AnnTrain::identifyChinese(cv::Mat input) {
return std::make_pair(s, province); 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) { std::pair<std::string, std::string> AnnTrain::identify(cv::Mat input) {
cv::Mat feature = charFeatures2(input, kPredictSize); 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); return std::make_pair(s, province);
} }
} }
// 这段代码是AnnTrain类的identify方法接受一个cv::Mat类型的输入参数input。
// 首先调用charFeatures2函数提取特征然后使用神经网络ann_对特征进行预测得到输出output。
// 接着遍历output找到最大值对应的索引result并计算出最终的索引index。
// 最后根据index判断返回的字符和省份信息返回一个包含字符和省份的pair。
void AnnTrain::test() { void AnnTrain::test() {
assert(chars_folder_); assert(chars_folder_);
@ -175,7 +193,10 @@ void AnnTrain::test() {
auto chars_files = utils::getFiles(sub_folder); auto chars_files = utils::getFiles(sub_folder);
int corrects = 0, sum = 0; int corrects = 0, sum = 0;
std::vector<std::pair<std::string, std::string>> error_files; std::vector<std::pair<std::string, std::string>> error_files;
// 这段代码是AnnTrain类的test方法用于测试字符识别的准确率。
// 首先根据type的值确定classNumber然后遍历每个字符的文件夹进行测试。
// 在测试过程中,会统计正确识别的字符数量和总测试字符数量,以及每个字符的识别准确率。
// 最后输出总的测试结果和平均准确率。
for (auto file : chars_files) { for (auto file : chars_files) {
auto img = cv::imread(file, 0); // a grayscale image auto img = cv::imread(file, 0); // a grayscale image
if (!img.data) { if (!img.data) {
@ -196,6 +217,11 @@ void AnnTrain::test() {
++sum; ++sum;
++sum_all; ++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); float rate = (float)corrects / (sum == 0 ? 1 : sum);
fprintf(stdout, ">> [sum: %d, correct: %d, rate: %.2f]\n", sum, corrects, rate); fprintf(stdout, ">> [sum: %d, correct: %d, rate: %.2f]\n", sum, corrects, rate);
rate_list.push_back(rate); rate_list.push_back(rate);
@ -217,6 +243,10 @@ void AnnTrain::test() {
} }
fprintf(stdout, ">> [\n%s\n ]\n", error_string.c_str()); 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, fprintf(stdout, ">> [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all,
(float)corrects_all / (sum_all == 0 ? 1 : sum_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); fprintf(stdout, ">> [classNumber: %d, avg_rate: %.4f]\n", classNumber, rate_mean);
} }
// 这段代码用于输出总的测试结果和平均准确率。
// 首先输出总的测试结果和准确率,然后计算了每个字符的识别准确率的平均值并输出。
cv::Mat getSyntheticImage(const Mat& image) { cv::Mat getSyntheticImage(const Mat& image) {
int rand_type = rand(); int rand_type = rand();
Mat result = image.clone(); Mat result = image.clone();
@ -244,7 +276,11 @@ cv::Mat getSyntheticImage(const Mat& image) {
return result; 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) { cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
assert(chars_folder_); assert(chars_folder_);
@ -256,6 +292,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
if (type == 1) classNumber = kChineseNumber; if (type == 1) classNumber = kChineseNumber;
srand((unsigned)time(0)); srand((unsigned)time(0));
// 这段代码是AnnTrain类的sdata方法用于生成训练数据。
// 首先检查chars_folder_是否存在然后初始化samples和labels。
// 根据type的值确定classNumber然后使用srand函数初始化随机数种子。
for (int i = 0; i < classNumber; ++i) { for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber]; 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 auto img = cv::imread(file, 0); // a grayscale image
matVec.push_back(img); 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++) { for (int t = 0; t < (int)number_for_count - (int)char_size; t++) {
int rand_range = char_size + t; int rand_range = char_size + t;
int ran_num = rand() % rand_range; 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); 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()); fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size());
for (auto img : matVec) { for (auto img : matVec) {
@ -297,7 +343,8 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
labels.push_back(i); labels.push_back(i);
} }
} }
// 这段代码用于统计字符数量并将特征和标签添加到训练数据中。
// 首先使用fprintf输出字符数量然后遍历matVec中的图像对每个图像提取特征并将特征和标签添加到训练数据中。
cv::Mat samples_; cv::Mat samples_;
samples.convertTo(samples_, CV_32F); samples.convertTo(samples_, CV_32F);
cv::Mat train_classes = 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, return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes); train_classes);
} }
// 该部分代码是AnnTrain类的tdata方法用于生成训练数据。
// 首先将samples转换为CV_32F类型的samples_然后初始化train_classes为全零矩阵。
// 接着遍历train_classes的每一行将对应位置的值设为1。
// 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() { cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
assert(chars_folder_); assert(chars_folder_);
@ -322,7 +372,9 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
int classNumber = 0; int classNumber = 0;
if (type == 0) classNumber = kCharsTotalNumber; if (type == 0) classNumber = kCharsTotalNumber;
if (type == 1) classNumber = kChineseNumber; if (type == 1) classNumber = kChineseNumber;
// 这段代码是AnnTrain类的tdata方法用于生成训练数据。
// 首先检查chars_folder_是否存在然后初始化samples和labels。
// 根据type的值确定classNumber。
for (int i = 0; i < classNumber; ++i) { for (int i = 0; i < classNumber; ++i) {
auto char_key = kChars[i + kCharsTotalNumber - classNumber]; auto char_key = kChars[i + kCharsTotalNumber - classNumber];
char sub_folder[512] = {0}; char sub_folder[512] = {0};
@ -340,7 +392,11 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
labels.push_back(i); labels.push_back(i);
} }
} }
// 这段代码是一个循环,遍历每个字符文件夹中的文件,并将文件读取为灰度图像后提取特征并添加到训练数据中。
// 首先根据循环变量i计算出当前字符的关键字char_key并构建对应的子文件夹路径sub_folder。
// 然后使用utils::getFiles函数获取子文件夹中的文件列表chars_files并遍历每个文件。
// 对每个文件使用cv::imread函数读取为灰度图像img然后调用charFeatures2函数提取特征fps并将其添加到samples中。
// 同时将当前字符的标签i添加到labels中。
cv::Mat samples_; cv::Mat samples_;
samples.convertTo(samples_, CV_32F); samples.convertTo(samples_, CV_32F);
cv::Mat train_classes = cv::Mat train_classes =
@ -354,22 +410,6 @@ cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
train_classes); train_classes);
} }
} }
/*这段代码是一个开源项目EasyPR中的一个类AnnTrain的实现。 // 该部分代码是用于生成训练数据的一部分首先将samples转换为CV_32F类型的samples_
// 然后初始化train_classes为全零矩阵接着遍历train_classes的每一行将对应位置的值设为1。
AnnTrainANNXML // 最后使用cv::ml::TrainData::create函数创建并返回训练数据对象。
ANN_MLPann_type01kv_Kv
train()typeXMLtest()
identifyChinese()identify()使pair
test()使
getSyntheticImage()
sdata()
tdata()sdata()
*/

@ -7,7 +7,10 @@ namespace easypr {
assert(img.type() == CV_8UC1); assert(img.type() == CV_8UC1);
int w = img.cols; int w = img.cols;
int h = img.rows; int h = img.rows;
// 该部分代码定义了一个名为getBorderColor的函数用于获取图像边界的颜色值
// 函数首先确保输入图像是单通道灰度图像,然后获取图像的宽度和高度
// 接下来通过遍历图像边界像素的方式计算边界颜色的平均值,并返回整数类型的平均值
// 该文件还包含了一些其他图像处理函数,如平移、旋转、裁剪和生成合成图像的函数
float sum = 0; float sum = 0;
for (int i = 0; i < h; ++i) { for (int i = 0; i < h; ++i) {
sum += img.at<unsigned char>(i, 0); sum += img.at<unsigned char>(i, 0);
@ -21,7 +24,9 @@ namespace easypr {
float avg = sum / float(w + w + h + h); float avg = sum / float(w + w + h + h);
return int(avg); return int(avg);
} }
// 该部分代码计算了图像边界像素的平均值作为边界颜色值,并返回整数类型的平均值
// 首先对图像边界像素进行遍历累加像素值到sum中
// 然后计算平均值avg并返回整数类型的平均值
// shift an image // shift an image
Mat translateImg(Mat img, int offsetx, int offsety, int bk){ Mat translateImg(Mat img, int offsetx, int offsety, int bk){
Mat dst; Mat dst;
@ -31,7 +36,12 @@ namespace easypr {
warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk)); warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk));
return dst; return dst;
} }
// 该部分代码定义了一个名为translateImg的函数用于对图像进行平移操作
// 函数接受输入图像img以及x和y方向的偏移量offsetx和offsety以及背景颜色bk
// 在函数内部首先创建了一个空的目标图像dst
// 然后定义了一个2x3的变换矩阵trans_mat用于表示平移操作
// 最后调用warpAffine函数对输入图像进行平移操作并将结果存储在目标图像dst中
// 最终返回平移后的目标图像dst
// rotate an image // rotate an image
Mat rotateImg(Mat source, float angle, int bk){ Mat rotateImg(Mat source, float angle, int bk){
Point2f src_center(source.cols / 2.0F, source.rows / 2.0F); 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)); warpAffine(source, dst, rot_mat, source.size(), 1, 0, Scalar(bk));
return dst; return dst;
} }
// 该部分代码定义了一个名为rotateImg的函数用于对图像进行旋转操作
// 函数接受输入图像source以及旋转角度angle和背景颜色bk作为参数
// 在函数内部首先计算了输入图像的中心点坐标src_center
// 然后利用getRotationMatrix2D函数计算了旋转矩阵rot_mat
// 最后调用warpAffine函数对输入图像进行旋转操作并将结果存储在目标图像dst中
// 最终返回旋转后的目标图像dst
// crop the image // crop the image
Mat cropImg(Mat src, int x, int y, int shift, int bk){ Mat cropImg(Mat src, int x, int y, int shift, int bk){
int width = src.cols; int width = src.cols;
@ -61,7 +76,13 @@ namespace easypr {
resize(dst, dst, Size(width, height)); resize(dst, dst, Size(width, height));
return dst; return dst;
} }
// 该部分代码定义了一个名为cropImg的函数用于对图像进行裁剪操作
// 函数接受输入图像src以及裁剪起始点的x和y坐标裁剪尺寸shift和背景颜色bk作为参数
// 在函数内部,首先获取输入图像的宽度和高度
// 然后计算裁剪后的图像宽度和高度
// 接着根据裁剪起始点和裁剪尺寸计算裁剪区域的矩形rect
// 将裁剪后的图像存储在目标图像dst中并进行大小调整
// 最终返回裁剪后的目标图像dst
Mat generateSyntheticImage(const Mat& image, int use_swap) { Mat generateSyntheticImage(const Mat& image, int use_swap) {
int rd = rand(); int rd = rand();
int bkColor = getBoderColor(image); int bkColor = getBoderColor(image);
@ -85,4 +106,10 @@ namespace easypr {
return result; return result;
} }
} }
// 该部分代码定义了一个名为generateSyntheticImage的函数用于生成合成图像
// 函数接受输入图像image以及一个整数参数use_swap
// 首先通过rand函数生成一个随机数rd
// 然后调用getBorderColor函数获取输入图像的边界颜色值并存储在bkColor中
// 接着对输入图像进行克隆存储在result中
// 之后根据随机数rd的不同位进行裁剪、平移和旋转操作并将结果存储在result中
// 最终返回合成后的目标图像result

Loading…
Cancel
Save