diff --git a/annCh_train.cpp b/annCh_train.cpp deleted file mode 100644 index fef78cc..0000000 --- a/annCh_train.cpp +++ /dev/null @@ -1,347 +0,0 @@ -#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函数,用于特征提取 - } -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,可能是为了增加一些额外的输入节点 -} - -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; // 设置输出层节点数量 -} - -// 设置神经网络层的大小 -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); - } - } -} - // generate train data - train_samples.convertTo(train_samples, CV_32F); - cv::Mat train_classes = cv::Mat::zeros((int)train_label.size(), classNumber, CV_32F); - for (int i = 0; i < train_classes.rows; ++i) - train_classes.at(i, train_label[i]) = 1.f; - auto train_data = cv::ml::TrainData::create(train_samples, cv::ml::SampleTypes::ROW_SAMPLE, train_classes); - - // train the data, calculate the cost time - std::cout << "Training ANN chinese model, please wait..." << std::endl; - long start = utils::getTimestamp(); - ann_->train(train_data); - long end = utils::getTimestamp(); - ann_->save(ann_xml_); - 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; - - // test the accuracy_rate in train - if (1) { - int corrects_all = 0, sum_all = train_images.size(); - std::cout << "train_images size: " << sum_all << std::endl; - for (size_t i = 0; i < train_images.size(); ++i) { - cv::Mat img = train_images.at(i); - int label = train_label.at(i); - auto char_key = kChars[label + kCharsTotalNumber - classNumber]; - std::pair ch = identifyGrayChinese(img); - if (ch.first == char_key) - corrects_all++; - } - float accuracy_rate = (float)corrects_all / (float)sum_all; - std::cout << "Train error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl; - } - - // test the accuracy_rate in val - if (1) { - int corrects_all = 0, sum_all = val_images.size(); - std::cout << "val_images: " << sum_all << std::endl; - for (size_t i = 0; i < val_images.size(); ++i) { - cv::Mat img = val_images.at(i); - int label = val_labels.at(i); - auto char_key = kChars[label + kCharsTotalNumber - classNumber]; - std::pair ch = identifyGrayChinese(img); - if (ch.first == char_key) - corrects_all++; - } - float accuracy_rate = (float)corrects_all / (float)sum_all; - std::cout << "Test error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl; - } - -} - -cv::Ptr AnnChTrain::tdata() { - assert(chars_folder_); - - cv::Mat samples; - std::vector labels; - - std::cout << "Collecting chars in " << chars_folder_ << std::endl; - - int classNumber = 0; - if (type == 0) classNumber = kCharsTotalNumber; - if (type == 1) 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::cout << " >> Featuring characters " << char_key << " in " - << sub_folder << std::endl; - - auto chars_files = utils::getFiles(sub_folder); - for (auto file : chars_files) { - auto img = cv::imread(file, 0); // a grayscale image - auto fps = charFeatures2(img, kPredictSize); - - samples.push_back(fps); - labels.push_back(i); - } - } - - cv::Mat samples_; - samples.convertTo(samples_, CV_32F); - cv::Mat train_classes = - cv::Mat::zeros((int)labels.size(), classNumber, CV_32F); - - for (int i = 0; i < train_classes.rows; ++i) { - train_classes.at(i, labels[i]) = 1.f; - } - - return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, - train_classes); -} -} diff --git a/src/core/chars_identify.cpp b/src/core/chars_identify.cpp deleted file mode 100644 index a88d727..0000000 --- a/src/core/chars_identify.cpp +++ /dev/null @@ -1,454 +0,0 @@ -#include "easypr/core/chars_identify.h" -#include "easypr/core/character.hpp" -#include "easypr/core/core_func.h" -#include "easypr/core/feature.h" -#include "easypr/core/params.h" -#include "easypr/config.h" - -using namespace cv; - -namespace easypr { - -CharsIdentify* CharsIdentify::instance_ = nullptr; - -CharsIdentify* CharsIdentify::instance() { - if (!instance_) { - instance_ = new CharsIdentify; - } - return instance_; -} - -CharsIdentify::CharsIdentify() { - LOAD_ANN_MODEL(ann_, kDefaultAnnPath); - LOAD_ANN_MODEL(annChinese_, kChineseAnnPath); - LOAD_ANN_MODEL(annGray_, kGrayAnnPath); - - kv_ = std::shared_ptr(new Kv); - kv_->load(kChineseMappingPath); - - extractFeature = getGrayPlusProject; -} - -void CharsIdentify::LoadModel(std::string path) { - if (path != std::string(kDefaultAnnPath)) { - if (!ann_->empty()) - ann_->clear(); - LOAD_ANN_MODEL(ann_, path); - } -} - -void CharsIdentify::LoadChineseModel(std::string path) { - if (path != std::string(kChineseAnnPath)) { - if (!annChinese_->empty()) - annChinese_->clear(); - LOAD_ANN_MODEL(annChinese_, path); - } -} - -void CharsIdentify::LoadGrayChANN(std::string path) { - if (path != std::string(kGrayAnnPath)) { - if (!annGray_->empty()) - annGray_->clear(); - LOAD_ANN_MODEL(annGray_, path); - } -} - -void CharsIdentify::LoadChineseMapping(std::string path) { - kv_->clear(); - kv_->load(path); -} - -void CharsIdentify::classify(cv::Mat featureRows, std::vector& out_maxIndexs, - std::vector& out_maxVals, std::vector isChineseVec){ - int rowNum = featureRows.rows; - - cv::Mat output(rowNum, kCharsTotalNumber, CV_32FC1); - ann_->predict(featureRows, output); - - for (int output_index = 0; output_index < rowNum; output_index++) { - Mat output_row = output.row(output_index); - int result = 0; - float maxVal = -2.f; - bool isChinses = isChineseVec[output_index]; - if (!isChinses) { - result = 0; - for (int j = 0; j < kCharactersNumber; j++) { - float val = output_row.at(j); - // std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - } - else { - result = kCharactersNumber; - for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { - float val = output_row.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - } - out_maxIndexs[output_index] = result; - out_maxVals[output_index] = maxVal; - } -} - - -void CharsIdentify::classify(std::vector& charVec){ - size_t charVecSize = charVec.size(); - - if (charVecSize == 0) - return; - - Mat featureRows; - for (size_t index = 0; index < charVecSize; index++) { - Mat charInput = charVec[index].getCharacterMat(); - Mat feature = charFeatures(charInput, kPredictSize); - featureRows.push_back(feature); - } - - cv::Mat output(charVecSize, kCharsTotalNumber, CV_32FC1); - ann_->predict(featureRows, output); - - for (size_t output_index = 0; output_index < charVecSize; output_index++) { - CCharacter& character = charVec[output_index]; - Mat output_row = output.row(output_index); - - int result = 0; - float maxVal = -2.f; - std::string label = ""; - - bool isChinses = character.getIsChinese(); - if (!isChinses) { - result = 0; - for (int j = 0; j < kCharactersNumber; j++) { - float val = output_row.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - label = std::make_pair(kChars[result], kChars[result]).second; - } - else { - result = kCharactersNumber; - for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { - float val = output_row.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - const char* key = kChars[result]; - std::string s = key; - std::string province = kv_->get(s); - label = std::make_pair(s, province).second; - } - /*std::cout << "result:" << result << std::endl; - std::cout << "maxVal:" << maxVal << std::endl;*/ - character.setCharacterScore(maxVal); - character.setCharacterStr(label); - } -} - - -void CharsIdentify::classifyChineseGray(std::vector& charVec){ - size_t charVecSize = charVec.size(); - if (charVecSize == 0) - return; - - Mat featureRows; - for (size_t index = 0; index < charVecSize; index++) { - Mat charInput = charVec[index].getCharacterMat(); - cv::Mat feature; - extractFeature(charInput, feature); - featureRows.push_back(feature); - } - - cv::Mat output(charVecSize, kChineseNumber, CV_32FC1); - annGray_->predict(featureRows, output); - - for (size_t output_index = 0; output_index < charVecSize; output_index++) { - CCharacter& character = charVec[output_index]; - Mat output_row = output.row(output_index); - bool isChinese = true; - - float maxVal = -2; - int result = 0; - - for (int j = 0; j < kChineseNumber; j++) { - float val = output_row.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - - // no match - if (-1 == result) { - result = 0; - maxVal = 0; - isChinese = false; - } - - auto index = result + kCharsTotalNumber - kChineseNumber; - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - - /*std::cout << "result:" << result << std::endl; - std::cout << "maxVal:" << maxVal << std::endl;*/ - - character.setCharacterScore(maxVal); - character.setCharacterStr(province); - character.setIsChinese(isChinese); - } -} - -void CharsIdentify::classifyChinese(std::vector& charVec){ - size_t charVecSize = charVec.size(); - - if (charVecSize == 0) - return; - - Mat featureRows; - for (size_t index = 0; index < charVecSize; index++) { - Mat charInput = charVec[index].getCharacterMat(); - Mat feature = charFeatures(charInput, kChineseSize); - featureRows.push_back(feature); - } - - cv::Mat output(charVecSize, kChineseNumber, CV_32FC1); - annChinese_->predict(featureRows, output); - - for (size_t output_index = 0; output_index < charVecSize; output_index++) { - CCharacter& character = charVec[output_index]; - Mat output_row = output.row(output_index); - bool isChinese = true; - - float maxVal = -2; - int result = 0; - - for (int j = 0; j < kChineseNumber; j++) { - float val = output_row.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - - // no match - if (-1 == result) { - result = 0; - maxVal = 0; - isChinese = false; - } - - auto index = result + kCharsTotalNumber - kChineseNumber; - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - - /*std::cout << "result:" << result << std::endl; - std::cout << "maxVal:" << maxVal << std::endl;*/ - - character.setCharacterScore(maxVal); - character.setCharacterStr(province); - character.setIsChinese(isChinese); - } -} - -int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){ - int result = 0; - - cv::Mat output(1, kCharsTotalNumber, CV_32FC1); - ann_->predict(f, output); - - maxVal = -2.f; - if (!isChinses) { - if (!isAlphabet) { - result = 0; - for (int j = 0; j < kCharactersNumber; j++) { - float val = output.at(j); - // std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - } - else { - result = 0; - // begin with 11th char, which is 'A' - for (int j = 10; j < kCharactersNumber; j++) { - float val = output.at(j); - // std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - } - } - else { - result = kCharactersNumber; - for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) { - float val = output.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - } - //std::cout << "maxVal:" << maxVal << std::endl; - return result; -} - -bool CharsIdentify::isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese) { - cv::Mat feature = charFeatures(input, kPredictSize); - auto index = static_cast(classify(feature, maxVal, isChinese)); - - if (isChinese) { - //std::cout << "maxVal:" << maxVal << std::endl; - } - - float chineseMaxThresh = 0.2f; - - if (maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)) { - if (index < kCharactersNumber) { - label = std::make_pair(kChars[index], kChars[index]).second; - } - else { - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - label = std::make_pair(s, province).second; - } - return true; - } - else - return false; -} - -std::pair CharsIdentify::identifyChinese(cv::Mat input, float& out, bool& isChinese) { - cv::Mat feature = charFeatures(input, kChineseSize); - float maxVal = -2; - int result = 0; - - cv::Mat output(1, kChineseNumber, CV_32FC1); - annChinese_->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; - } - } - - // no match - if (-1 == result) { - result = 0; - maxVal = 0; - isChinese = false; - } - else if (maxVal > 0.9){ - isChinese = true; - } - - auto index = result + kCharsTotalNumber - kChineseNumber; - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - out = maxVal; - - return std::make_pair(s, province); -} - -std::pair CharsIdentify::identifyChineseGray(cv::Mat input, float& out, bool& isChinese) { - cv::Mat feature; - extractFeature(input, feature); - float maxVal = -2; - int result = 0; - cv::Mat output(1, kChineseNumber, CV_32FC1); - annGray_->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; - } - } - // no match - if (-1 == result) { - result = 0; - maxVal = 0; - isChinese = false; - } else if (maxVal > 0.9){ - isChinese = true; - } - auto index = result + kCharsTotalNumber - kChineseNumber; - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - out = maxVal; - return std::make_pair(s, province); -} - - -std::pair CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) { - cv::Mat feature = charFeatures(input, kPredictSize); - float maxVal = -2; - auto index = static_cast(classify(feature, maxVal, isChinese, isAlphabet)); - if (index < kCharactersNumber) { - return std::make_pair(kChars[index], kChars[index]); - } - else { - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - return std::make_pair(s, province); - } -} - -int CharsIdentify::identify(std::vector inputs, std::vector>& outputs, - std::vector isChineseVec) { - Mat featureRows; - size_t input_size = inputs.size(); - for (size_t i = 0; i < input_size; i++) { - Mat input = inputs[i]; - cv::Mat feature = charFeatures(input, kPredictSize); - featureRows.push_back(feature); - } - - std::vector maxIndexs; - std::vector maxVals; - classify(featureRows, maxIndexs, maxVals, isChineseVec); - - for (size_t row_index = 0; row_index < input_size; row_index++) { - int index = maxIndexs[row_index]; - if (index < kCharactersNumber) { - outputs[row_index] = std::make_pair(kChars[index], kChars[index]); - } - else { - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - outputs[row_index] = std::make_pair(s, province); - } - } - return 0; -} -} diff --git a/src/core/chars_recognise.cpp b/src/core/chars_recognise.cpp deleted file mode 100644 index d101fd3..0000000 --- a/src/core/chars_recognise.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "easypr/core/chars_recognise.h" -#include "easypr/core/character.hpp" -#include "easypr/util/util.h" -#include - -namespace easypr { - -CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); } - -CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); } - -int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) { - std::vector matChars; - int result = m_charsSegment->charsSegment(plate, matChars); - if (result == 0) { - int num = matChars.size(); - for (int j = 0; j < num; j++) - { - Mat charMat = matChars.at(j); - bool isChinses = false; - float maxVal = 0; - if (j == 0) { - bool judge = true; - isChinses = true; - auto character = CharsIdentify::instance()->identifyChinese(charMat, maxVal, judge); - plateLicense.append(character.second); - } - else { - isChinses = false; - auto character = CharsIdentify::instance()->identify(charMat, isChinses); - plateLicense.append(character.second); - } - } - - } - if (plateLicense.size() < 7) { - return -1; - } - - return result; -} - - -int CCharsRecognise::charsRecognise(CPlate& plate, std::string& plateLicense) { - std::vector matChars; - std::vector grayChars; - Mat plateMat = plate.getPlateMat(); - if (0) writeTempImage(plateMat, "plateMat/plate"); - Color color; - if (plate.getPlateLocateType() == CMSER) { - color = plate.getPlateColor(); - } - else { - int w = plateMat.cols; - int h = plateMat.rows; - Mat tmpMat = plateMat(Rect_(w * 0.1, h * 0.1, w * 0.8, h * 0.8)); - color = getPlateType(tmpMat, true); - } - - int result = m_charsSegment->charsSegmentUsingOSTU(plateMat, matChars, grayChars, color); - - if (result == 0) { - int num = matChars.size(); - for (int j = 0; j < num; j++) - { - Mat charMat = matChars.at(j); - Mat grayChar = grayChars.at(j); - if (color != Color::BLUE) - grayChar = 255 - grayChar; - - bool isChinses = false; - std::pair character; - float maxVal; - if (0 == j) { - isChinses = true; - bool judge = true; - character = CharsIdentify::instance()->identifyChineseGray(grayChar, maxVal, judge); - plateLicense.append(character.second); - - // set plate chinese mat and str - plate.setChineseMat(grayChar); - plate.setChineseKey(character.first); - if (0) writeTempImage(grayChar, "char_data/" + character.first + "/chars_"); - } - else if (1 == j) { - isChinses = false; - bool isAbc = true; - character = CharsIdentify::instance()->identify(charMat, isChinses, isAbc); - plateLicense.append(character.second); - } - else { - isChinses = false; - SHOW_IMAGE(charMat, 0); - character = CharsIdentify::instance()->identify(charMat, isChinses); - plateLicense.append(character.second); - } - - CCharacter charResult; - charResult.setCharacterMat(charMat); - charResult.setCharacterGrayMat(grayChar); - if (isChinses) - charResult.setCharacterStr(character.first); - else - charResult.setCharacterStr(character.second); - - plate.addReutCharacter(charResult); - } - if (plateLicense.size() < 7) { - return -1; - } - } - - return result; -} - - -} \ No newline at end of file diff --git a/src/core/chars_segment.cpp b/src/core/chars_segment.cpp deleted file mode 100644 index c40d38e..0000000 --- a/src/core/chars_segment.cpp +++ /dev/null @@ -1,1169 +0,0 @@ -#include "easypr/core/chars_segment.h" -#include "easypr/core/chars_identify.h" -#include "easypr/core/core_func.h" -#include "easypr/core/params.h" -#include "easypr/config.h" -#include "thirdparty/mser/mser2.hpp" - -namespace easypr { - -const float DEFAULT_BLUEPERCEMT = 0.3f;//定义蓝色百分比 -const float DEFAULT_WHITEPERCEMT = 0.1f;//定义白色百分比 - -CCharsSegment::CCharsSegment() { - m_LiuDingSize = DEFAULT_LIUDING_SIZE; - m_theMatWidth = DEFAULT_MAT_WIDTH; - - m_ColorThreshold = DEFAULT_COLORTHRESHOLD; - m_BluePercent = DEFAULT_BLUEPERCEMT; - m_WhitePercent = DEFAULT_WHITEPERCEMT; - - m_debug = DEFAULT_DEBUG; -}//设置几个类的成员变量,进行初始化 - - -bool CCharsSegment::verifyCharSizes(Mat r) { - // Char sizes 45x90 - //接收一个OpenCV的Mat对象作为参数,将其赋值给r - float aspect = 45.0f / 90.0f;//预期的字符宽高比 - float charAspect = (float)r.cols / (float)r.rows; - //输入图像的宽高比,通过计算输入图像的列数和行数得出。 - float error = 0.7f;//允许的宽高比误差 - float minHeight = 10.f;//字符最小高度 - float maxHeight = 35.f;//字符最大高度 - // We have a different aspect ratio for number 1, and it can be ~0.2 - float minAspect = 0.05f//最小允许宽高比 - float maxAspect = aspect + aspect * error - //最大允许宽高比,由预期的宽高比加上其误差得出 - // area of pixels - int area = cv::countNonZero(r); - // 输入图像的非零像素数 - int bbArea = r.cols * r.rows; - //% of pixel in area - //输入图像的总面积,通过乘以其列数和行数得出 - int percPixels = area / bbArea; - //非零像素数在总面积中的比例 - if (percPixels <= 1 && charAspect > minAspect && charAspect < maxAspect && - r.rows >= minHeight && r.rows < maxHeight) - return true; - else - return false; -}//判断输入图像是否可以被视为一个字符,满足特定的字符尺寸和形状要求 - - -Mat CCharsSegment::preprocessChar(Mat in) { - // Remap image - //接收一个Mat对象作为输入,它对输入的图像进行一些预处理操作。 - int h = in.rows; - int w = in.cols; - //获取输入图像的高度和宽度 - int charSize = CHAR_SIZE; - - Mat transformMat = Mat::eye(2, 3, CV_32F); - //定义一个2x3的单位矩阵作为变换矩阵,这个矩阵用于图像的几何变换。 - int m = max(w, h); - - transformMat.at(0, 2) = float(m / 2 - w / 2); - transformMat.at(1, 2) = float(m / 2 - h / 2); - //根据输入图像的最大尺寸,调整变换矩阵的中央值, - // 以便将图像中心置于新图像的中心。 - Mat warpImage(m, m, in.type()); - warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, - BORDER_CONSTANT, Scalar(0)); - - Mat out; - //warpAffine函数应用仿射变换,生成新的变换后的图像。 - resize(warpImage, out, Size(charSize, charSize)); - //resize函数调整新图像的尺寸至预设的字符大小。 - return out; -} - - -//! choose the bese threshold method for chinese -void CCharsSegment::judgeChinese(Mat in, Mat& out, Color plateType) -{ //接收一个Mat对象作为输入,并对其进行阈值处理以识别中文字符 - Mat auxRoi = in; - //表示输入图像的一个子区域,其中可能包含要识别的中文字符 - float valOstu = -1.f, valAdap = -1.f; - Mat roiOstu, roiAdap; - //为Otsu阈值法和自适应阈值法分别定义了一个变量valOstu和valAdap, - // 用于存储阈值 - bool isChinese = true; - if (1) { - if (BLUE == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); - } - else if (YELLOW == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); - } - else if (WHITE == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); - } - else { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - } - //根据输入的颜色类型,使用不同的阈值处理方法对auxRoi进行阈值处理 - // 生成二值化的图像roiOstu - roiOstu = preprocessChar(roiOstu); - //对二值化的图像进行字符识别预处理 - //调用preprocessChar(roiOstu)方法对图像进行进一步处理 - if (0) { - imshow("roiOstu", roiOstu); - waitKey(0); - destroyWindow("roiOstu"); - } - auto character = CharsIdentify::instance()->identifyChinese(roiOstu, valOstu, isChinese); - //对预处理后的图像进行字符识别,并返回识别的结果和阈值。 - } - if (1) { - if (BLUE == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); - //调用图像处理函数对图像进行自适应阈值处理。 - } - else if (YELLOW == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); - } - else if (WHITE == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); - } - else { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); - } - roiAdap = preprocessChar(roiAdap); - //对二值化后的图像进行预处理字符操作。 - auto character = CharsIdentify::instance()->identifyChinese(roiAdap, valAdap, isChinese); - } - - //std::cout << "valOstu: " << valOstu << std::endl; - //std::cout << "valAdap: " << valAdap << std::endl; - - if (valOstu >= valAdap) { - out = roiOstu; - } - else { - out = roiAdap; - } -} - -void CCharsSegment::judgeChineseGray(Mat in, Mat& out, Color plateType) { - out = in;//复制输入图像 -} - -bool slideChineseWindow(Mat& image, Rect mr, Mat& newRoi, Color plateType, float slideLengthRatio, bool useAdapThreshold) { - std::vector charCandidateVec; - //用于存储候选的中文字符。 - Rect maxrect = mr; - Point tlPoint = mr.tl(); - //获取mr的左上角点,并赋值给tlPoint - bool isChinese = true;//标记图像中是否含有中文字符 - int slideLength = int(slideLengthRatio * maxrect.width); - int slideStep = 1;//控制滑动窗口的步长 - int fromX = 0;//指定从哪个位置开始滑动窗口 - fromX = tlPoint.x;//实际的起始位置是左上角的x坐标 - - for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { - float x_slide = 0; - - x_slide = float(fromX + slideX); - - float y_slide = (float)tlPoint.y; - //用来计算滑动窗口的位置。 - Point2f p_slide(x_slide, y_slide); - - //cv::circle(image, p_slide, 2, Scalar(255), 1); - - int chineseWidth = int(maxrect.width); - int chineseHeight = int(maxrect.height); - //宽度和高度 - Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight)); - - if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) - continue; - //检查新定义的矩形区域是否在图像的边界内。如果超出了边界,就跳过这次循环。 - Mat auxRoi = image(rect); - //原始图像中提取出这个矩形区域的子图像auxRoi - Mat roiOstu, roiAdap; - if (1) { - if (BLUE == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); - } - else if (YELLOW == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); - } - else if (WHITE == plateType) { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); - } - else { - threshold(auxRoi, roiOstu, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - } - //根据预设的颜色,使用不同的阈值处理方法对提取出的子图像进行阈值处理。 - // 处理后的图像分别保存到roiOstu和roiAdap。 - roiOstu = preprocessChar(roiOstu, kChineseSize); - - CCharacter charCandidateOstu; - charCandidateOstu.setCharacterPos(rect); // 设置字符的位置信息 - charCandidateOstu.setCharacterMat(roiOstu);// 设置字符的图像信息 - charCandidateOstu.setIsChinese(isChinese);// 设置字符是否为中文字符的信息 - charCandidateVec.push_back(charCandidateOstu);// 将字符信息添加到字符候选向量中以备后续处理使用。 - } - if (useAdapThreshold) { - if (BLUE == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); - } - else if (YELLOW == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); - } - else if (WHITE == plateType) { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY_INV, 3, 0); - } - else { - adaptiveThreshold(auxRoi, roiAdap, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 3, 0); - } - roiAdap = preprocessChar(roiAdap, kChineseSize); - //对图像进行阈值处理以获得二值化的图像。处理后的图像保存在roiAdap中。 - CCharacter charCandidateAdap; - charCandidateAdap.setCharacterPos(rect); - charCandidateAdap.setCharacterMat(roiAdap); - charCandidateAdap.setIsChinese(isChinese); - charCandidateVec.push_back(charCandidateAdap); - } - - } - - CharsIdentify::instance()->classifyChinese(charCandidateVec); - - double overlapThresh = 0.1; - NMStoCharacter(charCandidateVec, overlapThresh); - //使用了非极大值抑制(NMS)算法,它通过计算每个字符候选区域与其它所有字符候选区域的交并比 - // 将交并比低于某个阈值的字符候选区域去除 - if (charCandidateVec.size() >= 1) { - std::sort(charCandidateVec.begin(), charCandidateVec.end(), - [](const CCharacter& r1, const CCharacter& r2) { - return r1.getCharacterScore() > r2.getCharacterScore(); - }); - - newRoi = charCandidateVec.at(0).getCharacterMat(); - return true; - } - //如果生成的字符候选向量中至少有一个元素, - // 则将得分最高的字符候选区域的图像提取出来,并返回true;否则返回false。 - return false; -} - -bool slideChineseGrayWindow(const Mat& image, Rect& mr, Mat& newRoi, Color plateType, float slideLengthRatio) { - std::vector charCandidateVec; - // 定义一个向量来保存字符候选 - Rect maxrect = mr; - Point tlPoint = mr.tl(); - // 获取给定的最大矩形区域的左上角点和宽度 - bool isChinese = true; - // 默认假设它是中文字符 - int slideLength = int(slideLengthRatio * maxrect.width); - int slideStep = 1; - int fromX = 0; - fromX = tlPoint.x; - // 根据最大矩形的宽度计算滑动窗口的长度和步长 - for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { - // 在指定的范围内进行滑动窗口操作 - float x_slide = 0; - x_slide = float(fromX + slideX); - - float y_slide = (float)tlPoint.y; - // 计算当前滑动的x和y坐标 - int chineseWidth = int(maxrect.width); - int chineseHeight = int(maxrect.height); - // 获取中国字符的宽度和高度 - Rect rect(Point2f(x_slide, y_slide), Size(chineseWidth, chineseHeight)); - // 根据当前滑动的坐标和中国字符的尺寸创建一个矩形区域 - if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) - continue; - // 检查这个矩形是否在图像内,如果不在,则跳过当前的循环迭代 - Mat auxRoi = image(rect); - Mat grayChinese; - grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - // 创建一个灰度图像,尺寸为预设的中文字符的尺寸和通道数 - resize(auxRoi, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); - // 将提取的子图像调整为预设的中文字符的尺寸,并将其保存到灰度图像中 - // 从图像中提取当前矩形区域的子图像 - CCharacter charCandidateOstu; - charCandidateOstu.setCharacterPos(rect); - charCandidateOstu.setCharacterMat(grayChinese); - charCandidateOstu.setIsChinese(isChinese); - charCandidateVec.push_back(charCandidateOstu);// 将字符候选对象添加到向量中以备后续处理使用 - // 创建一个字符候选对象,并设置其位置、图像和其他属性(这里指定为中文字符) - } - - CharsIdentify::instance()->classifyChineseGray(charCandidateVec); - // 对所有的字符候选进行分类(这里假设是中文字符分类) - double overlapThresh = 0.1; - NMStoCharacter(charCandidateVec, overlapThresh); - // 对所有的字符候选进行非极大值抑制(NMS)以消除多余的字符候选区域, - // 这里的阈值设置为0.1(根据实际情况可能需要进行调整) - if (charCandidateVec.size() >= 1) { - std::sort(charCandidateVec.begin(), charCandidateVec.end(), - [](const CCharacter& r1, const CCharacter& r2) { - return r1.getCharacterScore() > r2.getCharacterScore(); - }); - - newRoi = charCandidateVec.at(0).getCharacterMat(); - mr = charCandidateVec.at(0).getCharacterPos(); - return true; - } - // 如果字符候选向量中至少有一个元素,则对它们进行排序, - // 并提取出得分最高的字符候选区域,将其图像保存到newRoi中, - // 并更新mr为最高得分字符候选的位置,然后返回true;否则返回false。 - // 注意这里使用了一个lambda表达式作为排序函数, - // 根据字符候选的得分进行降序排序。 - return false; -} - - -int CCharsSegment::charsSegment(Mat input, vector& resultVec, Color color) { - if (!input.data) return 0x01; - //检查输入图像是否有数据 - Color plateType = color; - - Mat input_grey; //存储灰度图像。 - cvtColor(input, input_grey, CV_BGR2GRAY); - //将输入图像转换为灰度图像,并保存到input_grey中。 - if (0) { - imshow("plate", input_grey); - waitKey(0); - destroyWindow("plate"); - } - - Mat img_threshold; - - img_threshold = input_grey.clone(); - spatial_ostu(img_threshold, 8, 2, plateType); - //对图像进行Otsu阈值分割 - - if (0) { - imshow("plate", img_threshold); - waitKey(0); - destroyWindow("plate"); - } - - // remove liuding and hor lines - // also judge weather is plate use jump count - if (!clearLiuDing(img_threshold)) return 0x02; - //清除图像中的一些无效区域 - Mat img_contours; - img_threshold.copyTo(img_contours); - //将img_threshold复制到img_contours中。 - vector > contours; - //定义一个二维向量contours,用于保存轮廓信息。 - findContours(img_contours, - contours, // a vector of contours - CV_RETR_EXTERNAL, // retrieve the external contours - CV_CHAIN_APPROX_NONE); // all pixels of each contours - //使用OpenCV的findContours函数查找图像中的轮廓。 - // 这里指定了检索模式为外部轮廓,并且指定了轮廓的近似方法。 - vector >::iterator itc = contours.begin(); - //定义一个迭代器itc,并初始化为轮廓向量的起始位置 - vector vecRect; - //定义一个向量vecRect,用于保存每个轮廓的边界框。 - while (itc != contours.end()) {//遍历轮廓向量 - Rect mr = boundingRect(Mat(*itc)); - //对于当前轮廓,使用OpenCV的boundingRect函数计算其边界框, - // 并保存到变量mr中。 - Mat auxRoi(img_threshold, mr); - //根据当前轮廓的边界框,从图像中提取一个子图像。 - if (verifyCharSizes(auxRoi)) vecRect.push_back(mr); - ++itc; - } - - - if (vecRect.size() == 0) return 0x03; - - vector sortedRect(vecRect); - //创建一个新的向量sortedRect,并将vecRect的内容复制到这个新的向量中。 - std::sort(sortedRect.begin(), sortedRect.end(), - [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); - //使用lambda函数将sortedRect按照x坐标从小到大进行排序。 - size_t specIndex = 0; - - specIndex = GetSpecificRect(sortedRect); - //获取特定的矩形 - Rect chineseRect; - if (specIndex < sortedRect.size()) - chineseRect = GetChineseRect(sortedRect[specIndex]); - //获取特定索引的矩形并存储到chineseRect - else - return 0x04; - - if (0) { - rectangle(img_threshold, chineseRect, Scalar(255)); - imshow("plate", img_threshold); - waitKey(0); - destroyWindow("plate"); - } - //绘制矩形chineseRect在图像上并显示出来 - //这部分代码永远不会被执行 - - vector newSortedRect; - newSortedRect.push_back(chineseRect); - RebuildRect(sortedRect, newSortedRect, specIndex); - //对图像中的矩形区域进行重新构建或处理的。 - if (newSortedRect.size() == 0) return 0x05; - - bool useSlideWindow = true; - bool useAdapThreshold = true; - //bool useAdapThreshold = CParams::instance()->getParam1b(); - - for (size_t i = 0; i < newSortedRect.size(); i++) { - //循环遍历新的排序矩形向量中的每一个矩形。 - Rect mr = newSortedRect[i]; - //获取当前矩形的坐标信息 - // Mat auxRoi(img_threshold, mr); - Mat auxRoi(input_grey, mr); - //根据给定的图像和矩形区域创建ROI - Mat newRoi; - //第二个是用来存储处理后的图像的。 - if (i == 0) { - if (useSlideWindow) { - float slideLengthRatio = 0.1f; - //float slideLengthRatio = CParams::instance()->getParam1f(); - if (!slideChineseWindow(input_grey, mr, newRoi, plateType, slideLengthRatio, useAdapThreshold)) - judgeChinese(auxRoi, newRoi, plateType); - } - else - judgeChinese(auxRoi, newRoi, plateType); - } - else { - if (BLUE == plateType) { - threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); - } - else if (YELLOW == plateType) { - threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); - } - else if (WHITE == plateType) { - threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); - } - else { - threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - } - //根据不同的颜色来执行不同的阈值处理操作, - // 然后对处理后的图像进行预处 - newRoi = preprocessChar(newRoi); - } - - if (0) { - if (i == 0) { - imshow("input_grey", input_grey); - // 使用imshow函数显示名为"input_grey"的窗口,并在其中显示图像"input_grey"。 - // 用于创建和显示窗口。 - - waitKey(0); - //这是调用OpenCV的waitKey函数,等待用户按键。 - // 该函数的参数是0,这意味着它将无限期地等待用户按键。 - // 一旦用户按下键,该函数将返回按下的键的ASCII值。 - - destroyWindow("input_grey"); - //这个函数会销毁窗口并释放其内存。 - } - if (i == 0) { - imshow("newRoi", newRoi); - waitKey(0); - destroyWindow("newRoi"); - } - } - - resultVec.push_back(newRoi); - // 将新的Roi添加到名为resultVec的向量中。 - // 存储一系列元素。这里将新的Roi图像添加到该向量中。 - - } - - return 0; -} - -int CCharsSegment::projectSegment(const Mat& input, Color color, vector& out_indexs) { - if (!input.data) return 0x01; - - Color plateType = color; // 将输入的颜色赋值给plateType - Mat input_grey; // 定义一个Mat对象用于存储灰度图像 - cvtColor(input, input_grey, CV_BGR2GRAY); // 将输入图像转换为灰度图像 - SHOW_IMAGE(input_grey, 0); // 显示灰度图像 - - Mat img_threshold; // 定义一个Mat对象用于存储阈值化的图像 - img_threshold = input_grey.clone(); // 将灰度图像复制到img_threshold中 - spatial_ostu(img_threshold, 8, 2, plateType); // 对图像进行空间自适应阈值化处理, - SHOW_IMAGE(img_threshold, 0); // 显示阈值化后的图像 - - // remove liuding and hor lines - // also judge weather is plate use jump count - // 进行一些形状的清理和判断是否为车牌的跳跃计数 - if (!clearLiuDing(img_threshold)) return 0x02; - // 使用clearLiuDing函数清理图像中的多余部分, - SHOW_IMAGE(img_threshold, 0); - // 显示清理后的图像 - - Mat vhist = ProjectedHistogram(img_threshold, VERTICAL, 0); - // 对图像进行垂直直方图投影,结果保存在vhist中 - Mat showHist = showHistogram(vhist); - // 显示直方图,但是此行代码在后续的代码中并没有被使用 - SHOW_IMAGE(showHist, 1); // 显示直方图 - - vector values; // 定义一个向量用于存储每个像素到直方图最大值的距离 - vector indexs; // 定义一个向量用于存储非最大值的索引 - int size = vhist.cols; // 获取直方图列的数量,作为后续循环的次数 - - for (int i = 0; i < size; i++) { // 遍历每个像素在直方图中的位置 - float val = vhist.at(i); // 获取当前像素在直方图中的值 - values.push_back(1.f - val); - // 计算当前像素到直方图最大值的距离,并添加到values向量中 - } - Mat img_test = img_threshold.clone(); // 复制阈值化后的图像到一个新的Mat对象中 - NMSfor1D(values, indexs); // 对values向量进行非极大值抑制(NMS) - - out_indexs.resize(size); // 重新调整out_indexs向量的尺寸,为后续的赋值做准备 - for (int j = 0; j < size; j++) - out_indexs.at(j) = 0; // 将out_indexs向量中的所有值初始化为0 - - for (int i = 0; i < size; i++) { // 遍历每个像素在直方图中的位置 - float val = vhist.at(i); // 获取当前像素在直方图中的值 - if (indexs.at(i) && val < 0.1f) { // 如果当前像素是非极大值且其值小于0.1f - out_indexs.at(i) = 1; // 则将out_indexs向量中的相应位置设置为1 - for (int j = 0; j < img_test.rows; j++) { - img_test.at(j, i) = (char)255; - // 将该位置的像素值设置为255(白色) - } - } - } - SHOW_IMAGE(img_test, 1); - - return 0; -} - -bool verifyCharRectSizes(Rect r) { - // Char sizes 45x90 - float aspect = 45.0f / 90.0f; - //定义一个变量aspect,其值为0.5,表示字符的预期宽高比。 - float charAspect = (float)r.width / (float)r.height; - //计算输入矩形的宽高比,并将其存储在变量charAspect中。 - float error = 0.5f; - //表示允许的宽高比误差。 - float minHeight = kPlateResizeHeight * 0.5f; - //表示矩形的最小高度 - float maxHeight = kPlateResizeHeight * 1.f; - //表示矩形的最大高度 - // We have a different aspect ratio for number 1, and it can be ~0.2 - float minAspect = 0.10f; //0.2f; - //表示允许的最小宽高比 - float maxAspect = 0.85f; // aspect + aspect * error; //0.8f; - //表示允许的最大宽高比 - int ch = r.tl().y + r.height / 2; - //计算矩形的中心点纵坐标 - int min_ch = int(kPlateResizeHeight * 0.3f); - //表示字符中心点的最小纵坐标 - int max_ch = int(kPlateResizeHeight * 0.7f); - //表示字符中心点的最大纵坐标 - if (ch > max_ch || ch < min_ch) - return false; - //如果字符中心点的纵坐标超出允许的范围,则返回false。 - float h = (float)r.height; - //将输入矩形的height转换为浮点数 - if (h > maxHeight || h < minHeight) - return false; - //矩形的height超出允许的范围,则返回false。 - if (charAspect < minAspect || charAspect > maxAspect) - return false; - //如果矩形的宽高比超出允许的范围,则返回false。 - return true; -} - -Mat preprocessCharMat(Mat in, int char_size) { - // Remap image - // 对图像进行映射变换 - int h = in.rows; // 获取输入图像的行数 - int w = in.cols; // 获取输入图像的列数 - - int charSize = char_size; // 将传入的字符大小参数保存为charSize - - // 创建一个2x3的单位矩阵,作为图像变换的转换矩阵 - Mat transformMat = Mat::eye(2, 3, CV_32F); - - // 找出输入图像的最大尺寸(高或宽) - int m = max(w, h); - - // 根据最大尺寸,设置转换矩阵的最后两行(平移参数) - // 这样做的目的是将输入图像的中心移动到变换后的图像的中心 - transformMat.at(0, 2) = float(m / 2 - w / 2); - transformMat.at(1, 2) = float(m / 2 - h / 2); - - // 创建一个与输入图像相同类型和大小的输出图像 - Mat warpImage(m, m, in.type()); - - // 使用上面定义的转换矩阵,对输入图像执行仿射变换(warpAffine) - warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, - BORDER_CONSTANT, Scalar(0)); - - // 根据指定的字符大小,调整变换后的图像大小 - Mat out; - cv::resize(warpImage, out, Size(charSize, charSize)); - - // 返回处理后的图像 - return out; -} - -Mat clearLiuDingAndBorder(const Mat& grayImage, Color color) { - SHOW_IMAGE(grayImage, 0); - //显示输入的灰度图像 - Mat img_threshold; - //定义一个Mat对象img_threshold,用于存放阈值化后的图像。 - img_threshold = grayImage.clone(); - //将输入的灰度图像复制到img_threshold中。 - spatial_ostu(img_threshold, 1, 1, color); - //对图像进行空间自适应阈值化(spatial OSTU), - // 这里的参数1和1可能表示的是高斯核的大小,而color则表示颜色空间。 - clearLiuDing(img_threshold); - //为了去除刘定噪声 - Rect cropRect; - //存放裁剪区域的坐标。 - clearBorder(img_threshold, cropRect); - //为了清除图像的边框 - Mat cropedGrayImage; - //存放裁剪后的灰度图像 - resize(grayImage(cropRect), cropedGrayImage, Size(kPlateResizeWidth, kPlateResizeHeight)); - //对裁剪后的灰度图像进行大小调整。 - SHOW_IMAGE(cropedGrayImage, 0); - //显示调整大小后的灰度图像。 - return cropedGrayImage; - //返回处理后的灰度图像。 -} - -void NMStoCharacterByRatio(std::vector &inVec, double overlap, const Rect groundRect) { - // rechange the score - for (auto& character : inVec) {//对inVec中的每个字符进行操作 - double score = character.getCharacterScore();//获取当前字符的得分 - //cout << "score:" << score << endl; - Rect rect = character.getCharacterPos();//获取当前字符的位置信息 - int w = rect.width; - int h = rect.height; - int gw = groundRect.width; - int gh = groundRect.height; - - float iou = computeIOU(rect, groundRect); - - int w_diff = abs(w - gw); - int h_diff = abs(h - gh); - //计算当前字符框与地面真实框的IOU(交并比) - //float w_ratio = (float)w / (float)gw; - //float h_ratio = (float)h / (float)gh; - - float w_ratio = 1 - (float)w_diff / (float)gw; - float h_ratio = 1 - (float)h_diff / (float)gh; - //分别表示字符框的宽度和高度与地面真实框的宽高比。 - // 这两个比例会影响最终的权重得分。 - float a = 0.5f; - float b = 0.5f; - //cout << "str:" << character.getCharacterStr() << endl; - // if the charater is '1', its probalilty is redcued by its iou - if ("1" == character.getCharacterStr()) { - a = 0.3f; //0.2f; - b = 0.7f; //0.8f; - //如果字符是'1',那么会对其IOU进行一个调整。 - } - float c = 0.1f; - //float weighted_score = a * (float)score + b * w_ratio + c * h_ratio; - float weighted_score = a * (float)score + b * w_ratio + c * h_ratio; - //计算权重得分 - SHOW_IMAGE(character.getCharacterMat(), 0); - character.setCharacterScore((double)weighted_score); - //设置新的权重得分。 - //cout << "weighted_score:" << character.getCharacterScore() << endl; - } - - std::sort(inVec.begin(), inVec.end()); - //对vector进行排序,以便后续的NMS操作。 - std::vector::iterator it = inVec.begin(); - for (; it != inVec.end(); ++it) { - CCharacter charSrc = *it; - // cout << "charScore:" << charSrc.getCharacterScore() << endl; - Rect rectSrc = charSrc.getCharacterPos(); - std::vector::iterator itc = it + 1; - - for (; itc != inVec.end();) { - CCharacter charComp = *itc; - Rect rectComp = charComp.getCharacterPos(); - float iou = computeIOU(rectSrc, rectComp); - - if (iou > overlap) { - itc = inVec.erase(itc); - } - else { - ++itc; - } - } - }//如果它与后面的字符的IOU大于预设的重叠阈值,那么就将后面的字符从vector中删除。 - //这样可以保证最终vector中每一个字符都不与其它字符有大的重叠 -} - -int getNearestIndex(Point center, const vector& groundCenters) { - int gc_size = int(groundCenters.size()); - //获取 groundCenters 向量的大小,并将其转换为整数类型并存储在变量 gc_size 中 - int index = 0; - //用于存储最接近 center 的地心坐标的索引 - int min_length = INT_MAX; - //用于存储与 center 距离最短的地心坐标的距离的平方 - for (int p = 0; p < gc_size; p++) {//遍历所有地心坐标 - Point gc_point = groundCenters.at(p);//获取索引为 p 的地心坐标,并将其存储在变量 gc_point 中。 - int length_square = (gc_point.x - center.x) * (gc_point.x - center.x) + - (gc_point.y - center.y) * (gc_point.y - center.y); - //计算当前地心坐标 gc_point 与 center 之间的距离的平方。 - //int length_square = abs(gc_point.x - center.x); - if (length_square < min_length) { - min_length = length_square; - //当前地心坐标与 center 的距离的平方设置为新的最小距离。 - index = p; - } - } - return index; -} - -int CCharsSegment::charsSegmentUsingMSER(Mat input, vector& resultVec, vector& grayChars, Color color) { - Mat grayImage; - cvtColor(input, grayImage, CV_BGR2GRAY); - std::vector bgrSplit; - split(input, bgrSplit); - - //Mat grayChannel = clearLiuDingAndBorder(grayImage, color); //clearLiuDingAndBorder(grayImage, color); - Mat grayChannel = grayImage; - - // Mat cropedGrayImage = grayImage; - // generate all channgel images; - vector channelImages; - bool useThreeChannel = false; - channelImages.push_back(grayChannel); - if (useThreeChannel) { - for (int i = 0; i < 3; i++) - channelImages.push_back(bgrSplit.at(i)); - } - int csize = channelImages.size(); - - //TODO three channels - std::vector> all_contours; - std::vector all_boxes; - all_contours.reserve(32); - all_boxes.reserve(32); - - const int imageArea = input.rows * input.cols; - const int delta = 1; - const int minArea = 30; - const double maxAreaRatio = 0.2; - - int type = -1; - if (Color::BLUE == color) type = 0; - if (Color::YELLOW == color) type = 1; - if (Color::WHITE == color) type = 1; - if (Color::UNKNOWN == color) type = 0; - - for (int c_index = 0; c_index < csize; c_index++) { - Mat cimage = channelImages.at(c_index); - Mat testImage = cimage.clone(); - cvtColor(testImage, testImage, CV_GRAY2BGR); - - const float plateMaxSymbolCount = kPlateMaxSymbolCount; - const int symbolIndex = kSymbolIndex; - float segmentRatio = plateMaxSymbolCount - int(plateMaxSymbolCount); - const int plateMaxCharCount = int(plateMaxSymbolCount); - - vector> charsVecVec; - charsVecVec.resize(plateMaxCharCount); - - vector groundCenters; - groundCenters.reserve(plateMaxCharCount); - vector groundRects; - groundRects.reserve(plateMaxCharCount); - - // compute the ground char rect - int avg_char_width = int(kPlateResizeWidth * (1.f / plateMaxSymbolCount)); - int avg_char_height = int(kPlateResizeHeight * 0.85f); - - int x_axis = 0; - int y_axis = int((kPlateResizeHeight - avg_char_height) * 0.5f); - for (int j = 0; j < plateMaxSymbolCount; j++) { - int char_width = avg_char_width; - if (j != symbolIndex) char_width = avg_char_width; - else char_width = int(segmentRatio * avg_char_width); - - Rect avg_char_rect = Rect(x_axis, y_axis, char_width, avg_char_height); - rectangle(testImage, avg_char_rect, Scalar(0, 0, 255)); - - Point center = Point(x_axis + int(char_width * 0.5f), y_axis + int(avg_char_height * 0.5f)); - circle(testImage, center, 3, Scalar(0, 255, 0)); - x_axis += char_width; - - if (j != symbolIndex) { - groundCenters.push_back(center); - groundRects.push_back(avg_char_rect); - } - } - SHOW_IMAGE(testImage, 0); - - Mat showImage = cimage.clone(); - cvtColor(showImage, showImage, CV_GRAY2BGR); - Mat mdoImage = cimage.clone(); - string candidateLicense; - - Ptr mser; - // use origin mser to detect as many as possible characters - mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea), false); - mser->detectRegions(cimage, all_contours, all_boxes, type); - - std::vector charVec; - charVec.reserve(16); - size_t size = all_contours.size(); - - int char_index = 0; - int char_size = 20; - - Mat showMSERImage = cimage.clone(); - cvtColor(showMSERImage, showMSERImage, CV_GRAY2BGR); - // verify char size and output to rects; - for (size_t index = 0; index < size; index++) { - Rect rect = all_boxes[index]; - vector &contour = all_contours[index]; - rectangle(showMSERImage, rect, Scalar(0,0,255)); - - // find character - if (verifyCharRectSizes(rect)) { - Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size)); - Mat mserInput = preprocessCharMat(mserMat, char_size); - - Rect charRect = rect; - Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); - Mat tmpMat; - double ostu_level = cv::threshold(cimage(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - Mat grayCharMat = cimage(charRect); - Mat ostuMat; - switch (color) { - case BLUE: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; - case YELLOW: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; - case WHITE: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; - default: threshold(grayCharMat, ostuMat, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; - } - Mat ostuInput = preprocessChar(ostuMat); - // use judegMDOratio2 function to - // remove the small lines in character like "zh-cuan" - if (judegMDOratio2(cimage, rect, contour, mdoImage, 1.2f, true)) { - CCharacter charCandidate; - //cout << contour.size() << endl; - charCandidate.setCharacterPos(charRect); - charCandidate.setCharacterMat(ostuInput); //charInput or ostuInput - charCandidate.setOstuLevel(ostu_level); - charCandidate.setCenterPoint(center); - int pos = getNearestIndex(center, groundCenters); - charsVecVec.at(pos).push_back(charCandidate); - charCandidate.setIndex(pos); - charCandidate.setIsChinese(false); - charVec.push_back(charCandidate); - } - } - else { - SHOW_IMAGE(showMSERImage(rect), 0); - } - } - SHOW_IMAGE(showMSERImage, 0); - SHOW_IMAGE(mdoImage, 0); - - // classify all the images; - CharsIdentify::instance()->classify(charVec); - Rect maxrect = groundRects.at(0); - - // NMS to the seven groud truth rect - bool useGround = true; - if (useGround) { - for (auto charCandidate : charVec) { - int pos = charCandidate.getIndex(); - charsVecVec.at(pos).push_back(charCandidate); - } - charVec.clear(); - for (size_t c = 0; c < charsVecVec.size(); c++) { - Mat testImage_2 = cimage.clone(); - cvtColor(testImage_2, testImage_2, CV_GRAY2BGR); - vector& charPosVec = charsVecVec.at(c); - for (auto character : charPosVec) { - rectangle(testImage_2, character.getCharacterPos(), Scalar(0, 255, 0)); - } - SHOW_IMAGE(testImage_2, 0); - - double overlapThresh = 0.; - NMStoCharacterByRatio(charPosVec, overlapThresh, groundRects.at(c)); - charPosVec.shrink_to_fit(); - - Mat testImage_3 = cimage.clone(); - cvtColor(testImage_3, testImage_3, CV_GRAY2BGR); - for (auto character : charPosVec) { - rectangle(testImage_3, character.getCharacterPos(), Scalar(0, 255, 0)); - } - - // only the last group will contain more than one candidate character - if (charsVecVec.size() - 1 == c) { - for (auto charPos : charPosVec) - charVec.push_back(charPos); - } - else { - if (charPosVec.size() != 0) { - CCharacter& inputChar = charPosVec.at(0); - charVec.push_back(inputChar); - Mat charMat = inputChar.getCharacterMat(); - SHOW_IMAGE(charMat, 0); - } - } - for (auto charPos : charPosVec) { - Rect r = charPos.getCharacterPos(); - if (r.area() > maxrect.area()) - maxrect = r; - } - SHOW_IMAGE(testImage_3, 0); - } - } - else { - NMStoCharacterByRatio(charVec, 0.2f, maxrect); - } - - if (charVec.size() < kCharsCountInOnePlate) return 0x03; - std::sort(charVec.begin(), charVec.end(),[](const CCharacter& r1, const CCharacter& r2) { return r1.getCharacterPos().x < r2.getCharacterPos().x; }); - - string predictLicense = ""; - vector sortedRect; - for (auto charCandidate : charVec) { - sortedRect.push_back(charCandidate.getCharacterPos()); - predictLicense.append(charCandidate.getCharacterStr()); - } - std::sort(sortedRect.begin(), sortedRect.end(), - [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); - cout << "predictLicense: " << predictLicense << endl; - - // find chinese rect - size_t specIndex = 0; - specIndex = GetSpecificRect(sortedRect); - SHOW_IMAGE(showImage(sortedRect[specIndex]), 0); - - Rect chineseRect; - if (specIndex < sortedRect.size()) - chineseRect = GetChineseRect(sortedRect[specIndex]); - else - return 0x04; - - vector newSortedRect; - newSortedRect.push_back(chineseRect); - if (newSortedRect.size() == 0) return 0x05; - - SHOW_IMAGE(showImage(chineseRect), 0); - RebuildRect(sortedRect, newSortedRect, specIndex); - - Mat theImage = channelImages.at(c_index); - for (size_t i = 0; i < newSortedRect.size(); i++) { - Rect mr = newSortedRect[i]; - //mr = rectEnlarge(newSortedRect[i], cimage.cols, cimage.rows); - Mat auxRoi(theImage, mr); - Mat newRoi; - if (i == 0) { - //Rect large_mr = rectEnlarge(mr, theImage.cols, theImage.rows); - Rect large_mr = mr; - Mat grayChar(theImage, large_mr); - Mat grayChinese; - grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(grayChar, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); - - Mat newChineseRoi; - if (1) { - float slideLengthRatio = 0.1f; - if (!slideChineseGrayWindow(theImage, large_mr, newChineseRoi, color, slideLengthRatio)) - judgeChineseGray(grayChinese, newChineseRoi, color); - } - grayChars.push_back(newChineseRoi); - } - else { - switch (color) { - case BLUE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; - case YELLOW: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; - case WHITE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; - default: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; - } - newRoi = preprocessChar(newRoi); - Rect fit_mr = rectFit(mr, cimage.cols, cimage.rows); - Mat grayChar(cimage, fit_mr); - grayChars.push_back(grayChar); - } - - rectangle(showImage, mr, Scalar(0, 0, 255), 1); - resultVec.push_back(newRoi); - } - SHOW_IMAGE(showImage, 0); - } - - return 0; -} - - -int CCharsSegment::charsSegmentUsingOSTU(Mat input, vector& resultVec, vector& grayChars, Color color) { - if (!input.data) return 0x01; - - Color plateType = color; - Mat input_grey; - cvtColor(input, input_grey, CV_BGR2GRAY); - - Mat img_threshold; - img_threshold = input_grey.clone(); - spatial_ostu(img_threshold, 8, 2, plateType); - - // remove liuding and hor lines, also judge weather is plate use jump count - if (!clearLiuDing(img_threshold)) return 0x02; - - Mat img_contours; - img_threshold.copyTo(img_contours); - - vector > contours; - findContours(img_contours, - contours, // a vector of contours - CV_RETR_EXTERNAL, // retrieve the external contours - CV_CHAIN_APPROX_NONE); // all pixels of each contours - - vector >::iterator itc = contours.begin(); - vector vecRect; - while (itc != contours.end()) { - Rect mr = boundingRect(Mat(*itc)); - Mat auxRoi(img_threshold, mr); - if (verifyCharSizes(auxRoi)) - vecRect.push_back(mr); - ++itc; - } - - if (vecRect.size() == 0) return 0x03; - - vector sortedRect(vecRect); - std::sort(sortedRect.begin(), sortedRect.end(), - [](const Rect& r1, const Rect& r2) { return r1.x < r2.x; }); - - size_t specIndex = 0; - specIndex = GetSpecificRect(sortedRect); - - Rect chineseRect; - if (specIndex < sortedRect.size()) - chineseRect = GetChineseRect(sortedRect[specIndex]); - else - return 0x04; - - if (0) { - rectangle(img_threshold, chineseRect, Scalar(255)); - imshow("plate", img_threshold); - waitKey(0); - destroyWindow("plate"); - } - - vector newSortedRect; - newSortedRect.push_back(chineseRect); - RebuildRect(sortedRect, newSortedRect, specIndex); - - if (newSortedRect.size() == 0) return 0x05; - - bool useSlideWindow = true; - bool useAdapThreshold = true; - //bool useAdapThreshold = CParams::instance()->getParam1b(); - - for (size_t i = 0; i < newSortedRect.size(); i++) { - Rect mr = newSortedRect[i]; - Mat auxRoi(input_grey, mr); - Mat newRoi; - - if (i == 0) { - // genenrate gray chinese char - Rect large_mr = rectEnlarge(mr, input_grey.cols, input_grey.rows); - Mat grayChar(input_grey, large_mr); - Mat grayChinese; - grayChinese.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(grayChar, grayChinese, grayChinese.size(), 0, 0, INTER_LINEAR); - - Mat newChineseRoi; - if (useSlideWindow) { - float slideLengthRatio = 0.1f; - if (!slideChineseGrayWindow(input_grey, large_mr, newChineseRoi, plateType, slideLengthRatio)) - judgeChineseGray(grayChinese, newChineseRoi, plateType); - } - else { - judgeChinese(auxRoi, newRoi, plateType); - } - grayChars.push_back(newChineseRoi); - } - else { - switch (plateType) { - case BLUE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY + CV_THRESH_OTSU); break; - case YELLOW: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_BINARY_INV + CV_THRESH_OTSU); break; - case WHITE: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); break; - default: threshold(auxRoi, newRoi, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); break; - } - newRoi = preprocessChar(newRoi); - - // genenrate gray chinese char - Rect fit_mr = rectFit(mr, input_grey.cols, input_grey.rows); - Mat grayChar(input_grey, fit_mr); - grayChars.push_back(grayChar); - } - resultVec.push_back(newRoi); - } - return 0; -} - - -Rect CCharsSegment::GetChineseRect(const Rect rectSpe) { - int height = rectSpe.height; - float newwidth = rectSpe.width * 1.15f; - int x = rectSpe.x; - int y = rectSpe.y; - - int newx = x - int(newwidth * 1.15); - newx = newx > 0 ? newx : 0; - - Rect a(newx, y, int(newwidth), height); - - return a; -} - -int CCharsSegment::GetSpecificRect(const vector& vecRect) { - vector xpositions; - int maxHeight = 0; - int maxWidth = 0; - - for (size_t i = 0; i < vecRect.size(); i++) { - xpositions.push_back(vecRect[i].x); - - if (vecRect[i].height > maxHeight) { - maxHeight = vecRect[i].height; - } - if (vecRect[i].width > maxWidth) { - maxWidth = vecRect[i].width; - } - } - - int specIndex = 0; - for (size_t i = 0; i < vecRect.size(); i++) { - Rect mr = vecRect[i]; - int midx = mr.x + mr.width / 2; - - // use prior knowledage to find the specific character - // position in 1/7 and 2/7 - if ((mr.width > maxWidth * 0.6 || mr.height > maxHeight * 0.6) && - (midx < int(m_theMatWidth / kPlateMaxSymbolCount) * kSymbolIndex && - midx > int(m_theMatWidth / kPlateMaxSymbolCount) * (kSymbolIndex - 1))) { - specIndex = i; - } - } - - return specIndex; -} - -int CCharsSegment::RebuildRect(const vector& vecRect, - vector& outRect, int specIndex) { - int count = 6; - for (size_t i = specIndex; i < vecRect.size() && count; ++i, --count) { - outRect.push_back(vecRect[i]); - } - - return 0; -} - -} diff --git a/src/core/core_func.cpp b/src/core/core_func.cpp deleted file mode 100644 index a4e419e..0000000 --- a/src/core/core_func.cpp +++ /dev/null @@ -1,2569 +0,0 @@ -#include "easypr/core/core_func.h" -#include "easypr/core/plate.hpp" -#include "easypr/core/chars_identify.h" -#include "easypr/config.h" -#include "easypr/core/params.h" -#include "thirdparty/mser/mser2.hpp" -#include - -namespace easypr { - Mat colorMatch(const Mat &src, Mat &match, const Color r, - const bool adaptive_minsv) { - - // if use adaptive_minsv - // min value of s and v is adaptive to h - const float max_sv = 255; - const float minref_sv = 64; - - const float minabs_sv = 95; //95; - - // H range of blue - - const int min_blue = 100; // 100 - const int max_blue = 140; // 140 - - // H range of yellow - - const int min_yellow = 15; // 15 - const int max_yellow = 40; // 40 - - // H range of white - - const int min_white = 0; // 15 - const int max_white = 30; // 40 - - Mat src_hsv; - - // convert to HSV space - cvtColor(src, src_hsv, CV_BGR2HSV); - - std::vector hsvSplit; - split(src_hsv, hsvSplit); - equalizeHist(hsvSplit[2], hsvSplit[2]); - merge(hsvSplit, src_hsv); - - // match to find the color - - int min_h = 0; - int max_h = 0; - switch (r) { - case BLUE: - min_h = min_blue; - max_h = max_blue; - break; - case YELLOW: - min_h = min_yellow; - max_h = max_yellow; - break; - case WHITE: - min_h = min_white; - max_h = max_white; - break; - default: - // Color::UNKNOWN - break; - } - - float diff_h = float((max_h - min_h) / 2); - float avg_h = min_h + diff_h; - - int channels = src_hsv.channels(); - int nRows = src_hsv.rows; - - // consider multi channel image - int nCols = src_hsv.cols * channels; - if (src_hsv.isContinuous()) { - nCols *= nRows; - nRows = 1; - } - - int i, j; - uchar* p; - float s_all = 0; - float v_all = 0; - float count = 0; - for (i = 0; i < nRows; ++i) { - p = src_hsv.ptr(i); - for (j = 0; j < nCols; j += 3) { - int H = int(p[j]); // 0-180 - int S = int(p[j + 1]); // 0-255 - int V = int(p[j + 2]); // 0-255 - - s_all += S; - v_all += V; - count++; - - bool colorMatched = false; - - if (H > min_h && H < max_h) { - float Hdiff = 0; - if (H > avg_h) - Hdiff = H - avg_h; - else - Hdiff = avg_h - H; - - float Hdiff_p = float(Hdiff) / diff_h; - - float min_sv = 0; - if (true == adaptive_minsv) - min_sv = - minref_sv - - minref_sv / 2 * - (1 - - Hdiff_p); // inref_sv - minref_sv / 2 * (1 - Hdiff_p) - else - min_sv = minabs_sv; // add - - if ((S > min_sv && S < max_sv) && (V > min_sv && V < max_sv)) - colorMatched = true; - } - - if (colorMatched == true) { - p[j] = 0; - p[j + 1] = 0; - p[j + 2] = 255; - } - else { - p[j] = 0; - p[j + 1] = 0; - p[j + 2] = 0; - } - } - } - - // cout << "avg_s:" << s_all / count << endl; - // cout << "avg_v:" << v_all / count << endl; - - // get the final binary - - Mat src_grey; - std::vector hsvSplit_done; - split(src_hsv, hsvSplit_done); - src_grey = hsvSplit_done[2]; - - match = src_grey; - - return src_grey; - } - - bool bFindLeftRightBound1(Mat &bound_threshold, int &posLeft, int &posRight) { - - float span = bound_threshold.rows * 0.2f; - - for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l < i + span; l++) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.15) { - posLeft = i; - break; - } - } - span = bound_threshold.rows * 0.2f; - - - for (int i = bound_threshold.cols - 1; i > span; i -= 2) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l > i - span; l--) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.06) { - posRight = i; - if (posRight + 5 < bound_threshold.cols) { - posRight = posRight + 5; - } else { - posRight = bound_threshold.cols - 1; - } - - break; - } - } - - if (posLeft < posRight) { - return true; - } - return false; - } - - bool bFindLeftRightBound(Mat &bound_threshold, int &posLeft, int &posRight) { - - - float span = bound_threshold.rows * 0.2f; - - for (int i = 0; i < bound_threshold.cols - span - 1; i += 2) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l < i + span; l++) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.36) { - posLeft = i; - break; - } - } - span = bound_threshold.rows * 0.2f; - - - for (int i = bound_threshold.cols - 1; i > span; i -= 2) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l > i - span; l--) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.26) { - posRight = i; - break; - } - } - - if (posLeft < posRight) { - return true; - } - return false; - } - - bool bFindLeftRightBound2(Mat &bound_threshold, int &posLeft, int &posRight) { - - float span = bound_threshold.rows * 0.2f; - - for (int i = 0; i < bound_threshold.cols - span - 1; i += 3) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l < i + span; l++) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.32) { - posLeft = i; - break; - } - } - span = bound_threshold.rows * 0.2f; - - - for (int i = bound_threshold.cols - 1; i > span; i -= 3) { - int whiteCount = 0; - for (int k = 0; k < bound_threshold.rows; k++) { - for (int l = i; l > i - span; l--) { - if (bound_threshold.data[k * bound_threshold.step[0] + l] == 255) { - whiteCount++; - } - } - } - - if (whiteCount * 1.0 / (span * bound_threshold.rows) > 0.22) { - posRight = i; - break; - } - } - - if (posLeft < posRight) { - return true; - } - return false; - } - - - bool plateColorJudge(const Mat &src, const Color r, const bool adaptive_minsv, - float &percent) { - - const float thresh = 0.45f; - - Mat src_gray; - colorMatch(src, src_gray, r, adaptive_minsv); - - percent = - float(countNonZero(src_gray)) / float(src_gray.rows * src_gray.cols); - // cout << "percent:" << percent << endl; - - if (percent > thresh) - return true; - else - return false; - } - - Color getPlateType(const Mat &src, const bool adaptive_minsv) { - float max_percent = 0; - Color max_color = UNKNOWN; - - float blue_percent = 0; - float yellow_percent = 0; - float white_percent = 0; - - if (plateColorJudge(src, BLUE, adaptive_minsv, blue_percent) == true) { - // cout << "BLUE" << endl; - return BLUE; - } else if (plateColorJudge(src, YELLOW, adaptive_minsv, yellow_percent) == - true) { - // cout << "YELLOW" << endl; - return YELLOW; - } else if (plateColorJudge(src, WHITE, adaptive_minsv, white_percent) == - true) { - // cout << "WHITE" << endl; - return WHITE; - } else { - //std::cout << "OTHER" << std::endl; - - /*max_percent = blue_percent > yellow_percent ? blue_percent : yellow_percent; - max_color = blue_percent > yellow_percent ? BLUE : YELLOW; - max_color = max_percent > white_percent ? max_color : WHITE;*/ - - // always return blue - return BLUE; - } - } - - void clearLiuDingOnly(Mat &img) { - const int x = 7; - Mat jump = Mat::zeros(1, img.rows, CV_32F); - for (int i = 0; i < img.rows; i++) { - int jumpCount = 0; - int whiteCount = 0; - for (int j = 0; j < img.cols - 1; j++) { - if (img.at(i, j) != img.at(i, j + 1)) jumpCount++; - - if (img.at(i, j) == 255) { - whiteCount++; - } - } - - jump.at(i) = (float) jumpCount; - } - - for (int i = 0; i < img.rows; i++) { - if (jump.at(i) <= x) { - for (int j = 0; j < img.cols; j++) { - img.at(i, j) = 0; - } - } - } - } - - bool clearLiuDing(Mat &img) { - std::vector fJump; - int whiteCount = 0; - const int x = 7; - Mat jump = Mat::zeros(1, img.rows, CV_32F); - for (int i = 0; i < img.rows; i++) { - int jumpCount = 0; - - for (int j = 0; j < img.cols - 1; j++) { - if (img.at(i, j) != img.at(i, j + 1)) jumpCount++; - - if (img.at(i, j) == 255) { - whiteCount++; - } - } - - jump.at(i) = (float) jumpCount; - } - - int iCount = 0; - for (int i = 0; i < img.rows; i++) { - fJump.push_back(jump.at(i)); - if (jump.at(i) >= 16 && jump.at(i) <= 45) { - - // jump condition - iCount++; - } - } - - // if not is not plate - if (iCount * 1.0 / img.rows <= 0.40) { - return false; - } - - if (whiteCount * 1.0 / (img.rows * img.cols) < 0.15 || - whiteCount * 1.0 / (img.rows * img.cols) > 0.50) { - return false; - } - - for (int i = 0; i < img.rows; i++) { - if (jump.at(i) <= x) { - for (int j = 0; j < img.cols; j++) { - img.at(i, j) = 0; - } - } - } - return true; - } - - -void clearBorder(const Mat &img, Rect& cropRect) { - int r = img.rows; - int c = img.cols; - Mat boder = Mat::zeros(1, r, CV_8UC1); - const int noJunpCount_thresh = int(0.15f * c); - - // if nojumpcount > - for (int i = 0; i < r; i++) { - int nojumpCount = 0; - int isBorder = 0; - for (int j = 0; j < c - 1; j++) { - if (img.at(i, j) == img.at(i, j + 1)) - nojumpCount++; - if (nojumpCount > noJunpCount_thresh) { - nojumpCount = 0; - isBorder = 1; - break; - } - } - boder.at(i) = (char) isBorder; - } - - const int mintop = int(0.1f * r); - const int maxtop = int(0.9f * r); - - int minMatTop = 0; - int maxMatTop = r - 1; - - for (int i = 0; i < mintop; i++) { - if (boder.at(i) == 1) { - minMatTop = i; - } - } - - for (int i = r - 1; i > maxtop; i--) { - if (boder.at(i) == 1) { - maxMatTop = i; - } - } - - cropRect = Rect(0, minMatTop, c, maxMatTop - minMatTop + 1); - -} - - void clearLiuDing(Mat mask, int &top, int &bottom) { - const int x = 7; - - for (int i = 0; i < mask.rows / 2; i++) { - int whiteCount = 0; - int jumpCount = 0; - for (int j = 0; j < mask.cols - 1; j++) { - if (mask.at(i, j) != mask.at(i, j + 1)) jumpCount++; - - if ((int) mask.at(i, j) == 255) { - whiteCount++; - } - } - if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || - whiteCount < 4) { - top = i; - } - } - top -= 1; - if (top < 0) { - top = 0; - } - - // ok, find top and bottom boudnadry - - for (int i = mask.rows - 1; i >= mask.rows / 2; i--) { - int jumpCount = 0; - int whiteCount = 0; - for (int j = 0; j < mask.cols - 1; j++) { - if (mask.at(i, j) != mask.at(i, j + 1)) jumpCount++; - if (mask.at(i, j) == 255) { - whiteCount++; - } - } - if ((jumpCount < x && whiteCount * 1.0 / mask.cols > 0.15) || - whiteCount < 4) { - bottom = i; - } - } - bottom += 1; - if (bottom >= mask.rows) { - bottom = mask.rows - 1; - } - - if (top >= bottom) { - top = 0; - bottom = mask.rows - 1; - } - } - - int ThresholdOtsu(Mat mat) { - int height = mat.rows; - int width = mat.cols; - - // histogram - float histogram[256] = {0}; - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - unsigned char p = (unsigned char) ((mat.data[i * mat.step[0] + j])); - histogram[p]++; - } - } - // normalize histogram - int size = height * width; - for (int i = 0; i < 256; i++) { - histogram[i] = histogram[i] / size; - } - - // average pixel value - float avgValue = 0; - for (int i = 0; i < 256; i++) { - avgValue += i * histogram[i]; - } - - int thresholdV; - float maxVariance = 0; - float w = 0, u = 0; - for (int i = 0; i < 256; i++) { - w += histogram[i]; - u += i * histogram[i]; - - float t = avgValue * w - u; - float variance = t * t / (w * (1 - w)); - if (variance > maxVariance) { - maxVariance = variance; - thresholdV = i; - } - } - - return thresholdV; - } - - - Mat histeq(Mat in) { - Mat out(in.size(), in.type()); - if (in.channels() == 3) { - Mat hsv; - std::vector hsvSplit; - cvtColor(in, hsv, CV_BGR2HSV); - split(hsv, hsvSplit); - equalizeHist(hsvSplit[2], hsvSplit[2]); - merge(hsvSplit, hsv); - cvtColor(hsv, out, CV_HSV2BGR); - } else if (in.channels() == 1) { - equalizeHist(in, out); - } - return out; - } - -#define HORIZONTAL 1 -#define VERTICAL 0 - - Mat CutTheRect(Mat &in, Rect &rect) { - int size = in.cols; // (rect.width>rect.height)?rect.width:rect.height; - Mat dstMat(size, size, CV_8UC1); - dstMat.setTo(Scalar(0, 0, 0)); - - int x = (int) floor((float) (size - rect.width) / 2.0f); - int y = (int) floor((float) (size - rect.height) / 2.0f); - - for (int i = 0; i < rect.height; ++i) { - - for (int j = 0; j < rect.width; ++j) { - dstMat.data[dstMat.step[0] * (i + y) + j + x] = - in.data[in.step[0] * (i + rect.y) + j + rect.x]; - } - } - - // - return dstMat; - } - - Rect GetCenterRect(Mat &in) { - Rect _rect; - - int top = 0; - int bottom = in.rows - 1; - - // find the center rect - - for (int i = 0; i < in.rows; ++i) { - bool bFind = false; - for (int j = 0; j < in.cols; ++j) { - if (in.data[i * in.step[0] + j] > 20) { - top = i; - bFind = true; - break; - } - } - if (bFind) { - break; - } - - } - for (int i = in.rows - 1; - i >= 0; - --i) { - bool bFind = false; - for (int j = 0; j < in.cols; ++j) { - if (in.data[i * in.step[0] + j] > 20) { - bottom = i; - bFind = true; - break; - } - } - if (bFind) { - break; - } - - } - - - int left = 0; - int right = in.cols - 1; - for (int j = 0; j < in.cols; ++j) { - bool bFind = false; - for (int i = 0; i < in.rows; ++i) { - if (in.data[i * in.step[0] + j] > 20) { - left = j; - bFind = true; - break; - } - } - if (bFind) { - break; - } - - } - for (int j = in.cols - 1; - j >= 0; - --j) { - bool bFind = false; - for (int i = 0; i < in.rows; ++i) { - if (in.data[i * in.step[0] + j] > 20) { - right = j; - bFind = true; - - break; - } - } - if (bFind) { - break; - } - } - - _rect.x = left; - _rect.y = top; - _rect.width = right - left + 1; - _rect.height = bottom - top + 1; - - return _rect; - } - - float countOfBigValue(Mat &mat, int iValue) { - float iCount = 0.0; - if (mat.rows > 1) { - for (int i = 0; i < mat.rows; ++i) { - if (mat.data[i * mat.step[0]] > iValue) { - iCount += 1.0; - } - } - return iCount; - - } else { - for (int i = 0; i < mat.cols; ++i) { - if (mat.data[i] > iValue) { - iCount += 1.0; - } - } - - return iCount; - } - } - - Mat ProjectedHistogram(Mat img, int t, int threshold) { - int sz = (t) ? img.rows : img.cols; - Mat mhist = Mat::zeros(1, sz, CV_32F); - - for (int j = 0; j < sz; j++) { - Mat data = (t) ? img.row(j) : img.col(j); - - mhist.at(j) = countOfBigValue(data, threshold); - } - - // Normalize histogram - double min, max; - minMaxLoc(mhist, &min, &max); - - if (max > 0) - mhist.convertTo(mhist, -1, 1.0f / max, 0); - - return mhist; - } - - Mat showHistogram(const Mat &hist) { - int height = 32; - int width = hist.cols; - Mat show = Mat::zeros(height, width, CV_8UC1); - for (int i = 0; i < width; i++) { - int len = int((float) height * hist.at(i)); - for (int j = height - 1; j >= 0; j--) { - if (height - j <= len) - show.at(j, i) = (char) 255; - } - } - return show; - } - - Mat preprocessChar(Mat in, int char_size) { - // Remap image - int h = in.rows; - int w = in.cols; - - int charSize = char_size; - - Mat transformMat = Mat::eye(2, 3, CV_32F); - int m = max(w, h); - transformMat.at(0, 2) = float(m / 2 - w / 2); - transformMat.at(1, 2) = float(m / 2 - h / 2); - - Mat warpImage(m, m, in.type()); - warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, - BORDER_CONSTANT, Scalar(0)); - - Mat out; - cv::resize(warpImage, out, Size(charSize, charSize)); - - return out; - } - - Rect GetChineseRect(const Rect rectSpe) { - int height = rectSpe.height; - float newwidth = rectSpe.width * 1.10f; - int x = rectSpe.x; - int y = rectSpe.y; - - int newx = x - int(newwidth * 1.10f); - newx = newx > 0 ? newx : 0; - - Rect a(newx, y, int(newwidth), height); - - return a; - } - - bool verifyCharSizes(Rect r) { - // Char sizes 45x90 - float aspect = 45.0f / 90.0f; - float charAspect = (float) r.width / (float) r.height; - float error = 0.35f; - float minHeight = 25.f; - float maxHeight = 50.f; - // We have a different aspect ratio for number 1, and it can be ~0.2 - float minAspect = 0.05f; - float maxAspect = aspect + aspect * error; - - // bb area - int bbArea = r.width * r.height; - - if (charAspect > minAspect && charAspect < maxAspect /*&& - r.rows >= minHeight && r.rows < maxHeight*/) - return true; - else - return false; - } - - - Mat scaleImage(const Mat &image, const Size &maxSize, double &scale_ratio) { - Mat ret; - - if (image.cols > maxSize.width || image.rows > maxSize.height) { - double widthRatio = image.cols / (double) maxSize.width; - double heightRatio = image.rows / (double) maxSize.height; - double m_real_to_scaled_ratio = max(widthRatio, heightRatio); - - int newWidth = int(image.cols / m_real_to_scaled_ratio); - int newHeight = int(image.rows / m_real_to_scaled_ratio); - - cv::resize(image, ret, Size(newWidth, newHeight), 0, 0); - scale_ratio = m_real_to_scaled_ratio; - } else { - ret = image; - scale_ratio = 1.0; - } - - return ret; - } - - -// Scale back RotatedRect - RotatedRect scaleBackRRect(const RotatedRect &rr, const float scale_ratio) { - float width = rr.size.width * scale_ratio; - float height = rr.size.height * scale_ratio; - float x = rr.center.x * scale_ratio; - float y = rr.center.y * scale_ratio; - RotatedRect mserRect(Point2f(x, y), Size2f(width, height), rr.angle); - - return mserRect; - } - - bool verifyPlateSize(Rect mr) { - float error = 0.6f; - // Spain car plate size: 52x11 aspect 4,7272 - // China car plate size: 440mm*140mm,aspect 3.142857 - - // Real car plate size: 136 * 32, aspect 4 - float aspect = 3.75; - - // Set a min and max area. All other patchs are discarded - // int min= 1*aspect*1; // minimum area - // int max= 2000*aspect*2000; // maximum area - int min = 34 * 8 * 1; // minimum area - int max = 34 * 8 * 200; // maximum area - - // Get only patchs that match to a respect ratio. - float rmin = aspect - aspect * error; - float rmax = aspect + aspect * error; - - float area = float(mr.height * mr.width); - float r = (float) mr.width / (float) mr.height; - if (r < 1) r = (float) mr.height / (float) mr.width; - - // cout << "area:" << area << endl; - // cout << "r:" << r << endl; - - if ((area < min || area > max) || (r < rmin || r > rmax)) - return false; - else - return true; - } - - bool verifyRotatedPlateSizes(RotatedRect mr, bool showDebug) { - float error = 0.65f; - // Spain car plate size: 52x11 aspect 4,7272 - // China car plate size: 440mm*140mm,aspect 3.142857 - - // Real car plate size: 136 * 32, aspect 4 - float aspect = 3.75f; - - // Set a min and max area. All other patchs are discarded - // int min= 1*aspect*1; // minimum area - // int max= 2000*aspect*2000; // maximum area - //int min = 34 * 8 * 1; // minimum area - //int max = 34 * 8 * 200; // maximum area - - // Get only patchs that match to a respect ratio. - float aspect_min = aspect - aspect * error; - float aspect_max = aspect + aspect * error; - - float width_max = 600.f; - float width_min = 30.f; - - float min = float(width_min * width_min / aspect_max); // minimum area - float max = float(width_max * width_max / aspect_min); // maximum area - - float width = mr.size.width; - float height = mr.size.height; - float area = width * height; - - float ratio = width / height; - float angle = mr.angle; - if (ratio < 1) { - swap(width, height); - ratio = width / height; - - angle = 90.f + angle; - //std::cout << "angle:" << angle << std::endl; - } - - float angle_min = -60.f; - float angle_max = 60.f; - - //std::cout << "aspect_min:" << aspect_min << std::endl; - //std::cout << "aspect_max:" << aspect_max << std::endl; - - if (area < min || area > max) { - if (0 && showDebug) { - std::cout << "area < min || area > max: " << area << std::endl; - } - - return false; - } else if (ratio < aspect_min || ratio > aspect_max) { - if (0 && showDebug) { - std::cout << "ratio < aspect_min || ratio > aspect_max: " << ratio << std::endl; - } - - return false; - } else if (angle < angle_min || angle > angle_max) { - if (0 && showDebug) { - std::cout << "angle < angle_min || angle > angle_max: " << angle << std::endl; - } - - return false; - } else if (width < width_min || width > width_max) { - if (0 && showDebug) { - std::cout << "width < width_min || width > width_max: " << width << std::endl; - } - - return false; - } else { - return true; - } - - return true; - } - -//! non-maximum suppression - void NMStoCharacter(std::vector &inVec, double overlap) { - - std::sort(inVec.begin(), inVec.end()); - - std::vector::iterator it = inVec.begin(); - for (; it != inVec.end(); ++it) { - CCharacter charSrc = *it; - //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; - Rect rectSrc = charSrc.getCharacterPos(); - - std::vector::iterator itc = it + 1; - - for (; itc != inVec.end();) { - CCharacter charComp = *itc; - Rect rectComp = charComp.getCharacterPos(); - //Rect rectInter = rectSrc & rectComp; - //Rect rectUnion = rectSrc | rectComp; - //double r = double(rectInter.area()) / double(rectUnion.area()); - - float iou = computeIOU(rectSrc, rectComp); - - if (iou > overlap) { - itc = inVec.erase(itc); - } else { - ++itc; - } - } - } - } - -// judge weather two CCharacter are nearly the same; - bool compareCharRect(const CCharacter &character1, const CCharacter &character2) { - Rect rect1 = character1.getCharacterPos(); - Rect rect2 = character2.getCharacterPos(); - - // the character in plate are similar height - float width_1 = float(rect1.width); - float height_1 = float(rect1.height); - - float width_2 = float(rect2.width); - float height_2 = float(rect2.height); - - float height_diff = abs(height_1 - height_2); - double height_diff_ratio = height_diff / min(height_1, height_2); - - if (height_diff_ratio > 0.25) - return false; - - // the character in plate are similar in the y-axis - float y_1 = float(rect1.tl().y); - float y_2 = float(rect2.tl().y); - - float y_diff = abs(y_1 - y_2); - double y_diff_ratio = y_diff / min(height_1, height_2); - - if (y_diff_ratio > 0.5) - return false; - - // the character center in plate are not to near in the x-axis - float x_1 = float(rect1.tl().x + rect1.width / 2); - float x_2 = float(rect2.tl().x + rect2.width / 2); - - float x_diff = abs(x_1 - x_2); - double x_diff_ratio = x_diff / min(height_1, height_2); - - if (x_diff_ratio < 0.25) - return false; - - // the character in plate are near in the x-axis but not very near - float x_margin_left = float(min(rect1.br().x, rect2.br().x)); - float x_margin_right = float(max(rect1.tl().x, rect2.tl().x)); - - float x_margin_diff = abs(x_margin_left - x_margin_right); - double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2); - - if (x_margin_diff_ratio > 1.0) - return false; - - return true; - } - -//! merge chars to group, using the similarity - void mergeCharToGroup(std::vector vecRect, - std::vector> &charGroupVec) { - - std::vector labels; - - int numbers = 0; - if (vecRect.size() > 0) - numbers = partition(vecRect, labels, &compareCharRect); - - for (size_t j = 0; j < size_t(numbers); j++) { - std::vector charGroup; - - for (size_t t = 0; t < vecRect.size(); t++) { - int label = labels[t]; - - if (label == j) - charGroup.push_back(vecRect[t]); - } - - if (charGroup.size() < 2) - continue; - - charGroupVec.push_back(charGroup); - } - } - - - void rotatedRectangle(InputOutputArray image, RotatedRect rrect, const Scalar &color, int thickness, int lineType, - int shift) { - Point2f rect_points[4]; - rrect.points(rect_points); - for (int j = 0; j < 4; j++) { - cv::line(image, rect_points[j], rect_points[(j + 1) % 4], color, thickness, lineType, shift); - } - } - - - void searchWeakSeed(const std::vector &charVec, std::vector &mserCharacter, double thresh1, - double thresh2, - const Vec4f &line, Point &boundaryPoint, const Rect &maxrect, Rect &plateResult, Mat result, - CharSearchDirection searchDirection) { - - float k = line[1] / line[0]; - float x_1 = line[2]; - float y_1 = line[3]; - - std::vector searchWeakSeedVec; - searchWeakSeedVec.reserve(8); - - for (auto weakSeed : charVec) { - Rect weakRect = weakSeed.getCharacterPos(); - - //cv::rectangle(result, weakRect, Scalar(255, 0, 255)); - - Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2); - float x_2 = (float) weakCenter.x; - - if (searchDirection == CharSearchDirection::LEFT) { - if (weakCenter.x + weakRect.width / 2 > boundaryPoint.x) { - continue; - } - } else if (searchDirection == CharSearchDirection::RIGHT) { - if (weakCenter.x - weakRect.width / 2 < boundaryPoint.x) { - continue; - } - } - - float y_2l = k * (x_2 - x_1) + y_1; - float y_2 = (float) weakCenter.y; - - float y_diff_ratio = abs(y_2l - y_2) / maxrect.height; - - if (y_diff_ratio < thresh1) { - float width_1 = float(maxrect.width); - float height_1 = float(maxrect.height); - - float width_2 = float(weakRect.width); - float height_2 = float(weakRect.height); - - float height_diff = abs(height_1 - height_2); - double height_diff_ratio = height_diff / min(height_1, height_2); - - float width_diff = abs(width_1 - width_2); - double width_diff_ratio = width_diff / maxrect.width; - - if (height_diff_ratio < thresh1 && width_diff_ratio < 0.5) { - //std::cout << "h" << height_diff_ratio << std::endl; - //std::cout << "w" << width_diff_ratio << std::endl; - searchWeakSeedVec.push_back(weakSeed); - } else { - - } - } - } - - // form right to left to split - if (searchWeakSeedVec.size() != 0) { - if (searchDirection == CharSearchDirection::LEFT) { - std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCharacterPos().tl().x > r2.getCharacterPos().tl().x; - }); - } else if (searchDirection == CharSearchDirection::RIGHT) { - std::sort(searchWeakSeedVec.begin(), searchWeakSeedVec.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; - }); - } - - CCharacter firstWeakSeed = searchWeakSeedVec.at(0); - Rect firstWeakRect = firstWeakSeed.getCharacterPos(); - Point firstWeakCenter(firstWeakRect.tl().x + firstWeakRect.width / 2, - firstWeakRect.tl().y + firstWeakRect.height / 2); - - float ratio = (float) abs(firstWeakCenter.x - boundaryPoint.x) / (float) maxrect.height; - if (ratio > thresh2) { - if (0) { - std::cout << "search seed ratio:" << ratio << std::endl; - } - return; - } - - mserCharacter.push_back(firstWeakSeed); - plateResult |= firstWeakRect; - boundaryPoint = firstWeakCenter; - - for (size_t weakSeedIndex = 0; weakSeedIndex + 1 < searchWeakSeedVec.size(); weakSeedIndex++) { - CCharacter weakSeed = searchWeakSeedVec[weakSeedIndex]; - CCharacter weakSeedCompare = searchWeakSeedVec[weakSeedIndex + 1]; - - Rect rect1 = weakSeed.getCharacterPos(); - Rect rect2 = weakSeedCompare.getCharacterPos(); - - Rect weakRect = rect2; - Point weakCenter(weakRect.tl().x + weakRect.width / 2, weakRect.tl().y + weakRect.height / 2); - - // the character in plate are similar height - float width_1 = float(rect1.width); - float height_1 = float(rect1.height); - - float width_2 = float(rect2.width); - float height_2 = float(rect2.height); - - // the character in plate are near in the x-axis but not very near - float x_margin_left = float(min(rect1.br().x, rect2.br().x)); - float x_margin_right = float(max(rect1.tl().x, rect2.tl().x)); - - float x_margin_diff = abs(x_margin_left - x_margin_right); - double x_margin_diff_ratio = x_margin_diff / min(height_1, height_2); - - if (x_margin_diff_ratio > thresh2) { - if (0) { - std::cout << "search seed x_margin_diff_ratio:" << x_margin_diff_ratio << std::endl; - } - break; - } else { - //::rectangle(result, weakRect, Scalar(255, 0, 0), 1); - mserCharacter.push_back(weakSeedCompare); - plateResult |= weakRect; - if (searchDirection == CharSearchDirection::LEFT) { - if (weakCenter.x < boundaryPoint.x) { - boundaryPoint = weakCenter; - } - } else if (searchDirection == CharSearchDirection::RIGHT) { - if (weakCenter.x > boundaryPoint.x) { - boundaryPoint = weakCenter; - } - } - } - } - } - } - - void slideWindowSearch(const Mat &image, std::vector &slideCharacter, const Vec4f &line, - Point &fromPoint, const Vec2i &dist, double ostu_level, float ratioWindow, - float threshIsCharacter, const Rect &maxrect, Rect &plateResult, - CharSearchDirection searchDirection, bool isChinese, Mat &result) { - float k = line[1] / line[0]; - float x_1 = line[2]; - float y_1 = line[3]; - - int slideLength = int(ratioWindow * maxrect.width); - int slideStep = 1; - int fromX = 0; - if (searchDirection == CharSearchDirection::LEFT) { - fromX = fromPoint.x - dist[0]; - } else if (searchDirection == CharSearchDirection::RIGHT) { - fromX = fromPoint.x + dist[0]; - } - - std::vector charCandidateVec; - for (int slideX = -slideLength; slideX < slideLength; slideX += slideStep) { - float x_slide = 0; - - if (searchDirection == CharSearchDirection::LEFT) { - x_slide = float(fromX - slideX); - } else if (searchDirection == CharSearchDirection::RIGHT) { - x_slide = float(fromX + slideX); - } - - float y_slide = k * (x_slide - x_1) + y_1; - Point2f p_slide(x_slide, y_slide); - cv::circle(result, p_slide, 2, Scalar(255, 255, 255), 1); - - int chineseWidth = int(maxrect.width * 1.05); - int chineseHeight = int(maxrect.height * 1.05); - - Rect rect(Point2f(x_slide - chineseWidth / 2, y_slide - chineseHeight / 2), Size(chineseWidth, chineseHeight)); - - if (rect.tl().x < 0 || rect.tl().y < 0 || rect.br().x >= image.cols || rect.br().y >= image.rows) - continue; - - Mat region = image(rect); - Mat binary_region; - - cv::threshold(region, binary_region, ostu_level, 255, CV_THRESH_BINARY); - //double ostu_level = threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - //std::cout << "ostu_level:" << ostu_level << std::endl;*/ - - Mat charInput = preprocessChar(binary_region, 20); - - if (0) { - imshow("charInput", charInput); - waitKey(0); - destroyWindow("charInput"); - } - - CCharacter charCandidate; - charCandidate.setCharacterPos(rect); - charCandidate.setCharacterMat(charInput); - charCandidate.setIsChinese(isChinese); - charCandidateVec.push_back(charCandidate); - } - - if (isChinese) { - CharsIdentify::instance()->classifyChinese(charCandidateVec); - } else { - CharsIdentify::instance()->classify(charCandidateVec); - } - - double overlapThresh = 0.1; - NMStoCharacter(charCandidateVec, overlapThresh); - - for (auto character : charCandidateVec) { - Rect rect = character.getCharacterPos(); - Point center(rect.tl().x + rect.width / 2, rect.tl().y + rect.height / 2); - - if (character.getCharacterScore() > threshIsCharacter && character.getCharacterStr() != "1") { - //cv::rectangle(result, rect, Scalar(255, 255, 255), 1); - plateResult |= rect; - slideCharacter.push_back(character); - fromPoint = center; - - if (0) { - std::cout << "label:" << character.getCharacterStr(); - std::cout << "__score:" << character.getCharacterScore() << std::endl; - } - } - } - } - - - bool judegMDOratio2(const Mat &image, const Rect &rect, std::vector &contour, Mat &result, const float thresh, - bool useExtendHeight) { - - Mat mser = image(rect); - Mat mser_mat; - cv::threshold(mser, mser_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - - Rect normalRect = adaptive_charrect_from_rect(rect, image.cols, image.rows, useExtendHeight); - Mat region = image(normalRect); - Mat thresh_mat; - cv::threshold(region, thresh_mat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - - // count mser diff ratio - int countdiff = abs(countNonZero(thresh_mat) - countNonZero(mser_mat)); - - float MserDiffOstuRatio = float(countdiff) / float(rect.area()); - - if (MserDiffOstuRatio > thresh) { - //std::cout << "MserDiffOstuRatio:" << MserDiffOstuRatio << std::endl; - /*imshow("tmpMat", mser_mat); - waitKey(0); - imshow("tmpMat", thresh_mat); - waitKey(0);*/ - - cv::rectangle(result, normalRect, Scalar(0, 0, 0), 2); - return false; - } - - return true; - } - - Rect interRect(const Rect &a, const Rect &b) { - Rect c; - int x1 = a.x > b.x ? a.x : b.x; - int y1 = a.y > b.y ? a.y : b.y; - c.width = (a.x + a.width < b.x + b.width ? a.x + a.width : b.x + b.width) - x1; - c.height = (a.y + a.height < b.y + b.height ? a.y + a.height : b.y + b.height) - y1; - c.x = x1; - c.y = y1; - if (c.width <= 0 || c.height <= 0) - c = Rect(); - return c; - } - - Rect mergeRect(const Rect &a, const Rect &b) { - Rect c; - int x1 = a.x < b.x ? a.x : b.x; - int y1 = a.y < b.y ? a.y : b.y; - c.width = (a.x + a.width > b.x + b.width ? a.x + a.width : b.x + b.width) - x1; - c.height = (a.y + a.height > b.y + b.height ? a.y + a.height : b.y + b.height) - y1; - c.x = x1; - c.y = y1; - return c; - } - - bool computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height, - const float thresh, float &result) { - Rect_ safe_rect1; - calcSafeRect(rrect1, width, height, safe_rect1); - - Rect_ safe_rect2; - calcSafeRect(rrect2, width, height, safe_rect2); - - Rect inter = interRect(safe_rect1, safe_rect2); - Rect urect = mergeRect(safe_rect1, safe_rect2); - - float iou = (float) inter.area() / (float) urect.area(); - - result = iou; - - if (iou > thresh) { - return true; - } - - return false; - } - - float computeIOU(const RotatedRect &rrect1, const RotatedRect &rrect2, const int width, const int height) { - Rect_ safe_rect1; - calcSafeRect(rrect1, width, height, safe_rect1); - - Rect_ safe_rect2; - calcSafeRect(rrect2, width, height, safe_rect2); - - Rect inter = interRect(safe_rect1, safe_rect2); - Rect urect = mergeRect(safe_rect1, safe_rect2); - - float iou = (float) inter.area() / (float) urect.area(); - //std::cout << "iou" << iou << std::endl; - - return iou; - } - - bool computeIOU(const Rect &rect1, const Rect &rect2, const float thresh, float &result) { - - Rect inter = interRect(rect1, rect2); - Rect urect = mergeRect(rect1, rect2); - - float iou = (float) inter.area() / (float) urect.area(); - result = iou; - - if (iou > thresh) { - return true; - } - - return false; - } - - float computeIOU(const Rect &rect1, const Rect &rect2) { - - Rect inter = interRect(rect1, rect2); - Rect urect = mergeRect(rect1, rect2); - - float iou = (float) inter.area() / (float) urect.area(); - - return iou; - } - - -// the slope are nealy the same along the line -// if one slope is much different others, it should be outliers -// this function to remove it - void removeRightOutliers(std::vector &charGroup, std::vector &out_charGroup, double thresh1, - double thresh2, Mat result) { - std::sort(charGroup.begin(), charGroup.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCenterPoint().x < r2.getCenterPoint().x; - }); - - std::vector slopeVec; - float slope_last = 0; - for (size_t charGroup_i = 0; charGroup_i + 1 < charGroup.size(); charGroup_i++) { - // line_between_two_points - Vec4f line_btp; - CCharacter leftChar = charGroup.at(charGroup_i); - CCharacter rightChar = charGroup.at(charGroup_i + 1); - std::vector two_points; - two_points.push_back(leftChar.getCenterPoint()); - two_points.push_back(rightChar.getCenterPoint()); - fitLine(Mat(two_points), line_btp, CV_DIST_L2, 0, 0.01, 0.01); - float slope = line_btp[1] / line_btp[0]; - slopeVec.push_back(slope); - - if (0) { - cv::line(result, leftChar.getCenterPoint(), rightChar.getCenterPoint(), Scalar(0, 0, 255)); - } - } - - int uniformity_count = 0; - int outlier_index = -1; - for (size_t slopeVec_i = 0; slopeVec_i + 1 < slopeVec.size(); slopeVec_i++) { - float slope_1 = slopeVec.at(slopeVec_i); - float slope_2 = slopeVec.at(slopeVec_i + 1); - float slope_diff = abs(slope_1 - slope_2); - if (0) { - std::cout << "slope_diff:" << slope_diff << std::endl; - } - if (slope_diff <= thresh1) { - uniformity_count++; - } - if (0) { - std::cout << "slope_1:" << slope_1 << std::endl; - std::cout << "slope_2:" << slope_2 << std::endl; - } - if (1/*(slope_1 <= 0 && slope_2 >= 0) || (slope_1 >= 0 && slope_2 <= 0)*/) { - if (uniformity_count >= 2 && slope_diff >= thresh2) { - outlier_index = slopeVec_i + 2; - break; - } - } - } - if (0) { - std::cout << "uniformity_count:" << uniformity_count << std::endl; - std::cout << "outlier_index:" << outlier_index << std::endl; - } - - for (int charGroup_i = 0; charGroup_i < (int) charGroup.size(); charGroup_i++) { - if (charGroup_i != outlier_index) { - CCharacter theChar = charGroup.at(charGroup_i); - out_charGroup.push_back(theChar); - } - } - - if (0) { - std::cout << "end:" << std::endl; - } - } - - Rect getSafeRect(Point2f center, float width, float height, Mat image) { - int rows = image.rows; - int cols = image.cols; - - float x = center.x; - float y = center.y; - - float x_tl = (x - width / 2.f); - float y_tl = (y - height / 2.f); - - float x_br = (x + width / 2.f); - float y_br = (y + height / 2.f); - - x_tl = x_tl > 0.f ? x_tl : 0.f; - y_tl = y_tl > 0.f ? y_tl : 0.f; - x_br = x_br < (float) image.cols ? x_br : (float) image.cols; - y_br = y_br < (float) image.rows ? y_br : (float) image.rows; - - Rect rect(Point((int) x_tl, int(y_tl)), Point((int) x_br, int(y_br))); - return rect; - } - -// based on the assumptions: distance beween two nearby characters in plate are the same. -// add not found rect and combine two small and near rect. - void reFoundAndCombineRect(std::vector &mserCharacter, float min_thresh, float max_thresh, - Vec2i dist, Rect maxrect, Mat result) { - if (mserCharacter.size() == 0) { - return; - } - - std::sort(mserCharacter.begin(), mserCharacter.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCenterPoint().x < r2.getCenterPoint().x; - }); - - int comparDist = dist[0] * dist[0] + dist[1] * dist[1]; - if (0) { - std::cout << "comparDist:" << comparDist << std::endl; - } - - std::vector reCharacters; - - size_t mserCharacter_i = 0; - for (; mserCharacter_i + 1 < mserCharacter.size(); mserCharacter_i++) { - CCharacter leftChar = mserCharacter.at(mserCharacter_i); - CCharacter rightChar = mserCharacter.at(mserCharacter_i + 1); - - Point leftCenter = leftChar.getCenterPoint(); - Point rightCenter = rightChar.getCenterPoint(); - - int x_diff = leftCenter.x - rightCenter.x; - int y_diff = leftCenter.y - rightCenter.y; - - // distance between two centers - int distance2 = x_diff * x_diff + y_diff * y_diff; - - if (0) { - std::cout << "distance2:" << distance2 << std::endl; - } - - float ratio = (float) distance2 / (float) comparDist; - if (ratio > max_thresh) { - float x_add = (float) (leftCenter.x + rightCenter.x) / 2.f; - float y_add = (float) (leftCenter.y + rightCenter.y) / 2.f; - - float width = (float) maxrect.width; - float height = (float) maxrect.height; - - float x_tl = (x_add - width / 2.f); - float y_tl = (y_add - height / 2.f); - - //Rect rect_add((int)x_tl, (int)y_tl, (int)width, (int)height); - Rect rect_add = getSafeRect(Point2f(x_add, y_add), width, height, result); - - reCharacters.push_back(leftChar); - - CCharacter charAdd; - charAdd.setCenterPoint(Point((int) x_add, (int) y_add)); - charAdd.setCharacterPos(rect_add); - reCharacters.push_back(charAdd); - - if (1) { - cv::rectangle(result, rect_add, Scalar(0, 128, 255)); - } - } else if (ratio < min_thresh) { - Rect rect_union = leftChar.getCharacterPos() | rightChar.getCharacterPos(); - /*float x_add = (float)(leftCenter.x + rightCenter.x) / 2.f; - float y_add = (float)(leftCenter.y + rightCenter.y) / 2.f;*/ - int x_add = rect_union.tl().x + rect_union.width / 2; - int y_add = rect_union.tl().y + rect_union.height / 2; - - CCharacter charAdd; - charAdd.setCenterPoint(Point(x_add, y_add)); - charAdd.setCharacterPos(rect_union); - reCharacters.push_back(charAdd); - if (1) { - cv::rectangle(result, rect_union, Scalar(0, 128, 255)); - } - - mserCharacter_i++; - } else { - reCharacters.push_back(leftChar); - } - } - - if (mserCharacter_i + 1 == mserCharacter.size()) { - reCharacters.push_back(mserCharacter.at(mserCharacter_i)); - } - - mserCharacter = reCharacters; - } - - - void removeOutliers(std::vector &charGroup, double thresh, Mat result) { - std::vector points; - Vec4f line; - for (auto character : charGroup) { - points.push_back(character.getCenterPoint()); - } - - fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01); - - float k = line[1] / line[0]; - float x_1 = line[2]; - float y_1 = line[3]; - float step = 100; - cv::line(result, Point2f(x_1 - step, y_1 - k * step), Point2f(x_1 + step, k * step + y_1), Scalar(0, 0, 255)); - - float a = k; - float b = -1; - float c = y_1 - k * x_1; - float sumdistance = 0; - for (auto character : charGroup) { - Point center = character.getCenterPoint(); - float distance = (a * center.x + b * center.y + c) / std::sqrt(a * a + b * b); - std::cout << "distance:" << distance << std::endl; - sumdistance += distance; - } - float avgdistance = sumdistance / (float) charGroup.size(); - - std::vector::iterator it = charGroup.begin(); - for (; it != charGroup.end();) { - Point center = it->getCenterPoint(); - float distance = a * center.x + b * center.y + c; - float ratio = distance / avgdistance; - std::cout << "ratio:" << ratio << std::endl; - if (ratio > (float) thresh) { - it = charGroup.erase(it); - } else { - ++it; - } - } - } - -//! use verify size to first generate char candidates - void mserCharMatch(const Mat &src, std::vector &match, std::vector &out_plateVec_blue, - std::vector &out_plateVec_yellow, - bool usePlateMser, std::vector &out_plateRRect_blue, - std::vector &out_plateRRect_yellow, int img_index, - bool showDebug) { - Mat image = src; - - std::vector>> all_contours; - std::vector> all_boxes; - all_contours.resize(2); - all_contours.at(0).reserve(1024); - all_contours.at(1).reserve(1024); - all_boxes.resize(2); - all_boxes.at(0).reserve(1024); - all_boxes.at(1).reserve(1024); - - match.resize(2); - - std::vector flags; - flags.push_back(BLUE); - flags.push_back(YELLOW); - - const int imageArea = image.rows * image.cols; - const int delta = 1; - //const int delta = CParams::instance()->getParam2i();; - const int minArea = 30; - const double maxAreaRatio = 0.05; - - Ptr mser; - mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea)); - mser->detectRegions(image, all_contours.at(0), all_boxes.at(0), all_contours.at(1), all_boxes.at(1)); - - // mser detect - // color_index = 0 : mser-, detect white characters, which is in blue plate. - // color_index = 1 : mser+, detect dark characters, which is in yellow plate. - -#pragma omp parallel for - for (int color_index = 0; color_index < 2; color_index++) { - Color the_color = flags.at(color_index); - - std::vector charVec; - charVec.reserve(128); - - match.at(color_index) = Mat::zeros(image.rows, image.cols, image.type()); - - Mat result = image.clone(); - cvtColor(result, result, COLOR_GRAY2BGR); - - size_t size = all_contours.at(color_index).size(); - - int char_index = 0; - int char_size = 20; - - // Chinese plate has max 7 characters. - const int char_max_count = 7; - - // verify char size and output to rects; - for (size_t index = 0; index < size; index++) { - Rect rect = all_boxes.at(color_index)[index]; - std::vector &contour = all_contours.at(color_index)[index]; - - // sometimes a plate could be a mser rect, so we could - // also use mser algorithm to find plate - if (usePlateMser) { - RotatedRect rrect = minAreaRect(Mat(contour)); - if (verifyRotatedPlateSizes(rrect)) { - //rotatedRectangle(result, rrect, Scalar(255, 0, 0), 2); - if (the_color == BLUE) out_plateRRect_blue.push_back(rrect); - if (the_color == YELLOW) out_plateRRect_yellow.push_back(rrect); - } - } - - // find character - if (verifyCharSizes(rect)) { - Mat mserMat = adaptive_image_from_points(contour, rect, Size(char_size, char_size)); - Mat charInput = preprocessChar(mserMat, char_size); - Rect charRect = rect; - - Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); - Mat tmpMat; - double ostu_level = cv::threshold(image(charRect), tmpMat, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - - //cv::circle(result, center, 3, Scalar(0, 0, 255), 2); - - // use judegMDOratio2 function to - // remove the small lines in character like "zh-cuan" - if (judegMDOratio2(image, rect, contour, result)) { - CCharacter charCandidate; - charCandidate.setCharacterPos(charRect); - charCandidate.setCharacterMat(charInput); - charCandidate.setOstuLevel(ostu_level); - charCandidate.setCenterPoint(center); - charCandidate.setIsChinese(false); - charVec.push_back(charCandidate); - } - } - } - - // improtant, use matrix multiplication to acclerate the - // classification of many samples. use the character - // score, we can use non-maximum superssion (nms) to - // reduce the characters which are not likely to be true - // charaters, and use the score to select the strong seed - // of which the score is larger than 0.9 - CharsIdentify::instance()->classify(charVec); - - // use nms to remove the character are not likely to be true. - double overlapThresh = 0.6; - //double overlapThresh = CParams::instance()->getParam1f(); - NMStoCharacter(charVec, overlapThresh); - charVec.shrink_to_fit(); - - std::vector strongSeedVec; - strongSeedVec.reserve(64); - std::vector weakSeedVec; - weakSeedVec.reserve(64); - std::vector littleSeedVec; - littleSeedVec.reserve(64); - - //size_t charCan_size = charVec.size(); - for (auto charCandidate : charVec) { - //CCharacter& charCandidate = charVec[char_index]; - Rect rect = charCandidate.getCharacterPos(); - double score = charCandidate.getCharacterScore(); - if (charCandidate.getIsStrong()) { - strongSeedVec.push_back(charCandidate); - } else if (charCandidate.getIsWeak()) { - weakSeedVec.push_back(charCandidate); - //cv::rectangle(result, rect, Scalar(255, 0, 255)); - } else if (charCandidate.getIsLittle()) { - littleSeedVec.push_back(charCandidate); - //cv::rectangle(result, rect, Scalar(255, 0, 255)); - } - } - - std::vector searchCandidate = charVec; - - // nms to srong seed, only leave the strongest one - overlapThresh = 0.3; - NMStoCharacter(strongSeedVec, overlapThresh); - - // merge chars to group - std::vector> charGroupVec; - charGroupVec.reserve(64); - mergeCharToGroup(strongSeedVec, charGroupVec); - - // genenrate the line of the group - // based on the assumptions , the mser rects which are - // given high socre by character classifier could be no doubtly - // be the characters in one plate, and we can use these characeters - // to fit a line which is the middle line of the plate. - std::vector plateVec; - plateVec.reserve(16); - for (auto charGroup : charGroupVec) { - Rect plateResult = charGroup[0].getCharacterPos(); - std::vector points; - points.reserve(32); - - Vec4f line; - int maxarea = 0; - Rect maxrect; - double ostu_level_sum = 0; - - int leftx = image.cols; - Point leftPoint(leftx, 0); - int rightx = 0; - Point rightPoint(rightx, 0); - - std::vector mserCharVec; - mserCharVec.reserve(32); - - // remove outlier CharGroup - std::vector roCharGroup; - roCharGroup.reserve(32); - - removeRightOutliers(charGroup, roCharGroup, 0.2, 0.5, result); - //roCharGroup = charGroup; - - for (auto character : roCharGroup) { - Rect charRect = character.getCharacterPos(); - cv::rectangle(result, charRect, Scalar(0, 255, 0), 1); - plateResult |= charRect; - - Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); - points.push_back(center); - mserCharVec.push_back(character); - //cv::circle(result, center, 3, Scalar(0, 255, 0), 2); - - ostu_level_sum += character.getOstuLevel(); - - if (charRect.area() > maxarea) { - maxrect = charRect; - maxarea = charRect.area(); - } - if (center.x < leftPoint.x) { - leftPoint = center; - } - if (center.x > rightPoint.x) { - rightPoint = center; - } - } - - double ostu_level_avg = ostu_level_sum / (double) roCharGroup.size(); - if (1 && showDebug) { - std::cout << "ostu_level_avg:" << ostu_level_avg << std::endl; - } - float ratio_maxrect = (float) maxrect.width / (float) maxrect.height; - - if (points.size() >= 2 && ratio_maxrect >= 0.3) { - fitLine(Mat(points), line, CV_DIST_L2, 0, 0.01, 0.01); - - float k = line[1] / line[0]; - //float angle = atan(k) * 180 / (float)CV_PI; - //std::cout << "k:" << k << std::endl; - //std::cout << "angle:" << angle << std::endl; - //std::cout << "cos:" << 0.3 * cos(k) << std::endl; - //std::cout << "ratio_maxrect:" << ratio_maxrect << std::endl; - - std::sort(mserCharVec.begin(), mserCharVec.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; - }); - - CCharacter midChar = mserCharVec.at(int(mserCharVec.size() / 2.f)); - Rect midRect = midChar.getCharacterPos(); - Point midCenter(midRect.tl().x + midRect.width / 2, midRect.tl().y + midRect.height / 2); - - int mindist = 7 * maxrect.width; - std::vector distVecVec; - distVecVec.reserve(32); - - Vec2i mindistVec; - Vec2i avgdistVec; - - // computer the dist which is the distacne between - // two near characters in the plate, use dist we can - // judege how to computer the max search range, and choose the - // best location of the sliding window in the next steps. - for (size_t mser_i = 0; mser_i + 1 < mserCharVec.size(); mser_i++) { - Rect charRect = mserCharVec.at(mser_i).getCharacterPos(); - Point center(charRect.tl().x + charRect.width / 2, charRect.tl().y + charRect.height / 2); - - Rect charRectCompare = mserCharVec.at(mser_i + 1).getCharacterPos(); - Point centerCompare(charRectCompare.tl().x + charRectCompare.width / 2, - charRectCompare.tl().y + charRectCompare.height / 2); - - int dist = charRectCompare.x - charRect.x; - Vec2i distVec(charRectCompare.x - charRect.x, charRectCompare.y - charRect.y); - distVecVec.push_back(distVec); - - //if (dist < mindist) { - // mindist = dist; - // mindistVec = distVec; - //} - } - - std::sort(distVecVec.begin(), distVecVec.end(), - [](const Vec2i &r1, const Vec2i &r2) { - return r1[0] < r2[0]; - }); - - avgdistVec = distVecVec.at(int((distVecVec.size() - 1) / 2.f)); - - //float step = 10.f * (float)maxrect.width; - //float step = (float)mindistVec[0]; - float step = (float) avgdistVec[0]; - - //cv::line(result, Point2f(line[2] - step, line[3] - k*step), Point2f(line[2] + step, k*step + line[3]), Scalar(255, 255, 255)); - cv::line(result, Point2f(midCenter.x - step, midCenter.y - k * step), - Point2f(midCenter.x + step, k * step + midCenter.y), Scalar(255, 255, 255)); - //cv::circle(result, leftPoint, 3, Scalar(0, 0, 255), 2); - - CPlate plate; - plate.setPlateLeftPoint(leftPoint); - plate.setPlateRightPoint(rightPoint); - - plate.setPlateLine(line); - plate.setPlatDistVec(avgdistVec); - plate.setOstuLevel(ostu_level_avg); - - plate.setPlateMergeCharRect(plateResult); - plate.setPlateMaxCharRect(maxrect); - plate.setMserCharacter(mserCharVec); - plateVec.push_back(plate); - } - } - - // use strong seed to construct the first shape of the plate, - // then we need to find characters which are the weak seed. - // because we use strong seed to build the middle lines of the plate, - // we can simply use this to consider weak seeds only lie in the - // near place of the middle line - for (auto plate : plateVec) { - Vec4f line = plate.getPlateLine(); - Point leftPoint = plate.getPlateLeftPoint(); - Point rightPoint = plate.getPlateRightPoint(); - - Rect plateResult = plate.getPlateMergeCharRect(); - Rect maxrect = plate.getPlateMaxCharRect(); - Vec2i dist = plate.getPlateDistVec(); - double ostu_level = plate.getOstuLevel(); - - std::vector mserCharacter = plate.getCopyOfMserCharacters(); - mserCharacter.reserve(16); - - float k = line[1] / line[0]; - float x_1 = line[2]; - float y_1 = line[3]; - - std::vector searchWeakSeedVec; - searchWeakSeedVec.reserve(16); - - std::vector searchRightWeakSeed; - searchRightWeakSeed.reserve(8); - std::vector searchLeftWeakSeed; - searchLeftWeakSeed.reserve(8); - - std::vector slideRightWindow; - slideRightWindow.reserve(8); - std::vector slideLeftWindow; - slideLeftWindow.reserve(8); - - // draw weak seed and little seed from line; - // search for mser rect - if (1 && showDebug) { - std::cout << "search for mser rect:" << std::endl; - } - - if (0 && showDebug) { - std::stringstream ss(std::stringstream::in | std::stringstream::out); - ss << "resources/image/tmp/" << img_index << "_1_" << "searcgMserRect.jpg"; - imwrite(ss.str(), result); - } - if (1 && showDebug) { - std::cout << "mserCharacter:" << mserCharacter.size() << std::endl; - } - - // if the count of strong seed is larger than max count, we dont need - // all the next steps, if not, we first need to search the weak seed in - // the same line as the strong seed. The judge condition contains the distance - // between strong seed and weak seed , and the rect simily of each other to improve - // the roubustnedd of the seed growing algorithm. - if (mserCharacter.size() < char_max_count) { - double thresh1 = 0.15; - double thresh2 = 2.0; - searchWeakSeed(searchCandidate, searchRightWeakSeed, thresh1, thresh2, line, rightPoint, - maxrect, plateResult, result, CharSearchDirection::RIGHT); - if (1 && showDebug) { - std::cout << "searchRightWeakSeed:" << searchRightWeakSeed.size() << std::endl; - } - for (auto seed : searchRightWeakSeed) { - cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1); - mserCharacter.push_back(seed); - } - - searchWeakSeed(searchCandidate, searchLeftWeakSeed, thresh1, thresh2, line, leftPoint, - maxrect, plateResult, result, CharSearchDirection::LEFT); - if (1 && showDebug) { - std::cout << "searchLeftWeakSeed:" << searchLeftWeakSeed.size() << std::endl; - } - for (auto seed : searchLeftWeakSeed) { - cv::rectangle(result, seed.getCharacterPos(), Scalar(255, 0, 0), 1); - mserCharacter.push_back(seed); - } - } - - // sometimes the weak seed is in the middle of the strong seed. - // and sometimes two strong seed are actually the two parts of one character. - // because we only consider the weak seed in the left and right direction of strong seed. - // now we examine all the strong seed and weak seed. not only to find the seed in the middle, - // but also to combine two seed which are parts of one character to one seed. - // only by this process, we could use the seed count as the condition to judge if or not to use slide window. - float min_thresh = 0.3f; - float max_thresh = 2.5f; - reFoundAndCombineRect(mserCharacter, min_thresh, max_thresh, dist, maxrect, result); - - // if the characters count is less than max count - // this means the mser rect in the lines are not enough. - // sometimes there are still some characters could not be captured by mser algorithm, - // such as blur, low light ,and some chinese characters like zh-cuan. - // to handle this ,we use a simple slide window method to find them. - if (mserCharacter.size() < char_max_count) { - if (1 && showDebug) { - std::cout << "search chinese:" << std::endl; - std::cout << "judege the left is chinese:" << std::endl; - } - - // if the left most character is chinese, this means - // that must be the first character in chinese plate, - // and we need not to do a slide window to left. So, - // the first thing is to judge the left charcater is - // or not the chinese. - bool leftIsChinese = false; - if (1) { - std::sort(mserCharacter.begin(), mserCharacter.end(), - [](const CCharacter &r1, const CCharacter &r2) { - return r1.getCharacterPos().tl().x < r2.getCharacterPos().tl().x; - }); - - CCharacter leftChar = mserCharacter[0]; - - //Rect theRect = adaptive_charrect_from_rect(leftChar.getCharacterPos(), image.cols, image.rows); - Rect theRect = leftChar.getCharacterPos(); - //cv::rectangle(result, theRect, Scalar(255, 0, 0), 1); - - Mat region = image(theRect); - Mat binary_region; - - ostu_level = cv::threshold(region, binary_region, 0, 255, CV_THRESH_BINARY | CV_THRESH_OTSU); - if (1 && showDebug) { - std::cout << "left : ostu_level:" << ostu_level << std::endl; - } - //plate.setOstuLevel(ostu_level); - - Mat charInput = preprocessChar(binary_region, char_size); - if (0 /*&& showDebug*/) { - imshow("charInput", charInput); - waitKey(0); - destroyWindow("charInput"); - } - - std::string label = ""; - float maxVal = -2.f; - leftIsChinese = CharsIdentify::instance()->isCharacter(charInput, label, maxVal, true); - //auto character = CharsIdentify::instance()->identifyChinese(charInput, maxVal, leftIsChinese); - //label = character.second; - if (0 /* && showDebug*/) { - std::cout << "isChinese:" << leftIsChinese << std::endl; - std::cout << "chinese:" << label; - std::cout << "__score:" << maxVal << std::endl; - } - } - - // if the left most character is not a chinese, - // this means we meed to slide a window to find the missed mser rect. - // search for sliding window - float ratioWindow = 0.4f; - //float ratioWindow = CParams::instance()->getParam3f(); - float threshIsCharacter = 0.8f; - //float threshIsCharacter = CParams::instance()->getParam3f(); - if (!leftIsChinese) { - slideWindowSearch(image, slideLeftWindow, line, leftPoint, dist, ostu_level, ratioWindow, threshIsCharacter, - maxrect, plateResult, CharSearchDirection::LEFT, true, result); - if (1 && showDebug) { - std::cout << "slideLeftWindow:" << slideLeftWindow.size() << std::endl; - } - for (auto window : slideLeftWindow) { - cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1); - mserCharacter.push_back(window); - } - } - } - - // if we still have less than max count characters, - // we need to slide a window to right to search for the missed mser rect. - if (mserCharacter.size() < char_max_count) { - // change ostu_level - float ratioWindow = 0.4f; - //float ratioWindow = CParams::instance()->getParam3f(); - float threshIsCharacter = 0.8f; - //float threshIsCharacter = CParams::instance()->getParam3f(); - slideWindowSearch(image, slideRightWindow, line, rightPoint, dist, plate.getOstuLevel(), ratioWindow, - threshIsCharacter, - maxrect, plateResult, CharSearchDirection::RIGHT, false, result); - if (1 && showDebug) { - std::cout << "slideRightWindow:" << slideRightWindow.size() << std::endl; - } - for (auto window : slideRightWindow) { - cv::rectangle(result, window.getCharacterPos(), Scalar(0, 0, 255), 1); - mserCharacter.push_back(window); - } - } - - // computer the plate angle - float angle = atan(k) * 180 / (float) CV_PI; - if (1 && showDebug) { - std::cout << "k:" << k << std::endl; - std::cout << "angle:" << angle << std::endl; - } - - // the plateResult rect need to be enlarge to contains all the plate, - // not only the character area. - float widthEnlargeRatio = 1.15f; //1.15f; - float heightEnlargeRatio = 1.25f; //1.25f; - RotatedRect platePos( - Point2f((float) plateResult.x + plateResult.width / 2.f, (float) plateResult.y + plateResult.height / 2.f), - Size2f(plateResult.width * widthEnlargeRatio, maxrect.height * heightEnlargeRatio), angle); - - // justify the size is likely to be a plate size. - if (verifyRotatedPlateSizes(platePos)) { - rotatedRectangle(result, platePos, Scalar(0, 0, 255), 1); - - plate.setPlatePos(platePos); - plate.setPlateColor(the_color); - plate.setPlateLocateType(CMSER); - - if (the_color == BLUE) out_plateVec_blue.push_back(plate); - if (the_color == YELLOW) out_plateVec_yellow.push_back(plate); - } - - // use deskew to rotate the image, so we need the binary image. - if (1) { - for (auto mserChar : mserCharacter) { - Rect rect = mserChar.getCharacterPos(); - match.at(color_index)(rect) = 255; - } - cv::line(match.at(color_index), rightPoint, leftPoint, Scalar(255)); - } - } - - if (0 /*&& showDebug*/) { - imshow("result", result); - waitKey(0); - destroyWindow("result"); - } - - if (0) { - imshow("match", match.at(color_index)); - waitKey(0); - destroyWindow("match"); - } - - if (1) { - std::stringstream ss(std::stringstream::in | std::stringstream::out); - ss << "resources/image/tmp/plateDetect/plate_" << img_index << "_" << the_color << ".jpg"; - imwrite(ss.str(), result); - } - } - - - } - -// this spatial_ostu algorithm are robust to -// the plate which has the same light shine, which is that -// the light in the left of the plate is strong than the right. - void spatial_ostu(InputArray _src, int grid_x, int grid_y, Color type) { - Mat src = _src.getMat(); - - int width = src.cols / grid_x; - int height = src.rows / grid_y; - - // iterate through grid - for (int i = 0; i < grid_y; i++) { - for (int j = 0; j < grid_x; j++) { - Mat src_cell = Mat(src, Range(i * height, (i + 1) * height), Range(j * width, (j + 1) * width)); - if (type == BLUE) { - cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - } else if (type == YELLOW) { - cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); - } else if (type == WHITE) { - cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY_INV); - } else { - cv::threshold(src_cell, src_cell, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - } - } - } - } - - - bool mat_valid_position(const Mat &mat, int row, int col) { - return row >= 0 && col >= 0 && row < mat.rows && col < mat.cols; - } - - - template - static void mat_set_invoke(Mat &mat, int row, int col, const Scalar &value) { - if (1 == mat.channels()) { - mat.at(row, col) = (T) value.val[0]; - } else if (3 == mat.channels()) { - T *ptr_src = mat.ptr(row, col); - *ptr_src++ = (T) value.val[0]; - *ptr_src++ = (T) value.val[1]; - *ptr_src = (T) value.val[2]; - } else if (4 == mat.channels()) { - T *ptr_src = mat.ptr(row, col); - *ptr_src++ = (T) value.val[0]; - *ptr_src++ = (T) value.val[1]; - *ptr_src++ = (T) value.val[2]; - *ptr_src = (T) value.val[3]; - } - } - - void setPoint(Mat &mat, int row, int col, const Scalar &value) { - if (CV_8U == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_8S == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_16U == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_16S == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_32S == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_32F == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } else if (CV_64F == mat.depth()) { - mat_set_invoke(mat, row, col, value); - } - } - - Rect adaptive_charrect_from_rect(const Rect &rect, int maxwidth, int maxheight, bool useExtendHeight) { - int expendWidth = 0; - int extendHeight = 0; - - if (rect.height > 3 * rect.width) { - expendWidth = int((int(rect.height * 0.5f) - rect.width) * 0.5f); - if (useExtendHeight) { - extendHeight = int(rect.height * 0.3f); - } - } - - //Rect resultRect(rect.tl().x - expendWidth, rect.tl().y, - // rect.width + expendWidth * 2, rect.height); - - int tlx = rect.tl().x - expendWidth > 0 ? rect.tl().x - expendWidth : 0; - int tly = rect.tl().y - extendHeight > 0 ? rect.tl().y - extendHeight : 0; - - int brx = rect.br().x + expendWidth < maxwidth ? rect.br().x + expendWidth : maxwidth; - int bry = rect.br().y + extendHeight < maxheight ? rect.br().y + extendHeight : maxheight; - - Rect resultRect(tlx, tly, brx - tlx, bry - tly); - return resultRect; - } - - - Mat adaptive_image_from_points(const std::vector &points, - const Rect &rect, const Size &size, - const Scalar &backgroundColor /* = ml_color_white */, - const Scalar &forgroundColor /* = ml_color_black */, bool gray /* = true */) { - int expendHeight = 0; - int expendWidth = 0; - - if (rect.width > rect.height) { - expendHeight = (rect.width - rect.height) / 2; - } else if (rect.height > rect.width) { - expendWidth = (rect.height - rect.width) / 2; - } - - Mat image(rect.height + expendHeight * 2, rect.width + expendWidth * 2, gray ? CV_8UC1 : CV_8UC3, backgroundColor); - - for (int i = 0; i < (int) points.size(); ++i) { - Point point = points[i]; - Point currentPt(point.x - rect.tl().x + expendWidth, point.y - rect.tl().y + expendHeight); - if (mat_valid_position(image, currentPt.y, currentPt.x)) { - setPoint(image, currentPt.y, currentPt.x, forgroundColor); - } - } - - Mat result; - cv::resize(image, result, size, 0, 0, INTER_NEAREST); - - return result; - } - -// calc safe Rect -// if not exit, return false - - bool calcSafeRect(const RotatedRect &roi_rect, const Mat &src, - Rect_ &safeBoundRect) { - Rect_ boudRect = roi_rect.boundingRect(); - - float tl_x = boudRect.x > 0 ? boudRect.x : 0; - float tl_y = boudRect.y > 0 ? boudRect.y : 0; - - float br_x = boudRect.x + boudRect.width < src.cols - ? boudRect.x + boudRect.width - 1 - : src.cols - 1; - float br_y = boudRect.y + boudRect.height < src.rows - ? boudRect.y + boudRect.height - 1 - : src.rows - 1; - - float roi_width = br_x - tl_x; - float roi_height = br_y - tl_y; - - if (roi_width <= 0 || roi_height <= 0) return false; - - // a new rect not out the range of mat - - safeBoundRect = Rect_(tl_x, tl_y, roi_width, roi_height); - - return true; - } - - bool calcSafeRect(const RotatedRect &roi_rect, const int width, const int height, - Rect_ &safeBoundRect) { - Rect_ boudRect = roi_rect.boundingRect(); - - float tl_x = boudRect.x > 0 ? boudRect.x : 0; - float tl_y = boudRect.y > 0 ? boudRect.y : 0; - - float br_x = boudRect.x + boudRect.width < width - ? boudRect.x + boudRect.width - 1 - : width - 1; - float br_y = boudRect.y + boudRect.height < height - ? boudRect.y + boudRect.height - 1 - : height - 1; - - float roi_width = br_x - tl_x; - float roi_height = br_y - tl_y; - - if (roi_width <= 0 || roi_height <= 0) return false; - - // a new rect not out the range of mat - - safeBoundRect = Rect_(tl_x, tl_y, roi_width, roi_height); - - return true; - } - - - Mat uniformResize(const Mat &result, float &scale) { - const int RESULTWIDTH = kShowWindowWidth; // 640 930 - const int RESULTHEIGHT = kShowWindowHeight; // 540 710 - - Mat img_window; - img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); - - int nRows = result.rows; - int nCols = result.cols; - - Mat result_resize; - if (nCols <= img_window.cols && nRows <= img_window.rows) { - result_resize = result; - } else if (nCols > img_window.cols && nRows <= img_window.rows) { - scale = float(img_window.cols) / float(nCols); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } else if (nCols <= img_window.cols && nRows > img_window.rows) { - scale = float(img_window.rows) / float(nRows); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } else if (nCols > img_window.cols && nRows > img_window.rows) { - float scale1 = float(img_window.cols) / float(nCols); - float scale2 = float(img_window.rows) / float(nRows); - scale = scale1 < scale2 ? scale1 : scale2; - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - } else { - result_resize = result; - } - return result_resize; - } - - Mat uniformResizePlates (const Mat &result, float &scale) { - const int RESULTWIDTH = kPlateResizeWidth; // 640 930 - const int RESULTHEIGHT = kPlateResizeHeight; // 540 710 - - Mat img_window; - img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); - - int nRows = result.rows; - int nCols = result.cols; - - Mat result_resize; - if (nCols <= img_window.cols && nRows <= img_window.rows) { - result_resize = result; - } - else if (nCols > img_window.cols && nRows <= img_window.rows) { - scale = float(img_window.cols) / float(nCols); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } - else if (nCols <= img_window.cols && nRows > img_window.rows) { - scale = float(img_window.rows) / float(nRows); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } - else if (nCols > img_window.cols && nRows > img_window.rows) { - float scale1 = float(img_window.cols) / float(nCols); - float scale2 = float(img_window.rows) / float(nRows); - scale = scale1 < scale2 ? scale1 : scale2; - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - } - else { - result_resize = result; - } - return result_resize; - } - - - - void showDectectResults(const Mat &img, const vector &plateVec, size_t num) { - int index = 0; - if (1) { - Mat result; - img.copyTo(result); - for (size_t j = 0; j < plateVec.size(); j++) { - // add plates to left corner - const CPlate& item = plateVec.at(j); - Mat plateMat = item.getPlateMat(); - - int height = 36; - int width = 136; - if (height * index + height < result.rows) { - Mat imageRoi = result(Rect(0, 0 + height * index, width, height)); - addWeighted(imageRoi, 0, plateMat, 1, 0, imageRoi); - } - index++; - - // draw the bouding box - RotatedRect theRect = item.getPlatePos(); - float scale = item.getPlateScale(); - RotatedRect minRect = scaleBackRRect(theRect, scale); - - Point2f rect_points[4]; - minRect.points(rect_points); - Scalar lineColor = Scalar(255, 255, 255); - if (item.getPlateLocateType() == SOBEL) lineColor = Scalar(255, 0, 0); - if (item.getPlateLocateType() == COLOR) lineColor = Scalar(0, 255, 0); - if (item.getPlateLocateType() == CMSER) lineColor = Scalar(0, 0, 255); - - for (int j = 0; j < 4; j++) - line(result, rect_points[j], rect_points[(j + 1) % 4], lineColor, 2, 8); - } - showResult(result); - } - } - - Mat showResult(const Mat &result, int img_index) { - namedWindow("EasyPR", CV_WINDOW_AUTOSIZE); - - const int RESULTWIDTH = kShowWindowWidth; // 640 930 - const int RESULTHEIGHT = kShowWindowHeight; // 540 710 - - Mat img_window; - img_window.create(RESULTHEIGHT, RESULTWIDTH, CV_8UC3); - - int nRows = result.rows; - int nCols = result.cols; - - Mat result_resize; - if (nCols <= img_window.cols && nRows <= img_window.rows) { - result_resize = result; - - } else if (nCols > img_window.cols && nRows <= img_window.rows) { - float scale = float(img_window.cols) / float(nCols); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } else if (nCols <= img_window.cols && nRows > img_window.rows) { - float scale = float(img_window.rows) / float(nRows); - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - - } else if (nCols > img_window.cols && nRows > img_window.rows) { - float scale1 = float(img_window.cols) / float(nCols); - float scale2 = float(img_window.rows) / float(nRows); - float scale = scale1 < scale2 ? scale1 : scale2; - resize(result, result_resize, Size(), scale, scale, CV_INTER_AREA); - } - else { - result_resize = result; - } - - Mat imageRoi = img_window(Rect((RESULTWIDTH - result_resize.cols) / 2, - (RESULTHEIGHT - result_resize.rows) / 2, - result_resize.cols, result_resize.rows)); - addWeighted(imageRoi, 0, result_resize, 1, 0, imageRoi); - - if (1) { - imshow("EasyPR", img_window); - waitKey(0); - destroyWindow("EasyPR"); - } - - if (1) { - std::stringstream ss(std::stringstream::in | std::stringstream::out); - ss << "resources/image/tmp/Result/plate_" << img_index << ".jpg"; - imwrite(ss.str(), img_window); - } - - return img_window; - } - - Rect rectEnlarge(const Rect &src, const int mat_width, const int mat_height) { - float w = (float) src.width; - float h = (float) src.height; - // enlarge the rect, - // width to 120% - // height to 105% - float new_w = w * 1.2f; - float new_h = h * 1.05f; - - Rect_ boudRect; - boudRect.x = (float) src.x - w * 0.1f; - boudRect.y = (float) src.y - h * 0.025f; - boudRect.width = new_w; - boudRect.height = new_h; - - float tl_x = boudRect.x > 0 ? boudRect.x : 0; - float tl_y = boudRect.y > 0 ? boudRect.y : 0; - - float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1 - ? boudRect.x + boudRect.width - 1 - : mat_width - 1; - float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1 - ? boudRect.y + boudRect.height - 1 - : mat_height - 1; - - float roi_width = br_x - tl_x + 1; - float roi_height = br_y - tl_y + 1; - - Rect dst(0, 0, 0, 0); - if (roi_width <= 0 || roi_height <= 0) - return src; - - //! a new rect not out the range of mat - dst = Rect_(tl_x, tl_y, roi_width, roi_height); - return dst; - - } - - Rect rectFit(const Rect &src, const int mat_width, const int mat_height) { - float w = (float)src.width; - float h = (float)src.height; - float new_w = h * 0.5f; - float new_h = h * 1.05f; - - if (new_w <= w || new_h <= h) { - return src; - } - - float ex_w = (new_w - w) * 0.5f; - float ex_h = (new_h - h) * 0.5f; - - Rect_ boudRect; - boudRect.x = (float)src.x - ex_w; - boudRect.y = (float)src.y - ex_h; - boudRect.width = new_w; - boudRect.height = new_h; - - float tl_x = boudRect.x > 0 ? boudRect.x : 0; - float tl_y = boudRect.y > 0 ? boudRect.y : 0; - - float br_x = boudRect.x + boudRect.width - 1 <= mat_width - 1 - ? boudRect.x + boudRect.width - 1 - : mat_width - 1; - float br_y = boudRect.y + boudRect.height - 1 < mat_height - 1 - ? boudRect.y + boudRect.height - 1 - : mat_height - 1; - - float roi_width = br_x - tl_x + 1; - float roi_height = br_y - tl_y + 1; - - Rect dst(0, 0, 0, 0); - if (roi_width <= 2 || roi_height <= 2) - return src; - - //! a new rect not out the range of mat - dst = Rect_(tl_x, tl_y, roi_width - 1, roi_height - 1); - return dst; - - } - - - void writeTempImage(const Mat &outImg, const string path, int index) { - std::stringstream ss(std::stringstream::in | std::stringstream::out); - time_t t = time(0); // get time now - struct tm *now = localtime(&t); - char buf[80]; - strftime(buf, sizeof(buf), "%Y-%m-%d %H_%M_%S", now); - ss << "resources/image/tmp/" << path << "_" << std::string(buf) << "_" << index << ".jpg"; - imwrite(ss.str(), outImg); - } -} \ No newline at end of file diff --git a/src/core/feature.cpp b/src/core/feature.cpp deleted file mode 100644 index ddced9a..0000000 --- a/src/core/feature.cpp +++ /dev/null @@ -1,466 +0,0 @@ -#include "easypr/core/feature.h" -#include "easypr/core/core_func.h" -#include "thirdparty/LBP/lbp.hpp" - -namespace easypr { - - -Mat getHistogram(Mat in) { - const int VERTICAL = 0; - const int HORIZONTAL = 1; - - // Histogram features - Mat vhist = ProjectedHistogram(in, VERTICAL); - Mat hhist = ProjectedHistogram(in, HORIZONTAL); - - // Last 10 is the number of moments components - int numCols = vhist.cols + hhist.cols; - - Mat out = Mat::zeros(1, numCols, CV_32F); - - int j = 0; - for (int i = 0; i < vhist.cols; i++) { - out.at(j) = vhist.at(i); - j++; - } - for (int i = 0; i < hhist.cols; i++) { - out.at(j) = hhist.at(i); - j++; - } - - return out; -} - -void getHistogramFeatures(const Mat& image, Mat& features) { - Mat grayImage; - cvtColor(image, grayImage, CV_RGB2GRAY); - - //grayImage = histeq(grayImage); - - Mat img_threshold; - threshold(grayImage, img_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - //Mat img_threshold = grayImage.clone(); - //spatial_ostu(img_threshold, 8, 2, getPlateType(image, false)); - - features = getHistogram(img_threshold); -} - -// compute color histom -void getColorFeatures(const Mat& src, Mat& features) { - Mat src_hsv; - - //grayImage = histeq(grayImage); - cvtColor(src, src_hsv, CV_BGR2HSV); - int channels = src_hsv.channels(); - int nRows = src_hsv.rows; - - // consider multi channel image - int nCols = src_hsv.cols * channels; - if (src_hsv.isContinuous()) { - nCols *= nRows; - nRows = 1; - } - - const int sz = 180; - int h[sz] = { 0 }; - - uchar* p; - for (int i = 0; i < nRows; ++i) { - p = src_hsv.ptr(i); - for (int j = 0; j < nCols; j += 3) { - int H = int(p[j]); // 0-180 - if (H > sz - 1) H = sz - 1; - if (H < 0) H = 0; - h[H]++; - } - } - - Mat mhist = Mat::zeros(1, sz, CV_32F); - for (int j = 0; j < sz; j++) { - mhist.at(j) = (float)h[j]; - } - - // Normalize histogram - double min, max; - minMaxLoc(mhist, &min, &max); - - if (max > 0) - mhist.convertTo(mhist, -1, 1.0f / max, 0); - - features = mhist; -} - - -void getHistomPlusColoFeatures(const Mat& image, Mat& features) { - // TODO - Mat feature1, feature2; - getHistogramFeatures(image, feature1); - getColorFeatures(image, feature2); - hconcat(feature1.reshape(1, 1), feature2.reshape(1, 1), features); -} - - -void getSIFTFeatures(const Mat& image, Mat& features) { - // TODO -} - - -//HOG Features -void getHOGFeatures(const Mat& image, Mat& features) { - //HOG descripter - HOGDescriptor hog(cvSize(128, 64), cvSize(16, 16), cvSize(8, 8), cvSize(8, 8), 3); //these parameters work well - std::vector descriptor; - - // resize input image to (128,64) for compute - Size dsize = Size(128,64); - Mat trainImg = Mat(dsize, CV_32S); - resize(image, trainImg, dsize); - - // compute descripter - hog.compute(trainImg, descriptor, Size(8, 8)); - - // copy the result - Mat mat_featrue(descriptor); - mat_featrue.copyTo(features); -} - - -void getHSVHistFeatures(const Mat& image, Mat& features) { - // TODO -} - -//! LBP feature -void getLBPFeatures(const Mat& image, Mat& features) { - - Mat grayImage; - cvtColor(image, grayImage, CV_RGB2GRAY); - - Mat lbpimage; - lbpimage = libfacerec::olbp(grayImage); - Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 32, 4, 4); - - features = lbp_hist; -} - -Mat charFeatures(Mat in, int sizeData) { - const int VERTICAL = 0; - const int HORIZONTAL = 1; - - // cut the cetner, will afect 5% perices. - Rect _rect = GetCenterRect(in); - Mat tmpIn = CutTheRect(in, _rect); - //Mat tmpIn = in.clone(); - - // Low data feature - Mat lowData; - resize(tmpIn, lowData, Size(sizeData, sizeData)); - - // Histogram features - Mat vhist = ProjectedHistogram(lowData, VERTICAL); - Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); - - // Last 10 is the number of moments components - int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols; - - Mat out = Mat::zeros(1, numCols, CV_32F); - // Asign values to - - int j = 0; - for (int i = 0; i < vhist.cols; i++) { - out.at(j) = vhist.at(i); - j++; - } - for (int i = 0; i < hhist.cols; i++) { - out.at(j) = hhist.at(i); - j++; - } - for (int x = 0; x < lowData.cols; x++) { - for (int y = 0; y < lowData.rows; y++) { - out.at(j) += (float)lowData.at (x, y); - j++; - } - } - - //std::cout << out << std::endl; - - return out; -} - - -Mat charFeatures2(Mat in, int sizeData) { - const int VERTICAL = 0; - const int HORIZONTAL = 1; - - // cut the cetner, will afect 5% perices. - Rect _rect = GetCenterRect(in); - Mat tmpIn = CutTheRect(in, _rect); - //Mat tmpIn = in.clone(); - - // Low data feature - Mat lowData; - resize(tmpIn, lowData, Size(sizeData, sizeData)); - - // Histogram features - Mat vhist = ProjectedHistogram(lowData, VERTICAL); - Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); - - // Last 10 is the number of moments components - int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols; - - Mat out = Mat::zeros(1, numCols, CV_32F); - - int j = 0; - for (int i = 0; i < vhist.cols; i++) { - out.at(j) = vhist.at(i); - j++; - } - for (int i = 0; i < hhist.cols; i++) { - out.at(j) = hhist.at(i); - j++; - } - for (int x = 0; x < lowData.cols; x++) { - for (int y = 0; y < lowData.rows; y++) { - out.at(j) += (float)lowData.at (x, y); - j++; - } - } - - //std::cout << out << std::endl; - - return out; -} - -Mat charProjectFeatures(const Mat& in, int sizeData) { - const int VERTICAL = 0; - const int HORIZONTAL = 1; - - SHOW_IMAGE(in, 0); - // cut the cetner, will afect 5% perices. - - Mat lowData; - resize(in, lowData, Size(sizeData, sizeData)); - - SHOW_IMAGE(lowData, 0); - // Histogram features - Mat vhist = ProjectedHistogram(lowData, VERTICAL); - Mat hhist = ProjectedHistogram(lowData, HORIZONTAL); - - // Last 10 is the number of moments components - int numCols = vhist.cols + hhist.cols; - - Mat out = Mat::zeros(1, numCols, CV_32F); - - int j = 0; - for (int i = 0; i < vhist.cols; i++) { - out.at(j) = vhist.at(i); - j++; - } - for (int i = 0; i < hhist.cols; i++) { - out.at(j) = hhist.at(i); - j++; - } - //std::cout << out << std::endl; - - return out; -} - -void getGrayCharFeatures(const Mat& grayChar, Mat& features) { - // TODO: check channnels == 1 - SHOW_IMAGE(grayChar, 0); - SHOW_IMAGE(255 - grayChar, 0); - - // resize to uniform size, like 20x32 - bool useResize = false; - bool useConvert = true; - bool useMean = true; - bool useLBP = false; - - Mat char_mat; - if (useResize) { - char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); - } else { - char_mat = grayChar; - } - SHOW_IMAGE(char_mat, 0); - - // convert to float - Mat float_img; - if (useConvert) { - float scale = 1.f / 255; - char_mat.convertTo(float_img, CV_32FC1, scale, 0); - } else { - float_img = char_mat; - } - SHOW_IMAGE(float_img, 0); - - // cut from mean, it can be optional - - Mat mean_img; - if (useMean) { - float_img -= mean(float_img); - mean_img = float_img; - } else { - mean_img = float_img; - } - SHOW_IMAGE(mean_img, 0); - - // use lbp to get features, it can be changed to other - Mat feautreImg; - if (useLBP) { - Mat lbpimage = libfacerec::olbp(char_mat); - SHOW_IMAGE(lbpimage, 0); - feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); - } else { - feautreImg = mean_img.reshape(1, 1); - } - - // return back - features = feautreImg; -} - - -void getGrayPlusProject(const Mat& grayChar, Mat& features) -{ - // TODO: check channnels == 1 - SHOW_IMAGE(grayChar, 0); - SHOW_IMAGE(255 - grayChar, 0); - - // resize to uniform size, like 20x32 - bool useResize = false; - bool useConvert = true; - bool useMean = true; - bool useLBP = false; - - Mat char_mat; - if (useResize) { - char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); - } - else { - char_mat = grayChar; - } - SHOW_IMAGE(char_mat, 0); - - // convert to float - Mat float_img; - if (useConvert) { - float scale = 1.f / 255; - char_mat.convertTo(float_img, CV_32FC1, scale, 0); - } - else { - float_img = char_mat; - } - SHOW_IMAGE(float_img, 0); - - // cut from mean, it can be optional - - Mat mean_img; - if (useMean) { - float_img -= mean(float_img); - mean_img = float_img; - } - else { - mean_img = float_img; - } - SHOW_IMAGE(mean_img, 0); - - // use lbp to get features, it can be changed to other - Mat feautreImg; - if (useLBP) { - Mat lbpimage = libfacerec::olbp(char_mat); - SHOW_IMAGE(lbpimage, 0); - feautreImg = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); - } - else { - feautreImg = mean_img.reshape(1, 1); - } - SHOW_IMAGE(grayChar, 0); - Mat binaryChar; - threshold(grayChar, binaryChar, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - SHOW_IMAGE(binaryChar, 0); - Mat projectFeature = charProjectFeatures(binaryChar, 32); - - hconcat(feautreImg.reshape(1, 1), projectFeature.reshape(1, 1), features); -} - - -void getGrayPlusLBP(const Mat& grayChar, Mat& features) -{ - // TODO: check channnels == 1 - SHOW_IMAGE(grayChar, 0); - SHOW_IMAGE(255 - grayChar, 0); - - // resize to uniform size, like 20x32 - bool useResize = false; - bool useConvert = true; - bool useMean = true; - bool useLBP = true; - - Mat char_mat; - if (useResize) { - char_mat.create(kGrayCharHeight, kGrayCharWidth, CV_8UC1); - resize(grayChar, char_mat, char_mat.size(), 0, 0, INTER_LINEAR); - } - else { - char_mat = grayChar; - } - SHOW_IMAGE(char_mat, 0); - - // convert to float - Mat float_img; - if (useConvert) { - float scale = 1.f / 255; - char_mat.convertTo(float_img, CV_32FC1, scale, 0); - } - else { - float_img = char_mat; - } - SHOW_IMAGE(float_img, 0); - - // cut from mean, it can be optional - - Mat mean_img; - if (useMean) { - float_img -= mean(float_img); - mean_img = float_img; - } - else { - mean_img = float_img; - } - SHOW_IMAGE(mean_img, 0); - - // use lbp to get features, it can be changed to other - Mat originImage = mean_img.clone(); - Mat lbpimage = libfacerec::olbp(mean_img); - SHOW_IMAGE(lbpimage, 0); - lbpimage = libfacerec::spatial_histogram(lbpimage, kCharLBPPatterns, kCharLBPGridX, kCharLBPGridY); - - // 32x20 + 16x16 - hconcat(mean_img.reshape(1, 1), lbpimage.reshape(1, 1), features); -} - -void getLBPplusHistFeatures(const Mat& image, Mat& features) { - Mat grayImage; - cvtColor(image, grayImage, CV_RGB2GRAY); - - Mat lbpimage; - lbpimage = libfacerec::olbp(grayImage); - Mat lbp_hist = libfacerec::spatial_histogram(lbpimage, 64, 8, 4); - //features = lbp_hist.reshape(1, 1); - - Mat greyImage; - cvtColor(image, greyImage, CV_RGB2GRAY); - - //grayImage = histeq(grayImage); - Mat img_threshold; - threshold(greyImage, img_threshold, 0, 255, - CV_THRESH_OTSU + CV_THRESH_BINARY); - Mat histomFeatures = getHistogram(img_threshold); - - hconcat(lbp_hist.reshape(1, 1), histomFeatures.reshape(1, 1), features); - //std::cout << features << std::endl; - //features = histomFeatures; -} - -} diff --git a/src/core/params.cpp b/src/core/params.cpp deleted file mode 100644 index 8809043..0000000 --- a/src/core/params.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "easypr/core/params.h" - -namespace easypr { - CParams* CParams::instance_ = nullptr; - - CParams* CParams::instance() { - if (!instance_) { - instance_ = new CParams; - } - return instance_; - } -}/*! \namespace easypr*/ \ No newline at end of file diff --git a/src/core/plate_detect.cpp b/src/core/plate_detect.cpp deleted file mode 100644 index 26d1ad9..0000000 --- a/src/core/plate_detect.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "easypr/core/plate_detect.h" -#include "easypr/util/util.h" -#include "easypr/core/core_func.h" -#include "easypr/config.h" - -namespace easypr { - - CPlateDetect::CPlateDetect() { - m_plateLocate = new CPlateLocate(); - m_maxPlates = 3; - m_type = 0; - m_showDetect = false; - } - - CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); } - - int CPlateDetect::plateDetect(Mat src, std::vector &resultVec, int type, - bool showDetectArea, int img_index) { - std::vector sobel_Plates; - sobel_Plates.reserve(16); - std::vector color_Plates; - color_Plates.reserve(16); - std::vector mser_Plates; - mser_Plates.reserve(16); - std::vector all_result_Plates; - all_result_Plates.reserve(64); -#pragma omp parallel sections - { -#pragma omp section - { - if (!type || type & PR_DETECT_SOBEL) { - m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index); - } - } -#pragma omp section - { - if (!type || type & PR_DETECT_COLOR) { - m_plateLocate->plateColorLocate(src, color_Plates, img_index); - } - } -#pragma omp section - { - if (!type || type & PR_DETECT_CMSER) { - m_plateLocate->plateMserLocate(src, mser_Plates, img_index); - } - } - } - for (auto plate : sobel_Plates) { - plate.setPlateLocateType(SOBEL); - all_result_Plates.push_back(plate); - } - for (auto plate : color_Plates) { - plate.setPlateLocateType(COLOR); - all_result_Plates.push_back(plate); - } - for (auto plate : mser_Plates) { - plate.setPlateLocateType(CMSER); - all_result_Plates.push_back(plate); - } - // use nms to judge plate - PlateJudge::instance()->plateJudgeUsingNMS(all_result_Plates, resultVec, m_maxPlates); - - if (0) - showDectectResults(src, resultVec, m_maxPlates); - return 0; - } - - int CPlateDetect::plateDetect(Mat src, std::vector &resultVec, int img_index) { - int result = plateDetect(src, resultVec, m_type, false, img_index); - return result; - } - - void CPlateDetect::LoadSVM(std::string path) { - PlateJudge::instance()->LoadModel(path); - } - -} \ No newline at end of file diff --git a/src/core/plate_judge.cpp b/src/core/plate_judge.cpp deleted file mode 100644 index 4dfa032..0000000 --- a/src/core/plate_judge.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include "easypr/core/plate_judge.h" -#include "easypr/config.h" -#include "easypr/core/core_func.h" -#include "easypr/core/params.h" - -namespace easypr { - - PlateJudge* PlateJudge::instance_ = nullptr; - - PlateJudge* PlateJudge::instance() { - if (!instance_) { - instance_ = new PlateJudge; - } - return instance_; - } - - PlateJudge::PlateJudge() { - bool useLBP = false; - if (useLBP) { - LOAD_SVM_MODEL(svm_, kLBPSvmPath); - extractFeature = getLBPFeatures; - } - else { - LOAD_SVM_MODEL(svm_, kHistSvmPath); - extractFeature = getHistomPlusColoFeatures; - } - } - - void PlateJudge::LoadModel(std::string path) { - if (path != std::string(kDefaultSvmPath)) { - if (!svm_->empty()) - svm_->clear(); - LOAD_SVM_MODEL(svm_, path); - } - } - - // set the score of plate - // 0 is plate, -1 is not. - int PlateJudge::plateSetScore(CPlate& plate) { - Mat features; - extractFeature(plate.getPlateMat(), features); - float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT); - //std::cout << "score:" << score << std::endl; - if (0) { - imshow("plate", plate.getPlateMat()); - waitKey(0); - destroyWindow("plate"); - } - // score is the distance of margin,below zero is plate, up is not - // when score is below zero, the samll the value, the more possibliy to be a plate. - plate.setPlateScore(score); - if (score < 0.5) return 0; - else return -1; - } - - int PlateJudge::plateJudge(const Mat& plateMat) { - CPlate plate; - plate.setPlateMat(plateMat); - return plateSetScore(plate); - } - - int PlateJudge::plateJudge(const std::vector &inVec, - std::vector &resultVec) { - int num = inVec.size(); - for (int j = 0; j < num; j++) { - Mat inMat = inVec[j]; - - int response = -1; - response = plateJudge(inMat); - - if (response == 0) resultVec.push_back(inMat); - } - return 0; - } - - int PlateJudge::plateJudge(const std::vector &inVec, - std::vector &resultVec) { - int num = inVec.size(); - for (int j = 0; j < num; j++) { - CPlate inPlate = inVec[j]; - Mat inMat = inPlate.getPlateMat(); - int response = -1; - response = plateJudge(inMat); - - if (response == 0) - resultVec.push_back(inPlate); - else { - int w = inMat.cols; - int h = inMat.rows; - Mat tmpmat = inMat(Rect_(w * 0.05, h * 0.1, w * 0.9, h * 0.8)); - Mat tmpDes = inMat.clone(); - resize(tmpmat, tmpDes, Size(inMat.size())); - - response = plateJudge(tmpDes); - if (response == 0) resultVec.push_back(inPlate); - } - } - return 0; - } - - // non-maximum suppression - void NMS(std::vector &inVec, std::vector &resultVec, double overlap) { - std::sort(inVec.begin(), inVec.end()); - std::vector::iterator it = inVec.begin(); - for (; it != inVec.end(); ++it) { - CPlate plateSrc = *it; - //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl; - Rect rectSrc = plateSrc.getPlatePos().boundingRect(); - std::vector::iterator itc = it + 1; - for (; itc != inVec.end();) { - CPlate plateComp = *itc; - Rect rectComp = plateComp.getPlatePos().boundingRect(); - float iou = computeIOU(rectSrc, rectComp); - if (iou > overlap) { - itc = inVec.erase(itc); - } - else { - ++itc; - } - } - } - resultVec = inVec; - } - - // judge plate using nms - int PlateJudge::plateJudgeUsingNMS(const std::vector &inVec, std::vector &resultVec, int maxPlates) { - std::vector plateVec; - int num = inVec.size(); - bool useCascadeJudge = true; - - for (int j = 0; j < num; j++) { - CPlate plate = inVec[j]; - Mat inMat = plate.getPlateMat(); - int result = plateSetScore(plate); - if (0 == result) { - if (0) { - imshow("inMat", inMat); - waitKey(0); - destroyWindow("inMat"); - } - - if (plate.getPlateLocateType() == CMSER) { - int w = inMat.cols; - int h = inMat.rows; - Mat tmpmat = inMat(Rect_(w * 0.05, h * 0.1, w * 0.9, h * 0.8)); - Mat tmpDes = inMat.clone(); - resize(tmpmat, tmpDes, Size(inMat.size())); - plate.setPlateMat(tmpDes); - if (useCascadeJudge) { - int resultCascade = plateSetScore(plate); - if (plate.getPlateLocateType() != CMSER) - plate.setPlateMat(inMat); - if (resultCascade == 0) { - if (0) { - imshow("tmpDes", tmpDes); - waitKey(0); - destroyWindow("tmpDes"); - } - plateVec.push_back(plate); - } - } - else - plateVec.push_back(plate); - } - else - plateVec.push_back(plate); - } - } - - std::vector reDupPlateVec; - double overlap = 0.5; - // double overlap = CParams::instance()->getParam1f(); - // use NMS to get the result plates - NMS(plateVec, reDupPlateVec, overlap); - // sort the plates due to their scores - std::sort(reDupPlateVec.begin(), reDupPlateVec.end()); - // output the plate judge plates - std::vector::iterator it = reDupPlateVec.begin(); - int count = 0; - for (; it != reDupPlateVec.end(); ++it) { - resultVec.push_back(*it); - if (0) { - imshow("plateMat", it->getPlateMat()); - waitKey(0); - destroyWindow("plateMat"); - } - count++; - if (count >= maxPlates) - break; - } - return 0; - } -} diff --git a/src/core/plate_locate.cpp b/src/core/plate_locate.cpp deleted file mode 100644 index 90b8719..0000000 --- a/src/core/plate_locate.cpp +++ /dev/null @@ -1,999 +0,0 @@ -#include "easypr/core/plate_locate.h" -#include "easypr/core/core_func.h" -#include "easypr/util/util.h" -#include "easypr/core/params.h" - -using namespace std; - -namespace easypr { - -const float DEFAULT_ERROR = 0.9f; // 0.6 -const float DEFAULT_ASPECT = 3.75f; // 3.75 - -CPlateLocate::CPlateLocate() { - m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE; - m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH; - m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT; - - m_error = DEFAULT_ERROR; - m_aspect = DEFAULT_ASPECT; - m_verifyMin = DEFAULT_VERIFY_MIN; - m_verifyMax = DEFAULT_VERIFY_MAX; - - m_angle = DEFAULT_ANGLE; - - m_debug = DEFAULT_DEBUG; -} - -void CPlateLocate::setLifemode(bool param) { - if (param) { - setGaussianBlurSize(5); - setMorphSizeWidth(10); - setMorphSizeHeight(3); - setVerifyError(0.75); - setVerifyAspect(4.0); - setVerifyMin(1); - setVerifyMax(200); - } else { - setGaussianBlurSize(DEFAULT_GAUSSIANBLUR_SIZE); - setMorphSizeWidth(DEFAULT_MORPH_SIZE_WIDTH); - setMorphSizeHeight(DEFAULT_MORPH_SIZE_HEIGHT); - setVerifyError(DEFAULT_ERROR); - setVerifyAspect(DEFAULT_ASPECT); - setVerifyMin(DEFAULT_VERIFY_MIN); - setVerifyMax(DEFAULT_VERIFY_MAX); - } -} - -bool CPlateLocate::verifySizes(RotatedRect mr) { - float error = m_error; - // Spain car plate size: 52x11 aspect 4,7272 - // China car plate size: 440mm*140mm,aspect 3.142857 - - // Real car plate size: 136 * 32, aspect 4 - float aspect = m_aspect; - - // Set a min and max area. All other patchs are discarded - // int min= 1*aspect*1; // minimum area - // int max= 2000*aspect*2000; // maximum area - int min = 34 * 8 * m_verifyMin; // minimum area - int max = 34 * 8 * m_verifyMax; // maximum area - - // Get only patchs that match to a respect ratio. - float rmin = aspect - aspect * error; - float rmax = aspect + aspect * error; - - float area = mr.size.height * mr.size.width; - float r = (float) mr.size.width / (float) mr.size.height; - if (r < 1) r = (float) mr.size.height / (float) mr.size.width; - - // cout << "area:" << area << endl; - // cout << "r:" << r << endl; - - if ((area < min || area > max) || (r < rmin || r > rmax)) - return false; - else - return true; -} - -//! mser search method -int CPlateLocate::mserSearch(const Mat &src, vector &out, - vector>& out_plateVec, bool usePlateMser, vector>& out_plateRRect, - int img_index, bool showDebug) { - vector match_grey; - - vector plateVec_blue; - plateVec_blue.reserve(16); - vector plateRRect_blue; - plateRRect_blue.reserve(16); - - vector plateVec_yellow; - plateVec_yellow.reserve(16); - - vector plateRRect_yellow; - plateRRect_yellow.reserve(16); - - mserCharMatch(src, match_grey, plateVec_blue, plateVec_yellow, usePlateMser, plateRRect_blue, plateRRect_yellow, img_index, showDebug); - - out_plateVec.push_back(plateVec_blue); - out_plateVec.push_back(plateVec_yellow); - - out_plateRRect.push_back(plateRRect_blue); - out_plateRRect.push_back(plateRRect_yellow); - - out = match_grey; - - return 0; -} - - -int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out, - vector &outRects) { - Mat match_grey; - - // width is important to the final results; - const int color_morph_width = 10; - const int color_morph_height = 2; - - colorMatch(src, match_grey, r, false); - SHOW_IMAGE(match_grey, 0); - - Mat src_threshold; - threshold(match_grey, src_threshold, 0, 255, - CV_THRESH_OTSU + CV_THRESH_BINARY); - - Mat element = getStructuringElement( - MORPH_RECT, Size(color_morph_width, color_morph_height)); - morphologyEx(src_threshold, src_threshold, MORPH_CLOSE, element); - - //if (m_debug) { - // utils::imwrite("resources/image/tmp/color.jpg", src_threshold); - //} - - src_threshold.copyTo(out); - - - vector> contours; - - findContours(src_threshold, - contours, // a vector of contours - CV_RETR_EXTERNAL, - CV_CHAIN_APPROX_NONE); // all pixels of each contours - - vector>::iterator itc = contours.begin(); - while (itc != contours.end()) { - RotatedRect mr = minAreaRect(Mat(*itc)); - - if (!verifySizes(mr)) - itc = contours.erase(itc); - else { - ++itc; - outRects.push_back(mr); - } - } - - return 0; -} - - -int CPlateLocate::sobelFrtSearch(const Mat &src, - vector> &outRects) { - Mat src_threshold; - - sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth, - m_MorphSizeHeight); - - vector> contours; - findContours(src_threshold, - contours, // a vector of contours - CV_RETR_EXTERNAL, - CV_CHAIN_APPROX_NONE); // all pixels of each contours - - vector>::iterator itc = contours.begin(); - - vector first_rects; - - while (itc != contours.end()) { - RotatedRect mr = minAreaRect(Mat(*itc)); - - - if (verifySizes(mr)) { - first_rects.push_back(mr); - - float area = mr.size.height * mr.size.width; - float r = (float) mr.size.width / (float) mr.size.height; - if (r < 1) r = (float) mr.size.height / (float) mr.size.width; - } - - ++itc; - } - - for (size_t i = 0; i < first_rects.size(); i++) { - RotatedRect roi_rect = first_rects[i]; - - Rect_ safeBoundRect; - if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue; - - outRects.push_back(safeBoundRect); - } - return 0; -} - - -int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint, - vector &outRects) { - Mat bound_threshold; - - sobelOperT(bound, bound_threshold, 3, 6, 2); - - Mat tempBoundThread = bound_threshold.clone(); - - clearLiuDingOnly(tempBoundThread); - - int posLeft = 0, posRight = 0; - if (bFindLeftRightBound(tempBoundThread, posLeft, posRight)) { - - // find left and right bounds to repair - - if (posRight != 0 && posLeft != 0 && posLeft < posRight) { - int posY = int(bound_threshold.rows * 0.5); - for (int i = posLeft + (int) (bound_threshold.rows * 0.1); - i < posRight - 4; i++) { - bound_threshold.data[posY * bound_threshold.cols + i] = 255; - } - } - - utils::imwrite("resources/image/tmp/repaireimg1.jpg", bound_threshold); - - // remove the left and right boundaries - - for (int i = 0; i < bound_threshold.rows; i++) { - bound_threshold.data[i * bound_threshold.cols + posLeft] = 0; - bound_threshold.data[i * bound_threshold.cols + posRight] = 0; - } - utils::imwrite("resources/image/tmp/repaireimg2.jpg", bound_threshold); - } - - vector> contours; - findContours(bound_threshold, - contours, // a vector of contours - CV_RETR_EXTERNAL, - CV_CHAIN_APPROX_NONE); // all pixels of each contours - - vector>::iterator itc = contours.begin(); - - vector second_rects; - while (itc != contours.end()) { - RotatedRect mr = minAreaRect(Mat(*itc)); - second_rects.push_back(mr); - ++itc; - } - - for (size_t i = 0; i < second_rects.size(); i++) { - RotatedRect roi = second_rects[i]; - if (verifySizes(roi)) { - Point2f refcenter = roi.center + refpoint; - Size2f size = roi.size; - float angle = roi.angle; - - RotatedRect refroi(refcenter, size, angle); - outRects.push_back(refroi); - } - } - - return 0; -} - - -int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint, - vector &outRects) { - Mat bound_threshold; - - - sobelOper(bound, bound_threshold, 3, 10, 3); - - utils::imwrite("resources/image/tmp/sobelSecSearch.jpg", bound_threshold); - - vector> contours; - findContours(bound_threshold, - contours, // a vector of contours - CV_RETR_EXTERNAL, - CV_CHAIN_APPROX_NONE); // all pixels of each contours - - vector>::iterator itc = contours.begin(); - - vector second_rects; - while (itc != contours.end()) { - RotatedRect mr = minAreaRect(Mat(*itc)); - second_rects.push_back(mr); - ++itc; - } - - for (size_t i = 0; i < second_rects.size(); i++) { - RotatedRect roi = second_rects[i]; - if (verifySizes(roi)) { - Point2f refcenter = roi.center + refpoint; - Size2f size = roi.size; - float angle = roi.angle; - - RotatedRect refroi(refcenter, size, angle); - outRects.push_back(refroi); - } - } - - return 0; -} - - -int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW, - int morphH) { - Mat mat_blur; - mat_blur = in.clone(); - GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT); - - Mat mat_gray; - if (mat_blur.channels() == 3) - cvtColor(mat_blur, mat_gray, CV_RGB2GRAY); - else - mat_gray = mat_blur; - - int scale = SOBEL_SCALE; - int delta = SOBEL_DELTA; - int ddepth = SOBEL_DDEPTH; - - Mat grad_x, grad_y; - Mat abs_grad_x, abs_grad_y; - - - Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT); - convertScaleAbs(grad_x, abs_grad_x); - - Mat grad; - addWeighted(abs_grad_x, SOBEL_X_WEIGHT, 0, 0, 0, grad); - - Mat mat_threshold; - double otsu_thresh_val = - threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - - - Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH)); - morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element); - - out = mat_threshold; - - return 0; -} - -void deleteNotArea(Mat &inmat, Color color = UNKNOWN) { - Mat input_grey; - cvtColor(inmat, input_grey, CV_BGR2GRAY); - - int w = inmat.cols; - int h = inmat.rows; - - Mat tmpMat = inmat(Rect_(w * 0.15, h * 0.1, w * 0.7, h * 0.7)); - - Color plateType; - if (UNKNOWN == color) { - plateType = getPlateType(tmpMat, true); - } - else { - plateType = color; - } - - Mat img_threshold; - - if (BLUE == plateType) { - img_threshold = input_grey.clone(); - Mat tmp = input_grey(Rect_(w * 0.15, h * 0.15, w * 0.7, h * 0.7)); - int threadHoldV = ThresholdOtsu(tmp); - - threshold(input_grey, img_threshold, threadHoldV, 255, CV_THRESH_BINARY); - // threshold(input_grey, img_threshold, 5, 255, CV_THRESH_OTSU + - // CV_THRESH_BINARY); - - utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold); - - } else if (YELLOW == plateType) { - img_threshold = input_grey.clone(); - Mat tmp = input_grey(Rect_(w * 0.1, h * 0.1, w * 0.8, h * 0.8)); - int threadHoldV = ThresholdOtsu(tmp); - - threshold(input_grey, img_threshold, threadHoldV, 255, - CV_THRESH_BINARY_INV); - - utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold); - - // threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU + - // CV_THRESH_BINARY_INV); - } else - threshold(input_grey, img_threshold, 10, 255, - CV_THRESH_OTSU + CV_THRESH_BINARY); - - //img_threshold = input_grey.clone(); - //spatial_ostu(img_threshold, 8, 2, plateType); - - int posLeft = 0; - int posRight = 0; - - int top = 0; - int bottom = img_threshold.rows - 1; - clearLiuDing(img_threshold, top, bottom); - - if (0) { - imshow("inmat", inmat); - waitKey(0); - destroyWindow("inmat"); - } - - if (bFindLeftRightBound1(img_threshold, posLeft, posRight)) { - inmat = inmat(Rect(posLeft, top, w - posLeft, bottom - top)); - if (0) { - imshow("inmat", inmat); - waitKey(0); - destroyWindow("inmat"); - } - } -} - - -int CPlateLocate::deskew(const Mat &src, const Mat &src_b, - vector &inRects, - vector &outPlates, bool useDeteleArea, Color color) { - Mat mat_debug; - src.copyTo(mat_debug); - - for (size_t i = 0; i < inRects.size(); i++) { - RotatedRect roi_rect = inRects[i]; - - float r = (float) roi_rect.size.width / (float) roi_rect.size.height; - float roi_angle = roi_rect.angle; - - Size roi_rect_size = roi_rect.size; - if (r < 1) { - roi_angle = 90 + roi_angle; - swap(roi_rect_size.width, roi_rect_size.height); - } - - if (m_debug) { - Point2f rect_points[4]; - roi_rect.points(rect_points); - for (int j = 0; j < 4; j++) - line(mat_debug, rect_points[j], rect_points[(j + 1) % 4], - Scalar(0, 255, 255), 1, 8); - } - - // changed - // rotation = 90 - abs(roi_angle); - // rotation < m_angel; - - // m_angle=60 - if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) { - Rect_ safeBoundRect; - bool isFormRect = calcSafeRect(roi_rect, src, safeBoundRect); - if (!isFormRect) continue; - - Mat bound_mat = src(safeBoundRect); - Mat bound_mat_b = src_b(safeBoundRect); - - if (0) { - imshow("bound_mat_b", bound_mat_b); - waitKey(0); - destroyWindow("bound_mat_b"); - } - - Point2f roi_ref_center = roi_rect.center - safeBoundRect.tl(); - - Mat deskew_mat; - if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle || - -90.0 == roi_angle) { - deskew_mat = bound_mat; - } else { - Mat rotated_mat; - Mat rotated_mat_b; - - if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center, roi_angle)) - continue; - - if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center, roi_angle)) - continue; - - // we need affine for rotatioed image - double roi_slope = 0; - // imshow("1roated_mat",rotated_mat); - // imshow("rotated_mat_b",rotated_mat_b); - if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) { - affine(rotated_mat, deskew_mat, roi_slope); - } else - deskew_mat = rotated_mat; - } - - Mat plate_mat; - plate_mat.create(HEIGHT, WIDTH, TYPE); - - // haitungaga add,affect 25% to full recognition. - if (useDeteleArea) - deleteNotArea(deskew_mat, color); - - if (deskew_mat.cols * 1.0 / deskew_mat.rows > 2.3 && deskew_mat.cols * 1.0 / deskew_mat.rows < 6) { - if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT) - resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA); - else - resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC); - - CPlate plate; - plate.setPlatePos(roi_rect); - plate.setPlateMat(plate_mat); - if (color != UNKNOWN) plate.setPlateColor(color); - outPlates.push_back(plate); - } - } - } - return 0; -} - - -bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size, - const Point2f center, const double angle) { - if (0) { - imshow("in", in); - waitKey(0); - destroyWindow("in"); - } - - Mat in_large; - in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type()); - - float x = in_large.cols / 2 - center.x > 0 ? in_large.cols / 2 - center.x : 0; - float y = in_large.rows / 2 - center.y > 0 ? in_large.rows / 2 - center.y : 0; - - float width = x + in.cols < in_large.cols ? in.cols : in_large.cols - x; - float height = y + in.rows < in_large.rows ? in.rows : in_large.rows - y; - - /*assert(width == in.cols); - assert(height == in.rows);*/ - - if (width != in.cols || height != in.rows) return false; - - Mat imageRoi = in_large(Rect_(x, y, width, height)); - addWeighted(imageRoi, 0, in, 1, 0, imageRoi); - - Point2f center_diff(in.cols / 2.f, in.rows / 2.f); - Point2f new_center(in_large.cols / 2.f, in_large.rows / 2.f); - - Mat rot_mat = getRotationMatrix2D(new_center, angle, 1); - - /*imshow("in_copy", in_large); - waitKey(0);*/ - - Mat mat_rotated; - warpAffine(in_large, mat_rotated, rot_mat, Size(in_large.cols, in_large.rows), - CV_INTER_CUBIC); - - /*imshow("mat_rotated", mat_rotated); - waitKey(0);*/ - - Mat img_crop; - getRectSubPix(mat_rotated, Size(rect_size.width, rect_size.height), - new_center, img_crop); - - out = img_crop; - - if (0) { - imshow("out", out); - waitKey(0); - destroyWindow("out"); - } - - /*imshow("img_crop", img_crop); - waitKey(0);*/ - - return true; -} - -bool CPlateLocate::isdeflection(const Mat &in, const double angle, - double &slope) { /*imshow("in",in); - waitKey(0);*/ - if (0) { - imshow("in", in); - waitKey(0); - destroyWindow("in"); - } - - int nRows = in.rows; - int nCols = in.cols; - - assert(in.channels() == 1); - - int comp_index[3]; - int len[3]; - - comp_index[0] = nRows / 4; - comp_index[1] = nRows / 4 * 2; - comp_index[2] = nRows / 4 * 3; - - const uchar* p; - - for (int i = 0; i < 3; i++) { - int index = comp_index[i]; - p = in.ptr(index); - - int j = 0; - int value = 0; - while (0 == value && j < nCols) value = int(p[j++]); - - len[i] = j; - } - - // cout << "len[0]:" << len[0] << endl; - // cout << "len[1]:" << len[1] << endl; - // cout << "len[2]:" << len[2] << endl; - - // len[0]/len[1]/len[2] are used to calc the slope - - double maxlen = max(len[2], len[0]); - double minlen = min(len[2], len[0]); - double difflen = abs(len[2] - len[0]); - - double PI = 3.14159265; - - double g = tan(angle * PI / 180.0); - - if (maxlen - len[1] > nCols / 32 || len[1] - minlen > nCols / 32) { - - double slope_can_1 = - double(len[2] - len[0]) / double(comp_index[1]); - double slope_can_2 = double(len[1] - len[0]) / double(comp_index[0]); - double slope_can_3 = double(len[2] - len[1]) / double(comp_index[0]); - // cout<<"angle:"<=0) - slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1 - : slope_can_2; - // cout << "slope:" << slope << endl; - return true; - } else { - slope = 0; - } - - return false; -} - - -void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) { - // imshow("in", in); - // waitKey(0); - - Point2f dstTri[3]; - Point2f plTri[3]; - - float height = (float) in.rows; - float width = (float) in.cols; - float xiff = (float) abs(slope) * height; - - if (slope > 0) { - - // right, new position is xiff/2 - - plTri[0] = Point2f(0, 0); - plTri[1] = Point2f(width - xiff - 1, 0); - plTri[2] = Point2f(0 + xiff, height - 1); - - dstTri[0] = Point2f(xiff / 2, 0); - dstTri[1] = Point2f(width - 1 - xiff / 2, 0); - dstTri[2] = Point2f(xiff / 2, height - 1); - } else { - - // left, new position is -xiff/2 - - plTri[0] = Point2f(0 + xiff, 0); - plTri[1] = Point2f(width - 1, 0); - plTri[2] = Point2f(0, height - 1); - - dstTri[0] = Point2f(xiff / 2, 0); - dstTri[1] = Point2f(width - 1 - xiff + xiff / 2, 0); - dstTri[2] = Point2f(xiff / 2, height - 1); - } - - Mat warp_mat = getAffineTransform(plTri, dstTri); - - Mat affine_mat; - affine_mat.create((int) height, (int) width, TYPE); - - if (in.rows > HEIGHT || in.cols > WIDTH) - - warpAffine(in, affine_mat, warp_mat, affine_mat.size(), - CV_INTER_AREA); - else - warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC); - - out = affine_mat; -} - -int CPlateLocate::plateColorLocate(Mat src, vector &candPlates, - int index) { - vector rects_color_blue; - rects_color_blue.reserve(64); - vector rects_color_yellow; - rects_color_yellow.reserve(64); - - vector plates_blue; - plates_blue.reserve(64); - vector plates_yellow; - plates_yellow.reserve(64); - - Mat src_clone = src.clone(); - - Mat src_b_blue; - Mat src_b_yellow; -#pragma omp parallel sections - { -#pragma omp section - { - colorSearch(src, BLUE, src_b_blue, rects_color_blue); - deskew(src, src_b_blue, rects_color_blue, plates_blue, true, BLUE); - } -#pragma omp section - { - colorSearch(src_clone, YELLOW, src_b_yellow, rects_color_yellow); - deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW); - } - } - - candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end()); - candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end()); - - return 0; -} - - -//! MSER plate locate -int CPlateLocate::plateMserLocate(Mat src, vector &candPlates, int img_index) { - std::vector channelImages; - std::vector flags; - flags.push_back(BLUE); - flags.push_back(YELLOW); - - bool usePlateMser = false; - int scale_size = 1000; - //int scale_size = CParams::instance()->getParam1i(); - double scale_ratio = 1; - - // only conside blue plate - if (1) { - Mat grayImage; - cvtColor(src, grayImage, COLOR_BGR2GRAY); - channelImages.push_back(grayImage); - } - - for (size_t i = 0; i < channelImages.size(); ++i) { - vector> plateRRectsVec; - vector> platesVec; - vector src_b_vec; - - Mat channelImage = channelImages.at(i); - Mat image = scaleImage(channelImage, Size(scale_size, scale_size), scale_ratio); - - // vector rects; - mserSearch(image, src_b_vec, platesVec, usePlateMser, plateRRectsVec, img_index, false); - - for (size_t j = 0; j < flags.size(); j++) { - vector& plates = platesVec.at(j); - Mat& src_b = src_b_vec.at(j); - Color color = flags.at(j); - - vector rects_mser; - rects_mser.reserve(64); - std::vector deskewPlate; - deskewPlate.reserve(64); - std::vector mserPlate; - mserPlate.reserve(64); - - // deskew for rotation and slope image - for (auto plate : plates) { - RotatedRect rrect = plate.getPlatePos(); - RotatedRect scaleRect = scaleBackRRect(rrect, (float)scale_ratio); - plate.setPlatePos(scaleRect); - plate.setPlateColor(color); - - rects_mser.push_back(scaleRect); - mserPlate.push_back(plate); - } - - Mat resize_src_b; - resize(src_b, resize_src_b, Size(channelImage.cols, channelImage.rows)); - - deskew(src, resize_src_b, rects_mser, deskewPlate, false, color); - - for (auto dplate : deskewPlate) { - RotatedRect drect = dplate.getPlatePos(); - Mat dmat = dplate.getPlateMat(); - - for (auto splate : mserPlate) { - RotatedRect srect = splate.getPlatePos(); - float iou = 0.f; - bool isSimilar = computeIOU(drect, srect, src.cols, src.rows, 0.95f, iou); - if (isSimilar) { - splate.setPlateMat(dmat); - candPlates.push_back(splate); - break; - } - } - } - } - } - - if (0) { - imshow("src", src); - waitKey(0); - destroyWindow("src"); - } - - return 0; -} - -int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW, - int morphH) { - Mat mat_blur; - mat_blur = in.clone(); - GaussianBlur(in, mat_blur, Size(blurSize, blurSize), 0, 0, BORDER_DEFAULT); - - Mat mat_gray; - if (mat_blur.channels() == 3) - cvtColor(mat_blur, mat_gray, CV_BGR2GRAY); - else - mat_gray = mat_blur; - - utils::imwrite("resources/image/tmp/grayblure.jpg", mat_gray); - - // equalizeHist(mat_gray, mat_gray); - - int scale = SOBEL_SCALE; - int delta = SOBEL_DELTA; - int ddepth = SOBEL_DDEPTH; - - Mat grad_x, grad_y; - Mat abs_grad_x, abs_grad_y; - - Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT); - convertScaleAbs(grad_x, abs_grad_x); - - Mat grad; - addWeighted(abs_grad_x, 1, 0, 0, 0, grad); - - utils::imwrite("resources/image/tmp/graygrad.jpg", grad); - - Mat mat_threshold; - double otsu_thresh_val = - threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY); - - utils::imwrite("resources/image/tmp/grayBINARY.jpg", mat_threshold); - - Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH)); - morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element); - - utils::imwrite("resources/image/tmp/phologyEx.jpg", mat_threshold); - - out = mat_threshold; - - return 0; -} - -int CPlateLocate::plateSobelLocate(Mat src, vector &candPlates, - int index) { - vector rects_sobel_all; - rects_sobel_all.reserve(256); - - vector plates; - plates.reserve(32); - - vector> bound_rects; - bound_rects.reserve(256); - - sobelFrtSearch(src, bound_rects); - - vector> bound_rects_part; - bound_rects_part.reserve(256); - - // enlarge area - for (size_t i = 0; i < bound_rects.size(); i++) { - float fRatio = bound_rects[i].width * 1.0f / bound_rects[i].height; - if (fRatio < 3.0 && fRatio > 1.0 && bound_rects[i].height < 120) { - Rect_ itemRect = bound_rects[i]; - - itemRect.x = itemRect.x - itemRect.height * (4 - fRatio); - if (itemRect.x < 0) { - itemRect.x = 0; - } - itemRect.width = itemRect.width + itemRect.height * 2 * (4 - fRatio); - if (itemRect.width + itemRect.x >= src.cols) { - itemRect.width = src.cols - itemRect.x; - } - - itemRect.y = itemRect.y - itemRect.height * 0.08f; - itemRect.height = itemRect.height * 1.16f; - - bound_rects_part.push_back(itemRect); - } - } - - // second processing to split one -#pragma omp parallel for - for (int i = 0; i < (int)bound_rects_part.size(); i++) { - Rect_ bound_rect = bound_rects_part[i]; - Point2f refpoint(bound_rect.x, bound_rect.y); - - float x = bound_rect.x > 0 ? bound_rect.x : 0; - float y = bound_rect.y > 0 ? bound_rect.y : 0; - - float width = - x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x; - float height = - y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y; - - Rect_ safe_bound_rect(x, y, width, height); - Mat bound_mat = src(safe_bound_rect); - - vector rects_sobel; - rects_sobel.reserve(128); - sobelSecSearchPart(bound_mat, refpoint, rects_sobel); - -#pragma omp critical - { - rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end()); - } - } - -#pragma omp parallel for - for (int i = 0; i < (int)bound_rects.size(); i++) { - Rect_ bound_rect = bound_rects[i]; - Point2f refpoint(bound_rect.x, bound_rect.y); - - float x = bound_rect.x > 0 ? bound_rect.x : 0; - float y = bound_rect.y > 0 ? bound_rect.y : 0; - - float width = - x + bound_rect.width < src.cols ? bound_rect.width : src.cols - x; - float height = - y + bound_rect.height < src.rows ? bound_rect.height : src.rows - y; - - Rect_ safe_bound_rect(x, y, width, height); - Mat bound_mat = src(safe_bound_rect); - - vector rects_sobel; - rects_sobel.reserve(128); - sobelSecSearch(bound_mat, refpoint, rects_sobel); - -#pragma omp critical - { - rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end()); - } - } - - Mat src_b; - sobelOper(src, src_b, 3, 10, 3); - - deskew(src, src_b, rects_sobel_all, plates); - - //for (size_t i = 0; i < plates.size(); i++) - // candPlates.push_back(plates[i]); - - candPlates.insert(candPlates.end(), plates.begin(), plates.end()); - - return 0; -} - - -int CPlateLocate::plateLocate(Mat src, vector &resultVec, int index) { - vector all_result_Plates; - - plateColorLocate(src, all_result_Plates, index); - plateSobelLocate(src, all_result_Plates, index); - plateMserLocate(src, all_result_Plates, index); - - for (size_t i = 0; i < all_result_Plates.size(); i++) { - CPlate plate = all_result_Plates[i]; - resultVec.push_back(plate.getPlateMat()); - } - - return 0; -} - -int CPlateLocate::plateLocate(Mat src, vector &resultVec, int index) { - vector all_result_Plates; - - plateColorLocate(src, all_result_Plates, index); - plateSobelLocate(src, all_result_Plates, index); - plateMserLocate(src, all_result_Plates, index); - - for (size_t i = 0; i < all_result_Plates.size(); i++) { - resultVec.push_back(all_result_Plates[i]); - } - - return 0; -} - -} diff --git a/src/core/plate_recognize.cpp b/src/core/plate_recognize.cpp deleted file mode 100644 index a351709..0000000 --- a/src/core/plate_recognize.cpp +++ /dev/null @@ -1,105 +0,0 @@ -#include "easypr/core/plate_recognize.h" -#include "easypr/config.h" -#include "thirdparty/textDetect/erfilter.hpp" - -namespace easypr { - -CPlateRecognize::CPlateRecognize() { - m_showResult = false; -} - - -// main method, plate recognize, contain two parts -// 1. plate detect -// 2. chars recognize -int CPlateRecognize::plateRecognize(const Mat& src, std::vector &plateVecOut, int img_index) { - // resize to uniform sizes - float scale = 1.f; - Mat img = uniformResize(src, scale); - - // 1. plate detect - std::vector plateVec; - int resultPD = plateDetect(img, plateVec, img_index); - if (resultPD == 0) { - size_t num = plateVec.size(); - for (size_t j = 0; j < num; j++) { - CPlate& item = plateVec.at(j); - Mat plateMat = item.getPlateMat(); - SHOW_IMAGE(plateMat, 0); - - // scale the rect to src; - item.setPlateScale(scale); - RotatedRect rect = item.getPlatePos(); - item.setPlatePos(scaleBackRRect(rect, 1.f / scale)); - - // get plate color - Color color = item.getPlateColor(); - if (color == UNKNOWN) { - color = getPlateType(plateMat, true); - item.setPlateColor(color); - } - std::string plateColor = getPlateColor(color); - if (0) { - std::cout << "plateColor:" << plateColor << std::endl; - } - - // 2. chars recognize - std::string plateIdentify = ""; - int resultCR = charsRecognise(item, plateIdentify); - if (resultCR == 0) { - std::string license = plateColor + ":" + plateIdentify; - item.setPlateStr(license); - plateVecOut.push_back(item); - if (0) std::cout << "resultCR:" << resultCR << std::endl; - } - else { - std::string license = plateColor; - item.setPlateStr(license); - plateVecOut.push_back(item); - if (0) std::cout << "resultCR:" << resultCR << std::endl; - } - } - if (getResultShow()) { - // param type: 0 detect, 1 recognize; - int showType = 1; - if (0 == showType) - showDectectResults(img, plateVec, num); - else - showDectectResults(img, plateVecOut, num); - } - } - return resultPD; -} - -void CPlateRecognize::LoadSVM(std::string path) { - PlateJudge::instance()->LoadModel(path); -} - -void CPlateRecognize::LoadANN(std::string path) { - CharsIdentify::instance()->LoadModel(path); -} - -void CPlateRecognize::LoadChineseANN(std::string path) { - CharsIdentify::instance()->LoadChineseModel(path); -} - -void CPlateRecognize::LoadGrayChANN(std::string path) { - CharsIdentify::instance()->LoadGrayChANN(path); -} - -void CPlateRecognize::LoadChineseMapping(std::string path) { - CharsIdentify::instance()->LoadChineseMapping(path); -} - -// deprected -int CPlateRecognize::plateRecognize(const Mat& src, std::vector &licenseVec) { - vector plates; - int resultPR = plateRecognize(src, plates, 0); - - for (auto plate : plates) { - licenseVec.push_back(plate.getPlateStr()); - } - return resultPR; -} - -} \ No newline at end of file diff --git a/src/src/train/annCh_train.cpp b/src/src/train/annCh_train.cpp index ec98607..fef78cc 100644 --- a/src/src/train/annCh_train.cpp +++ b/src/src/train/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); diff --git a/src/train/annCh_train.cpp b/src/train/annCh_train.cpp deleted file mode 100644 index ec98607..0000000 --- a/src/train/annCh_train.cpp +++ /dev/null @@ -1,277 +0,0 @@ -#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; - } - - // using raw data or raw + synthic data. - trainVal(m_number_for_count); -} - -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); -} - - void AnnChTrain::test() { - //TODO -} - -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); - for (int i = 0; i < train_classes.rows; ++i) - train_classes.at(i, train_label[i]) = 1.f; - auto train_data = cv::ml::TrainData::create(train_samples, cv::ml::SampleTypes::ROW_SAMPLE, train_classes); - - // train the data, calculate the cost time - std::cout << "Training ANN chinese model, please wait..." << std::endl; - long start = utils::getTimestamp(); - ann_->train(train_data); - long end = utils::getTimestamp(); - ann_->save(ann_xml_); - 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; - - // test the accuracy_rate in train - if (1) { - int corrects_all = 0, sum_all = train_images.size(); - std::cout << "train_images size: " << sum_all << std::endl; - for (size_t i = 0; i < train_images.size(); ++i) { - cv::Mat img = train_images.at(i); - int label = train_label.at(i); - auto char_key = kChars[label + kCharsTotalNumber - classNumber]; - std::pair ch = identifyGrayChinese(img); - if (ch.first == char_key) - corrects_all++; - } - float accuracy_rate = (float)corrects_all / (float)sum_all; - std::cout << "Train error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl; - } - - // test the accuracy_rate in val - if (1) { - int corrects_all = 0, sum_all = val_images.size(); - std::cout << "val_images: " << sum_all << std::endl; - for (size_t i = 0; i < val_images.size(); ++i) { - cv::Mat img = val_images.at(i); - int label = val_labels.at(i); - auto char_key = kChars[label + kCharsTotalNumber - classNumber]; - std::pair ch = identifyGrayChinese(img); - if (ch.first == char_key) - corrects_all++; - } - float accuracy_rate = (float)corrects_all / (float)sum_all; - std::cout << "Test error_rate: " << (1.f - accuracy_rate) * 100.f << "% "<< std::endl; - } - -} - -cv::Ptr AnnChTrain::tdata() { - assert(chars_folder_); - - cv::Mat samples; - std::vector labels; - - std::cout << "Collecting chars in " << chars_folder_ << std::endl; - - int classNumber = 0; - if (type == 0) classNumber = kCharsTotalNumber; - if (type == 1) 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::cout << " >> Featuring characters " << char_key << " in " - << sub_folder << std::endl; - - auto chars_files = utils::getFiles(sub_folder); - for (auto file : chars_files) { - auto img = cv::imread(file, 0); // a grayscale image - auto fps = charFeatures2(img, kPredictSize); - - samples.push_back(fps); - labels.push_back(i); - } - } - - cv::Mat samples_; - samples.convertTo(samples_, CV_32F); - cv::Mat train_classes = - cv::Mat::zeros((int)labels.size(), classNumber, CV_32F); - - for (int i = 0; i < train_classes.rows; ++i) { - train_classes.at(i, labels[i]) = 1.f; - } - - return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, - train_classes); -} -} diff --git a/src/train/ann_train.cpp b/src/train/ann_train.cpp deleted file mode 100644 index 51aec1f..0000000 --- a/src/train/ann_train.cpp +++ /dev/null @@ -1,356 +0,0 @@ -#include -#include - -#include "easypr/train/ann_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/train/create_data.h" -#include "easypr/util/util.h" - -namespace easypr { - -AnnTrain::AnnTrain(const char* chars_folder, const char* xml) - : chars_folder_(chars_folder), ann_xml_(xml) { - ann_ = cv::ml::ANN_MLP::create(); - // type=0, all characters - // type=1, only chinese - type = 0; - kv_ = std::shared_ptr(new Kv); - kv_->load("resources/text/province_mapping"); -} - -void AnnTrain::train() { - - int classNumber = 0; - - cv::Mat layers; - - int input_number = 0; - int hidden_number = 0; - int output_number = 0; - - if (type == 0) { - classNumber = kCharsTotalNumber; - - input_number = kAnnInput; - hidden_number = kNeurons; - output_number = classNumber; - } - else if (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))); - 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 { - // Two-layers neural networks is hard to train, So do not try it - 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 \"ann\") under \"tmp\". \n"); - return; - } - - //using raw data or raw + synthic data. - auto traindata = sdata(350); - - std::cout << "Training ANN model, please wait..." << std::endl; - long start = utils::getTimestamp(); - ann_->train(traindata); - long end = utils::getTimestamp(); - ann_->save(ann_xml_); - - test(); - 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::pair AnnTrain::identifyChinese(cv::Mat input) { - cv::Mat feature = charFeatures2(input, kPredictSize); - 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); -} - - -std::pair AnnTrain::identify(cv::Mat input) { - cv::Mat feature = charFeatures2(input, kPredictSize); - float maxVal = -2; - int result = 0; - - //std::cout << feature << std::endl; - cv::Mat output(1, kCharsTotalNumber, CV_32FC1); - ann_->predict(feature, output); - //std::cout << output << std::endl; - for (int j = 0; j < kCharsTotalNumber; j++) { - float val = output.at(j); - //std::cout << "j:" << j << "val:" << val << std::endl; - if (val > maxVal) { - maxVal = val; - result = j; - } - } - - auto index = result; - if (index < kCharactersNumber) { - return std::make_pair(kChars[index], kChars[index]); - } - else { - const char* key = kChars[index]; - std::string s = key; - std::string province = kv_->get(s); - return std::make_pair(s, province); - } -} - -void AnnTrain::test() { - assert(chars_folder_); - - int classNumber = 0; - if (type == 0) classNumber = kCharsTotalNumber; - if (type == 1) classNumber = kChineseNumber; - - int corrects_all = 0, sum_all = 0; - std::vector rate_list; - 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); - fprintf(stdout, ">> Testing characters %s in %s \n", char_key, sub_folder); - - auto chars_files = utils::getFiles(sub_folder); - int corrects = 0, sum = 0; - std::vector> error_files; - - for (auto file : chars_files) { - auto img = cv::imread(file, 0); // a grayscale image - if (!img.data) { - //cout << "Null pointer!" << endl; - continue; - } - std::pair ch; - - if (type == 0) ch = identify(img); - if (type == 1) ch = identifyChinese(img); - - if (ch.first == char_key) { - ++corrects; - ++corrects_all; - } else { - error_files.push_back(std::make_pair(utils::getFileName(file), ch.second)); - } - ++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); - - std::string error_string; - auto end = error_files.end(); - if (error_files.size() >= 10) { - end -= static_cast(error_files.size() * (1 - 0.1)); - } - for (auto k = error_files.begin(); k != end; ++k) { - auto kv = *k; - error_string.append(" ").append(kv.first).append(": ").append( - kv.second); - if (k != end - 1) { - error_string.append(",\n"); - } else { - error_string.append("\n ..."); - } - } - fprintf(stdout, ">> [\n%s\n ]\n", error_string.c_str()); - } - fprintf(stdout, ">> [sum_all: %d, correct_all: %d, rate: %.4f]\n", sum_all, corrects_all, - (float)corrects_all / (sum_all == 0 ? 1 : sum_all)); - - double rate_sum = std::accumulate(rate_list.begin(), rate_list.end(), 0.0); - double rate_mean = rate_sum / (rate_list.size() == 0 ? 1 : rate_list.size()); - - 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(); - - if (rand_type % 2 == 0) { - int ran_x = rand() % 5 - 2; - int ran_y = rand() % 5 - 2; - - result = translateImg(result, ran_x, ran_y); - } - else if (rand_type % 2 != 0) { - float angle = float(rand() % 15 - 7); - - result = rotateImg(result, angle); - } - - return result; -} - -cv::Ptr AnnTrain::sdata(size_t number_for_count) { - assert(chars_folder_); - - cv::Mat samples; - std::vector labels; - - int classNumber = 0; - if (type == 0) classNumber = kCharsTotalNumber; - if (type == 1) classNumber = kChineseNumber; - - srand((unsigned)time(0)); - 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); - 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) { - auto img = cv::imread(file, 0); // a grayscale image - matVec.push_back(img); - } - - 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); - auto simg = getSyntheticImage(img); - matVec.push_back(simg); - if (1) { - std::stringstream ss(std::stringstream::in | std::stringstream::out); - ss << sub_folder << "/" << i << "_" << t << "_" << ran_num << ".jpg"; - imwrite(ss.str(), simg); - } - } - - fprintf(stdout, ">> Characters count: %d \n", (int)matVec.size()); - - for (auto img : matVec) { - auto fps = charFeatures2(img, kPredictSize); - - samples.push_back(fps); - labels.push_back(i); - } - } - - cv::Mat samples_; - samples.convertTo(samples_, CV_32F); - cv::Mat train_classes = - cv::Mat::zeros((int)labels.size(), classNumber, CV_32F); - - for (int i = 0; i < train_classes.rows; ++i) { - train_classes.at(i, labels[i]) = 1.f; - } - - return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, - train_classes); -} - -cv::Ptr AnnTrain::tdata() { - assert(chars_folder_); - - cv::Mat samples; - std::vector labels; - - std::cout << "Collecting chars in " << chars_folder_ << std::endl; - - int classNumber = 0; - if (type == 0) classNumber = kCharsTotalNumber; - if (type == 1) 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::cout << " >> Featuring characters " << char_key << " in " - << sub_folder << std::endl; - - auto chars_files = utils::getFiles(sub_folder); - for (auto file : chars_files) { - auto img = cv::imread(file, 0); // a grayscale image - auto fps = charFeatures2(img, kPredictSize); - - samples.push_back(fps); - labels.push_back(i); - } - } - - cv::Mat samples_; - samples.convertTo(samples_, CV_32F); - cv::Mat train_classes = - cv::Mat::zeros((int)labels.size(), classNumber, CV_32F); - - for (int i = 0; i < train_classes.rows; ++i) { - train_classes.at(i, labels[i]) = 1.f; - } - - return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, - train_classes); -} -} diff --git a/src/train/create_data.cpp b/src/train/create_data.cpp deleted file mode 100644 index 5eb713a..0000000 --- a/src/train/create_data.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "easypr/train/create_data.h" - -namespace easypr { - - int getBoderColor(Mat img) { - assert(img.channels() == 1); - assert(img.type() == CV_8UC1); - int w = img.cols; - int h = img.rows; - - float sum = 0; - for (int i = 0; i < h; ++i) { - sum += img.at(i, 0); - sum += img.at(i, w-1); - } - for (int j = 0; j < w; ++j) { - sum += img.at(0, j); - sum += img.at(h-1, j); - } - - float avg = sum / float(w + w + h + h); - return int(avg); - } - - // shift an image - Mat translateImg(Mat img, int offsetx, int offsety, int bk){ - Mat dst; - //cout << test << endl; - Mat trans_mat = (Mat_(2, 3) << 1, 0, offsetx, 0, 1, offsety); - //cout << trans_mat << endl; - warpAffine(img, dst, trans_mat, img.size(), 1, 0, Scalar(bk)); - return dst; - } - - // rotate an image - Mat rotateImg(Mat source, float angle, int bk){ - Point2f src_center(source.cols / 2.0F, source.rows / 2.0F); - Mat rot_mat = getRotationMatrix2D(src_center, angle, 1.0); - Mat dst; - warpAffine(source, dst, rot_mat, source.size(), 1, 0, Scalar(bk)); - return dst; - } - - // crop the image - Mat cropImg(Mat src, int x, int y, int shift, int bk){ - int width = src.cols; - int height = src.rows; - - int crop_width = width - shift; - int crop_height = height - shift; - - int x_shift = shift; - int y_shift = shift; - - x = x < x_shift ? x : x_shift; - y = y < y_shift ? y : y_shift; - - Rect rect = Rect(x, y, crop_width, crop_height); - - Mat dst = src(rect); - resize(dst, dst, Size(width, height)); - return dst; - } - - Mat generateSyntheticImage(const Mat& image, int use_swap) { - int rd = rand(); - int bkColor = getBoderColor(image); - Mat result = image.clone(); - if (0 && (rd >> 6 & 1)) { - int shift = 2; - int ran_x = rand() % shift; - int ran_y = rand() % shift; - result = cropImg(result, ran_x, ran_y, shift, bkColor); - } - if (0 && (rd >> 4 & 1)) { - int ran_x = rand() % 2 - 1; - int ran_y = rand() % 2 - 1; - result = translateImg(result, ran_x, ran_y, bkColor); - } - if (1 && (rd >> 2 & 1)) { - float angle = float(rand() % 100) * 0.1f - 5.f; - result = rotateImg(result, angle, bkColor); - } - - return result; - } -} - diff --git a/src/train/svm.7z b/src/train/svm.7z deleted file mode 100644 index ef37b5b..0000000 Binary files a/src/train/svm.7z and /dev/null differ diff --git a/src/train/svm_train.cpp b/src/train/svm_train.cpp deleted file mode 100644 index edd711e..0000000 --- a/src/train/svm_train.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#include "easypr/train/svm_train.h" -#include "easypr/util/util.h" -#include "easypr/config.h" - -#ifdef OS_WINDOWS -#include -#endif - -using namespace cv; -using namespace cv::ml; - -namespace easypr { - -SvmTrain::SvmTrain(const char* plates_folder, const char* xml) - : plates_folder_(plates_folder), svm_xml_(xml) { - assert(plates_folder); - assert(xml); - - extractFeature = getHistomPlusColoFeatures; -} - -void SvmTrain::train() { - svm_ = cv::ml::SVM::create(); - svm_->setType(cv::ml::SVM::C_SVC); - svm_->setKernel(cv::ml::SVM::RBF); - svm_->setDegree(0.1); - // 1.4 bug fix: old 1.4 ver gamma is 1 - svm_->setGamma(0.1); - svm_->setCoef0(0.1); - svm_->setC(1); - svm_->setNu(0.1); - svm_->setP(0.1); - svm_->setTermCriteria(cvTermCriteria(CV_TERMCRIT_ITER, 20000, 0.0001)); - - this->prepare(); - - if (train_file_list_.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 \"SVM\") under \"tmp\". \n"); - return; - } - auto train_data = tdata(); - - fprintf(stdout, ">> Training SVM model, please wait...\n"); - long start = utils::getTimestamp(); - svm_->trainAuto(train_data, 10, SVM::getDefaultGrid(SVM::C), - SVM::getDefaultGrid(SVM::GAMMA), SVM::getDefaultGrid(SVM::P), - SVM::getDefaultGrid(SVM::NU), SVM::getDefaultGrid(SVM::COEF), - SVM::getDefaultGrid(SVM::DEGREE), true); - //svm_->train(train_data); - - long end = utils::getTimestamp(); - fprintf(stdout, ">> Training done. Time elapse: %ldms\n", end - start); - fprintf(stdout, ">> Saving model file...\n"); - svm_->save(svm_xml_); - - fprintf(stdout, ">> Your SVM Model was saved to %s\n", svm_xml_); - fprintf(stdout, ">> Testing...\n"); - - this->test(); - -} - -void SvmTrain::test() { - // 1.4 bug fix: old 1.4 ver there is no null judge - // if (NULL == svm_) - LOAD_SVM_MODEL(svm_, svm_xml_); - - if (test_file_list_.empty()) { - this->prepare(); - } - - double count_all = test_file_list_.size(); - double ptrue_rtrue = 0; - double ptrue_rfalse = 0; - double pfalse_rtrue = 0; - double pfalse_rfalse = 0; - - for (auto item : test_file_list_) { - auto image = cv::imread(item.file); - if (!image.data) { - std::cout << "no" << std::endl; - continue; - } - cv::Mat feature; - extractFeature(image, feature); - - auto predict = int(svm_->predict(feature)); - //std::cout << "predict: " << predict << std::endl; - - auto real = item.label; - if (predict == kForward && real == kForward) ptrue_rtrue++; - if (predict == kForward && real == kInverse) ptrue_rfalse++; - if (predict == kInverse && real == kForward) pfalse_rtrue++; - if (predict == kInverse && real == kInverse) pfalse_rfalse++; - } - - std::cout << "count_all: " << count_all << std::endl; - std::cout << "ptrue_rtrue: " << ptrue_rtrue << std::endl; - std::cout << "ptrue_rfalse: " << ptrue_rfalse << std::endl; - std::cout << "pfalse_rtrue: " << pfalse_rtrue << std::endl; - std::cout << "pfalse_rfalse: " << pfalse_rfalse << std::endl; - - double precise = 0; - if (ptrue_rtrue + ptrue_rfalse != 0) { - precise = ptrue_rtrue / (ptrue_rtrue + ptrue_rfalse); - std::cout << "precise: " << precise << std::endl; - } else { - std::cout << "precise: " - << "NA" << std::endl; - } - - double recall = 0; - if (ptrue_rtrue + pfalse_rtrue != 0) { - recall = ptrue_rtrue / (ptrue_rtrue + pfalse_rtrue); - std::cout << "recall: " << recall << std::endl; - } else { - std::cout << "recall: " - << "NA" << std::endl; - } - - double Fsocre = 0; - if (precise + recall != 0) { - Fsocre = 2 * (precise * recall) / (precise + recall); - std::cout << "Fsocre: " << Fsocre << std::endl; - } else { - std::cout << "Fsocre: " - << "NA" << std::endl; - } -} - -void SvmTrain::prepare() { - srand(unsigned(time(NULL))); - - char buffer[260] = {0}; - - sprintf(buffer, "%s/has/train", plates_folder_); - auto has_file_train_list = utils::getFiles(buffer); - std::random_shuffle(has_file_train_list.begin(), has_file_train_list.end()); - - sprintf(buffer, "%s/has/test", plates_folder_); - auto has_file_test_list = utils::getFiles(buffer); - std::random_shuffle(has_file_test_list.begin(), has_file_test_list.end()); - - sprintf(buffer, "%s/no/train", plates_folder_); - auto no_file_train_list = utils::getFiles(buffer); - std::random_shuffle(no_file_train_list.begin(), no_file_train_list.end()); - - sprintf(buffer, "%s/no/test", plates_folder_); - auto no_file_test_list = utils::getFiles(buffer); - std::random_shuffle(no_file_test_list.begin(), no_file_test_list.end()); - - fprintf(stdout, ">> Collecting train data...\n"); - - for (auto file : has_file_train_list) - train_file_list_.push_back({ file, kForward }); - - for (auto file : no_file_train_list) - train_file_list_.push_back({ file, kInverse }); - - fprintf(stdout, ">> Collecting test data...\n"); - - for (auto file : has_file_test_list) - test_file_list_.push_back({ file, kForward }); - - for (auto file : no_file_test_list) - test_file_list_.push_back({ file, kInverse }); -} - -cv::Ptr SvmTrain::tdata() { - cv::Mat samples; - std::vector responses; - - for (auto f : train_file_list_) { - auto image = cv::imread(f.file); - if (!image.data) { - fprintf(stdout, ">> Invalid image: %s ignore.\n", f.file.c_str()); - continue; - } - cv::Mat feature; - extractFeature(image, feature); - feature = feature.reshape(1, 1); - - samples.push_back(feature); - responses.push_back(int(f.label)); - } - - cv::Mat samples_, responses_; - samples.convertTo(samples_, CV_32FC1); - cv::Mat(responses).copyTo(responses_); - - return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE, responses_); -} - -} // namespace easypr diff --git a/src/train/train.cpp b/src/train/train.cpp deleted file mode 100644 index 79cd69f..0000000 --- a/src/train/train.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "easypr/train/train.h" - -namespace easypr { - -ITrain::ITrain() {} - -ITrain::~ITrain() {} -} diff --git a/src/util/kv.cpp b/src/util/kv.cpp deleted file mode 100644 index edacd3e..0000000 --- a/src/util/kv.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include "easypr/util/kv.h" -#include "easypr/util/util.h" - -namespace easypr { - -Kv::Kv() { } //Kv类的构造函数 - -void Kv::load(const std::string &file) { //一个成员函数,用于从指定的文件中加载键值对 - this->clear();//清空当前的键值对 - std::ifstream reader(file);//打开文件 - assert(reader); - - if (reader.is_open()) { - while (!reader.eof()) { - std::string line; - std::getline(reader, line); - if (line.empty()) continue; -//打开文件并读取每一行 - const auto parse = [](const std::string &str) {//解析键和值 - std::string tmp, key, value; - for (size_t i = 0, len = str.length(); i < len; ++i) { - const char ch = str[i]; - if (ch == ' ') {//遇到空格时 - if (i > 0 && str[i - 1] != ' ' && key.empty()) { - key = tmp; - tmp.clear(); - } - }//将之前的字符串作为键,空格后的字符串作为值 - else { - tmp.push_back(ch); - } - if (i == len - 1) { - value = tmp; - } - } - return std::make_pair(key, value); - }; - - auto kv = parse(line); - this->add(kv.first, kv.second);//解析出的键值对添加到存储中 - } - reader.close();//关闭文件 - } -} - -std::string Kv::get(const std::string &key) {//是一个成员函数,用于获取给定键的值 - if (data_.find(key) == data_.end()) { - std::cerr << "[Kv] cannot find " << key << std::endl;//如果键不存在,它会打印一个错误消息 - return "";//返回一个空字符串。 - } - return data_.at(key);//获取给定键的值 -} - -void Kv::add(const std::string &key, const std::string &value) {//是一个成员函数,用于添加一个键值对 - if (data_.find(key) != data_.end()) {//如果键已经存在 - fprintf(stderr, - "[Kv] find duplicate: %s = %s , ignore\n", - key.c_str(), - value.c_str());//会打印一个错误消息并忽略这个添加操作 - } else { - std::string v(value); -#ifdef OS_WINDOWS//如果在Windows操作系统上 - v = utils::utf8_to_gbk(value.c_str()); //将值从UTF-8编码转换为GBK编码。 -#endif - data_[key] = v;//添加一个键值对 - } -} - -void Kv::remove(const std::string &key) {//是一个成员函数,用于删除一个键值对。 - if (data_.find(key) == data_.end()) {//如果键不存在 - std::cerr << "[Kv] cannot find " << key << std::endl;//打印一个错误消息。 - return; - } - data_.erase(key);//删除一个键值对。 -} - -void Kv::clear() {//是一个成员函数,用于清空所有的键值对。 - data_.clear();//用于清空所有的键值对。 -} - -} diff --git a/src/util/program_options.cpp b/src/util/program_options.cpp deleted file mode 100644 index f12a849..0000000 --- a/src/util/program_options.cpp +++ /dev/null @@ -1,541 +0,0 @@ -#include "easypr/util/program_options.h" - -namespace program_options { - -// class ParseError -//定义了一个名为ParseError的类 -//ParseError类是一个用于处理命令行解析错误的异常类。 -ParseError::ParseError(const std::string& msg) : _msg(msg) {}//是ParseError类的构造函数 -//它接受一个字符串作为参数,并将这个字符串赋值给成员变量_msg。 -const char* ParseError::what() const throw() {//是一个成员函数,它返回一个描述错误的字符串 - std::string msg;//首先创建一个新的字符串 - msg.append("Command line parse error: ").append(_msg).push_back('.');//添加一个错误消息前缀,接着添加成员变量_msg,最后添加一个句点。 - return msg.c_str();//返回这个字符串的C风格字符串。 -} - -ParseError::~ParseError() throw() {}// 是ParseError类的析构函数 - -// class Generator -//定义了一个名为Generator的类,该类用于生成和管理命令行选项解析器和子程序。 -Generator::Generator() : parser_(nullptr) {//Generator类的构造函数 - current_subroutine_ = Subroutine::get_default_name();//初始化parser_为nullptr,设置当前子程序为默认子程序 - add_subroutine(current_subroutine_.c_str());//添加这个子程序。 -} - -Generator::~Generator() {//Generator类的析构函数 - if (parser_) { - delete parser_; - parser_ = nullptr; - }//它删除parser_和所有的子程序 - for (auto it = subroutines_.begin(); it != subroutines_.end(); ++it) { - if (it->second) { - delete it->second;//它删除所有的子程序, - it->second = nullptr;//并将parser_和所有的子程序设置为nullptr。 - } - } -} - -Generator& Generator::make_usage(const char* first_line) {//是一个成员函数 - get_subroutine()->set_first_line(first_line);//它设置当前子程序的第一行 - return *this;//并返回this指针。 -} - -Parser* Generator::make_parser() {//是一个成员函数 - if (parser_) delete parser_; - parser_ = new Parser;//它创建一个新的Parser对象 - parser_->set_usage_subroutines(&subroutines_);//设置其使用的子程序 - return parser_;//并返回这个Parser对象。 -} - -Generator& Generator::add_subroutine(const char* name) {//是成员函数 - add_subroutine(name, "");//它们添加一个新的子程序。 - return *this; -} - -Generator& Generator::add_subroutine(const char* name, - const char* description) {//成员函数 - if (subroutines_.find(name) == subroutines_.end()) {//如果子程序已经存在,它们不会添加。 - // a new subroutine - current_subroutine_ = name;//设立新名字 - Subroutine* routine = new Subroutine(name, description);//新建一个子程序。 - subroutines_.insert({current_subroutine_, routine});//添加一个新的子程序。 - } - return *this; -} - -std::map Generator::get_subroutine_list() {//是一个成员函数,它返回一个包含所有子程序名称和描述的映射。 - std::map kv; - for (auto pr : subroutines_) {//遍历所有的子程序 - Subroutine* subroutine = pr.second;//遍历所有的子程序 - if (subroutine->get_name() != Subroutine::get_default_name())//如果子程序的名称不是默认名称 - kv[subroutine->get_name()] = subroutine->get_description();//将子程序的名称和描述添加到映射中。 - } - return std::move(kv);//返回一个包含所有子程序名称和描述的映射 -} - -bool Generator::add_usage_line(const char* option, const char* default_value, - const char* description) {// 是一个成员函数,它添加一个使用行到当前子程序。 - std::string option_str(option); - auto delimiter_pos = option_str.find(kDelimiter); -//定义新变量,将选项字符串赋给新变量 - std::string option_short; - std::string option_long; -//将选项字符串分割为短选项和长选项 - if (delimiter_pos != std::string::npos) { - option_short.assign(std::move(option_str.substr(0, delimiter_pos))); - option_long.assign(std::move(option_str.substr(delimiter_pos + 1))); - - Row row;//创建一个Row对象, - row.oshort(option_short); - row.olong(option_long); - row.value(default_value); - row.desc(description); -////设置其短选项、长选项、默认值和描述 - get_subroutine()->add_usage_line(row);//将这个Row对象添加到当前子程序的使用行中。 - return true; - } - return false; -} - -std::ostream& operator<<(std::ostream& out, Generator& generator) {// 是一个输出运算符重载函数,它打印所有子程序的名称和描述。 - for (auto pr : generator.subroutines_) {//遍历所有的子程序 - Subroutine* subroutine = pr.second; - if (subroutine->get_name() != Subroutine::get_default_name()) { - out << subroutine->get_name() << "\t"; - }//如果子程序的名称不是默认名称,就打印子程序的名称 - out << subroutine->get_description();//打印子程序的描述 - if (!subroutine->get_usage().empty()) {//如果子程序的使用信息不为空,就打印一个换行符 - out << std::endl;//打印一个换行符 - } - out << *subroutine;//打印子程序的使用信息。 - } - return out; -} - -// class ParseItem - -ParseItem::ParseItem(const std::string& value) : value_(value) {} - -// class Parser - -ParseItem* Parser::get(const std::string& key) { - if (pr_->find(key) != pr_->end()) { - return (*pr_)[key]; - } - return nullptr; -} - -Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {} - -Parser::~Parser() { this->cleanup(); } - -Parser::ParseResult* Parser::parse(const int argc, const char** argv) { - if (!this->init(argc, argv)) { - return nullptr; - } - auto ibegin = args_.begin() + 1; // ignore the first cmd name - auto iend = args_.end(); - auto it = ibegin; - - if (argc >= 2 && args_[1][0] != '-') { - // the second block may be a subroutine name - // e.g., ./exec pull --option - if (subroutines_ && (subroutines_->find(args_[1]) != subroutines_->end())) { - subroutine_name_ = args_[1]; - it++; // ignore the subroutine name - } else { - subroutine_name_ = args_[1]; - } - } else { - // there is no options as well as subroutine name - // e.g., ./exec - subroutine_name_ = Subroutine::get_default_name(); - } - - std::string block; - std::string previous(*ibegin); - - for (; it != iend; ++it) { - block.assign(*it); - - switch (block.size()) { - case 1: - if (block == "-") { - throw ParseError("single '-' is not allowed"); - } - break; - case 2: - if (block[0] == '-') { - if (block[1] == '-') { - throw ParseError("option '--' is incomplete"); - } else if (block[1] == '=') { - throw ParseError("option '-=' is invalid"); - } else { - // single option - // e.g., ./exec -s - (*pr_)[block.substr(1)] = nullptr; - } - } - break; - default: // >=3 - if (block[0] == '-') { - if (block[1] == '-') { - size_t pos_equal = block.find('='); - if (pos_equal == std::string::npos) { - // a long format option - // e.g., ./exec --option - (*pr_)[block.substr(2)] = nullptr; - } else { - if (pos_equal > 3) { - // e.g, ./exec --op[..=]value - std::string key(block.substr(2, pos_equal - 2)); - if (block.size() > 5) - // e.g, ./exec --op=v - (*pr_)[key] = new ParseItem(block.substr(pos_equal + 1)); - else - (*pr_)[key] = nullptr; - } else { - // a long format option but = is illegal - // e.g., ./exec --o=[...] - (*pr_)[block.substr(2)] = nullptr; - } - } - } else if (block[2] == '=') { - // a single option with = - // e.g., ./exec -o=[...] - std::string key; - key.push_back(block[1]); - if (block.size() > 3) - (*pr_)[key] = new ParseItem(block.substr(3)); - else - (*pr_)[key] = nullptr; - } else { - // a combination options - // e.g., ./exec -ab[...] - auto tbegin = block.begin() + 1; // ignore the first '-' - auto tend = block.end(); - auto t = tbegin; - - for (; t != tend; ++t) { - std::string key; - key.push_back(*t); - (*pr_)[key] = nullptr; - } - } - } - break; - } // switch - - if (block[0] != '-' && previous != block // not the first option - ) { - if (previous[0] != '-') { - // previous is not an option, error occur - // e.g., ./exec abc def - throw ParseError("'" + block + "' is not allowed here"); - } - - std::string key; - - if (previous[0] == '-' && previous[1] == '-') { - // previous is a long format option. - // e.g., ./exec --option value - key = previous.substr(2); - } else { - // it's the value of previous option. - // e.g., ./exec -o [...] - // e.g., ./exec -opq [...] - key.push_back(*(previous.end() - 1)); - } - - if (pr_->find(key) != pr_->end()) { - (*pr_)[key] = new ParseItem(block); - } - } - - previous = block; - } // for - - if (subroutines_) { - this->set_addition(); - } - - return pr_; -} - -Parser::ParseResult* Parser::parse(const char* command_line) { - int i = 0; - std::string block; - std::vector blocks; - char c; - while ((c = command_line[i++]) != '\0') { - if (c != ' ') { - block.push_back(c); - } else { - if (!block.empty()) { - blocks.push_back(block); - } - block.clear(); - } - } - if (!block.empty()) { - blocks.push_back(block); - } - size_t size = blocks.size(); // argc - char** argv = new char*[size]; - i = 0; - std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) { - argv[i++] = const_cast(b.c_str()); - }); - auto pr = - this->parse(static_cast(size), const_cast(argv)); - - delete[] argv; - argv = nullptr; - - return pr; -} - -bool Parser::has(const char* key) { - std::string skey(key); - - if (pr_ && !pr_->empty() && !skey.empty()) { - if (skey[0] == '-') { - // check combination options, e.g., Parser::has("-xyz") - for (size_t i = 1; i < skey.size(); ++i) { - std::string tkey; - tkey.push_back(skey[i]); - if (pr_->find(tkey) == pr_->end()) { - return false; - } - } - return true; - } else { - // check single option, e.g., Parser::has("x") - return pr_->find(skey) != pr_->end(); - } - } - return false; -} - -bool Parser::has_or(std::initializer_list options) { - if (options.size() == 0) { - return false; - } - for (auto key : options) { - if (this->has(key)) return true; - } - return false; -} - -bool Parser::has_and(std::initializer_list options) { - if (options.size() == 0) { - return false; - } - for (auto key : options) { - if (!this->has(key)) return false; - } - return true; -} - -bool Parser::init(const int argc, const char** argv) { - argc_ = argc; - // argv_ = argv; - // don't save it, point to a local var in parse(const char* command_line). - // use member var args_ instead. - if (argc > 0) { - this->cleanup(); - - args_.reserve(static_cast(argc_)); - - for (int i = 0; i < argc_; ++i) { - args_.push_back(argv[i]); - } - - pr_ = new Parser::ParseResult; - return true; - } - return false; -} - -void Parser::cleanup() { - args_.clear(); - if (pr_) { - auto ibegin = pr_->begin(); - auto iend = pr_->end(); - auto it = ibegin; - for (; it != iend; ++it) { - ParseItem* item = it->second; - if (item) delete item; - } - delete pr_; - pr_ = nullptr; - } -} - -void Parser::set_addition() { - if (subroutines_->find(subroutine_name_) != subroutines_->end()) { - for (const Row& row : *(subroutines_->at(subroutine_name_))) { - // assume both -o and --option are allowed, - // but only provide -o, - // then set the another --option. - // vice versa. - const std::string& def = row.value(); - const std::string& ops = row.oshort(); - const std::string& opl = row.olong(); - ParseResult& pr = *pr_; - - bool has_short = this->has(ops.c_str()); - bool has_long = this->has(opl.c_str()); - - // assume -o [ --option ] arg = 1 - // but not provide option value, - // then set to default 1. - // otherwise, both set to user defined value - - if (!ops.empty()) { - if (has_short) { - if (pr[ops] != nullptr && !opl.empty()) { - pr[opl] = new ParseItem(std::move(pr[ops]->val())); - } else if (pr[ops] == nullptr && !def.empty()) { - pr[ops] = new ParseItem(std::move(def)); - if (!opl.empty()) pr[opl] = new ParseItem(std::move(def)); - } else { - pr[opl] = nullptr; - } - } - } - - if (!opl.empty()) { - if (has_long) { - if (pr[opl] != nullptr && !ops.empty()) { - pr[ops] = new ParseItem(std::move(pr[opl]->val())); - } else if (pr[opl] == nullptr && !def.empty()) { - if (!ops.empty()) pr[ops] = new ParseItem(std::move(def)); - pr[opl] = new ParseItem(std::move(def)); - } else { - pr[ops] = nullptr; - } - } - } - - if (!has_long && !has_short && !def.empty()) { - if (!opl.empty()) pr[opl] = new ParseItem(std::move(def)); - if (!ops.empty()) pr[ops] = new ParseItem(std::move(def)); - } - } // for - } // if -} - -// class Row - -Row::Row() : require_value(true) {} - -// class Subroutine - -Subroutine::Subroutine() : first_line_("") {} - -Subroutine::Subroutine(const char* name, const char* description) - : first_line_(""), description_(description), name_(name) { - usages_.reserve(5); -} - -void Subroutine::print_with_row(std::ostream& out) { - // print the subroutine name and its description - if (strcmp(get_first_line(), "") != 0) { - // print the first line - out << get_first_line(); - if (!usages_.empty()) { - out << std::endl; - } - } - auto begin = usages_.begin(); - auto end = usages_.end(); - - std::vector row_list; - row_list.reserve(usages_.size()); - - // build usage rows without description field, - // find the max-len row at the same time. - size_t max_len = 0; - std::for_each(begin, end, [&max_len, &row_list](const Row& row) { - std::stringstream ss; - ss << " "; - if (!row.oshort().empty()) { - ss << "-" << row.oshort() << " "; - } - if (!row.olong().empty()) { - if (!row.oshort().empty()) - ss << "[ --" << row.olong() << " ] "; - else - ss << "--" << row.olong() << " "; - } - - if (row.required()) { - ss << "arg "; - if (!row.value().empty()) { - ss << "= " << row.value() << " "; - } - } - - max_len = std::max(max_len, ss.str().size()); - row_list.push_back(std::move(ss.str())); - }); - - // show all rows and align description field - size_t row_count = usages_.size(); - for (size_t i = 0; i < row_count; ++i) { - std::string str_row(std::move(row_list[i])); - // print row without description - out << str_row; - // print spaces - size_t spaces = 0; - size_t len = str_row.size(); - if (max_len > len) spaces = max_len - len; - - while (spaces--) { - out << " "; - } - // print description - out << usages_.at(i).desc() << std::endl; - } -} - -void Subroutine::print_with_template(std::ostream& out) { - for (auto usage : usages_) { - size_t i = 0; - for (auto t = template_str_.begin(); t != template_str_.end(); ++t) { - if (*t == '%') { - switch (*(order_.begin() + i)) { - case Row::kShort: - out << usage.oshort(); - break; - case Row::kLong: - out << usage.olong(); - break; - case Row::kDefault: - out << usage.value(); - break; - case Row::kDescription: - out << usage.desc(); - break; - default: - break; - } - ++i; - } else { - out << *t; - } // if % - } // for template_str_ - out << std::endl; - } // for usages_ -} - -std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) { - if (subroutine.template_str_.empty()) { - subroutine.print_with_row(out); - } else { - subroutine.print_with_template(out); - } - return out; -} -} \ No newline at end of file diff --git a/src/util/util.cpp b/src/util/util.cpp deleted file mode 100644 index b293c2a..0000000 --- a/src/util/util.cpp +++ /dev/null @@ -1,293 +0,0 @@ -#include "easypr/util/util.h" -#include - -#ifdef OS_WINDOWS -#include -#include -#include -#define PATH_DELIMITER '\\' -#ifdef min -#undef min -#endif - -#ifdef max -#undef max -#endif -#elif defined(OS_LINUX) || defined(OS_UNIX) - -#include -#include -#include -#include - -#define PATH_DELIMITER '/' -#endif - -#ifdef OS_UNIX - -#include - -#endif - -#include -#include - -namespace easypr { - -long Utils::getTimestamp() { -#ifdef OS_WINDOWS - return static_cast(cv::getTickCount()); -#endif - -#ifdef OS_LINUX - struct timespec ts; - - clock_gettime(CLOCK_MONOTONIC, &ts); - - return (ts.tv_sec * 1e3 + ts.tv_nsec / 1e6); -#endif - -#ifdef OS_UNIX - // there is no function provided by osx to get system tick count. - // but considering the purpose by using this function, - // we can simply return a millisecond since 1970/1/1 to calc the time elapse. - struct timeb tb; - ftime(&tb); - return long(tb.time * 1e3 + tb.millitm); -#endif -} - -std::string Utils::getFileName(const std::string &path, - const bool postfix /* = false */) { - if (!path.empty()) { - size_t last_slash = utils::get_last_slash(path); - size_t last_dot = path.find_last_of('.'); - - if (last_dot < last_slash || last_dot == std::string::npos) { - // not found the right dot of the postfix, - // return the file name directly - return path.substr(last_slash + 1); - } else { - // the path has a postfix - if (postfix) { - // return the file name including postfix - return path.substr(last_slash + 1); - } - // without postfix - return path.substr(last_slash + 1, last_dot - last_slash - 1); - } - } - return ""; -} - -std::vector Utils::splitString(const std::string &str, - const char delimiter) { - std::vector splited; - std::string s(str); - size_t pos; - - while ((pos = s.find(delimiter)) != std::string::npos) { - std::string sec = s.substr(0, pos); - - if (!sec.empty()) { - splited.push_back(s.substr(0, pos)); - } - - s = s.substr(pos + 1); - } - - splited.push_back(s); - - return splited; -} - -std::vector Utils::getFiles(const std::string &folder, - const bool all /* = true */) { - std::vector files; - std::list subfolders; - subfolders.push_back(folder); -#ifdef OS_WINDOWS - while (!subfolders.empty()) { - std::string current_folder(subfolders.back()); - - if (*(current_folder.end() - 1) != '/') { - current_folder.append("/*"); - } else { - current_folder.append("*"); - } - - subfolders.pop_back(); - - struct _finddata_t file_info; - auto file_handler = _findfirst(current_folder.c_str(), &file_info); - - while (file_handler != -1) { - if (all && - (!strcmp(file_info.name, ".") || !strcmp(file_info.name, ".."))) { - if (_findnext(file_handler, &file_info) != 0) break; - continue; - } - - if (file_info.attrib & _A_SUBDIR) { - // it's a sub folder - if (all) { - // will search sub folder - std::string folder(current_folder); - folder.pop_back(); - folder.append(file_info.name); - - subfolders.push_back(folder.c_str()); - } - } else { - // it's a file - std::string file_path; - // current_folder.pop_back(); - file_path.assign(current_folder.c_str()).pop_back(); - file_path.append(file_info.name); - - files.push_back(file_path); - } - - if (_findnext(file_handler, &file_info) != 0) break; - } // while - _findclose(file_handler); - } -#elif defined(OS_LINUX) || defined(OS_UNIX) - while (!subfolders.empty()) { - std::string current_folder(subfolders.back()); - - if (*(current_folder.end() - 1) != '/') { - current_folder.push_back('/'); - } - - DIR* pdir = opendir(current_folder.c_str()); - - subfolders.pop_back(); - - if (!pdir) { - continue; - } - - dirent* dir = NULL; - - while ((dir = readdir(pdir)) != NULL) { - // iterates the current folder, search file & sub folder - struct stat st; - - if (all && (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."))) { - // must ignore . & .. - continue; - } - - if (!strcmp(dir->d_name, ".DS_Store")) { - // in OSX, 'finder' will create .DS_Store - continue; - } - - std::string file_path; - - file_path.append(current_folder.c_str()); - file_path.append(dir->d_name); - - if (lstat(file_path.c_str(), &st) < 0) { - // perror("lstat"); - continue; - } - - if (S_ISDIR(st.st_mode)) { - // it's a sub folder - if (all) { - // will search sub folder - std::string subfolder(current_folder); - subfolder.append(dir->d_name); - - subfolders.push_back(subfolder.c_str()); - } - } else { - // it's a file - files.push_back(file_path); - } - } // while - closedir(pdir); - } -#endif - return files; -} - -bool Utils::mkdir(const std::string folder) { - std::string folder_builder; - std::string sub; - sub.reserve(folder.size()); - for (auto it = folder.begin(); it != folder.end(); ++it) { - const char c = *it; - sub.push_back(c); - if (c == PATH_DELIMITER || it == folder.end() - 1) { - folder_builder.append(sub); -#ifdef OS_WINDOWS - if (0 != ::_access(folder_builder.c_str(), 0)) { -#else - if (0 != ::access(folder_builder.c_str(), 0)) { -#endif - // this folder not exist -#ifdef OS_WINDOWS - if (0 != ::_mkdir(folder_builder.c_str())) { -#else - if (0 != ::mkdir(folder_builder.c_str(), S_IRWXU)) { -#endif - // create failed - return false; - } - } - sub.clear(); - } - } - return true; -} - -bool Utils::imwrite(const std::string &file, const cv::Mat &image) { - auto folder = file.substr(0, utils::get_last_slash(file)); - Utils::mkdir(folder); - return cv::imwrite(file, image); -} - -#ifdef OS_WINDOWS -std::string Utils::utf8_to_gbk(const char* utf8) { - int len = MultiByteToWideChar(CP_UTF8, 0, utf8, -1, NULL, 0); - wchar_t* wszGBK = new wchar_t[len + 1]; - memset(wszGBK, 0, len * 2 + 2); - MultiByteToWideChar(CP_UTF8, 0, utf8, -1, wszGBK, len); - len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL); - char* szGBK = new char[len + 1]; - memset(szGBK, 0, len + 1); - WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL); - std::string strTemp(szGBK); - if (wszGBK) - delete[] wszGBK; - if (szGBK) - delete[] szGBK; - return strTemp; -} -#endif - -std::size_t Utils::get_last_slash(const std::string &path) { -#ifdef OS_WINDOWS - size_t last_slash_1 = path.find_last_of("\\"); - size_t last_slash_2 = path.find_last_of("/"); - size_t last_slash; - - if (last_slash_1 != std::string::npos && last_slash_2 != std::string::npos) { - // C:/path\\to/file.postfix - last_slash = std::max(last_slash_1, last_slash_2); - } else { - // C:\\path\\to\\file.postfix - // C:/path/to/file.postfix - last_slash = - (last_slash_1 == std::string::npos) ? last_slash_2 : last_slash_1; - } -#else - size_t last_slash = path.find_last_of('/'); -#endif - return last_slash; -} - -} // namespace easypr \ No newline at end of file