Compare commits

..

2 Commits

Author SHA1 Message Date
heureux 3e7dd9deaa zhy
10 months ago
heureux 494b46daee zhy
11 months ago

@ -0,0 +1,347 @@
#include <numeric>//包含了C++标准库中的<numeric>头文件,提供了数值计算的相关函数和模板。
#include <ctime>//包含了C++标准库中的<ctime>头文件,提供了关于时间和日期的相关函数和类型。
#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(); // 创建一个MLPMultilayer Perceptron多层感知器对象用于字符识别
type = 1; // 初始化type为1可能表示某种类型或模式
kv_ = std::shared_ptr<Kv>(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<int>(0) = input_number; // 设置输入层节点数量
layers.at<int>(1) = hidden_number; // 设置隐藏层节点数量
layers.at<int>(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<int>(0) = input_number; // 设置输入层节点数量
layers.at<int>(1) = first_hidden_neurons; // 设置第一层隐藏层节点数量
layers.at<int>(2) = second_hidden_neurons; // 设置第二层隐藏层节点数量
layers.at<int>(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<std::string, std::string> AnnChTrain::identifyGrayChinese(cv::Mat input) {
// 定义特征向量
Mat feature;
// 从输入图像中提取特征
extractFeature(input, feature);
// 初始化最大值为-2
float maxVal = -2;
// 初始化结果为0
int result = 0;
// 定义输出矩阵大小为1行kChineseNumber列数据类型为CV_32FC132位浮点型
cv::Mat output(1, kChineseNumber, CV_32FC1);
// 使用神经网络模型进行预测输入特征向量输出结果到output矩阵中
ann_->predict(feature, output);
// 遍历输出矩阵中的每一个值
for (int j = 0; j < kChineseNumber; j++) {
// 获取当前位置的值
float val = output.at<float>(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_samplesTODO: 这里需要解释这个变量名和变量的具体含义)
cv::Mat train_samples;
// 定义训练图像、验证图像的存储容器TODO: 这里需要解释这些变量名和变量的具体含义)
std::vector<cv::Mat> train_images, val_images;
std::vector<int> train_label, val_labels;
// 设置训练验证集分割比例为0.770%用于训练30%用于验证)
float percentage = 0.7f;
// 设置类别数为kChineseNumberTODO: 需要解释这个变量的具体含义)直接把代码改成评注形式
// 循环遍历每个字符类别
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<cv::Mat> 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<float>(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<std::string, std::string> 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<std::string, std::string> 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<cv::ml::TrainData> AnnChTrain::tdata() {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> 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<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes);
}
}

Binary file not shown.

@ -0,0 +1,454 @@
#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<Kv>(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<int>& out_maxIndexs,
std::vector<float>& out_maxVals, std::vector<bool> 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<float>(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<float>(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<CCharacter>& 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<float>(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<float>(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<CCharacter>& 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<float>(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<CCharacter>& 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<float>(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<float>(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<float>(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<float>(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<int>(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<std::string, std::string> 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<float>(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<std::string, std::string> 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<float>(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<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) {
cv::Mat feature = charFeatures(input, kPredictSize);
float maxVal = -2;
auto index = static_cast<int>(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<cv::Mat> inputs, std::vector<std::pair<std::string, std::string>>& outputs,
std::vector<bool> 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<int> maxIndexs;
std::vector<float> 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;
}
}

@ -0,0 +1,117 @@
#include "easypr/core/chars_recognise.h"
#include "easypr/core/character.hpp"
#include "easypr/util/util.h"
#include <ctime>
namespace easypr {
CCharsRecognise::CCharsRecognise() { m_charsSegment = new CCharsSegment(); }
CCharsRecognise::~CCharsRecognise() { SAFE_RELEASE(m_charsSegment); }
int CCharsRecognise::charsRecognise(Mat plate, std::string& plateLicense) {
std::vector<Mat> 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<Mat> matChars;
std::vector<Mat> 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_<double>(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<std::string, std::string> 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;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,466 @@
#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<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(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<uchar>(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<float>(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<float> 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<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
for (int x = 0; x < lowData.cols; x++) {
for (int y = 0; y < lowData.rows; y++) {
out.at<float>(j) += (float)lowData.at <unsigned char>(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<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
for (int x = 0; x < lowData.cols; x++) {
for (int y = 0; y < lowData.rows; y++) {
out.at<float>(j) += (float)lowData.at <unsigned char>(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<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = 0; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(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;
}
}

@ -0,0 +1,12 @@
#include "easypr/core/params.h"
namespace easypr {
CParams* CParams::instance_ = nullptr;
CParams* CParams::instance() {
if (!instance_) {
instance_ = new CParams;
}
return instance_;
}
}/*! \namespace easypr*/

@ -0,0 +1,77 @@
#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<CPlate> &resultVec, int type,
bool showDetectArea, int img_index) {
std::vector<CPlate> sobel_Plates;
sobel_Plates.reserve(16);
std::vector<CPlate> color_Plates;
color_Plates.reserve(16);
std::vector<CPlate> mser_Plates;
mser_Plates.reserve(16);
std::vector<CPlate> 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<CPlate> &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);
}
}

@ -0,0 +1,193 @@
#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 marginbelow 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<Mat> &inVec,
std::vector<Mat> &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<CPlate> &inVec,
std::vector<CPlate> &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_<double>(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<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) {
std::sort(inVec.begin(), inVec.end());
std::vector<CPlate>::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<CPlate>::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<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) {
std::vector<CPlate> 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_<double>(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<CPlate> 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<CPlate>::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;
}
}

@ -0,0 +1,999 @@
#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*140mmaspect 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<Mat> &out,
vector<vector<CPlate>>& out_plateVec, bool usePlateMser, vector<vector<RotatedRect>>& out_plateRRect,
int img_index, bool showDebug) {
vector<Mat> match_grey;
vector<CPlate> plateVec_blue;
plateVec_blue.reserve(16);
vector<RotatedRect> plateRRect_blue;
plateRRect_blue.reserve(16);
vector<CPlate> plateVec_yellow;
plateVec_yellow.reserve(16);
vector<RotatedRect> 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<RotatedRect> &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<vector<Point>> contours;
findContours(src_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::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<Rect_<float>> &outRects) {
Mat src_threshold;
sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth,
m_MorphSizeHeight);
vector<vector<Point>> contours;
findContours(src_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> 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_<float> safeBoundRect;
if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue;
outRects.push_back(safeBoundRect);
}
return 0;
}
int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
vector<RotatedRect> &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<vector<Point>> contours;
findContours(bound_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> 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<RotatedRect> &outRects) {
Mat bound_threshold;
sobelOper(bound, bound_threshold, 3, 10, 3);
utils::imwrite("resources/image/tmp/sobelSecSearch.jpg", bound_threshold);
vector<vector<Point>> contours;
findContours(bound_threshold,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> 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_<double>(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_<double>(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_<double>(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<RotatedRect> &inRects,
vector<CPlate> &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_<float> 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 addaffect 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_<float>(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<uchar>(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:"<<angle<<endl;
// cout<<"g:"<<g<<endl;
// cout << "slope_can_1:" << slope_can_1 << endl;
// cout << "slope_can_2:" << slope_can_2 << endl;
// cout << "slope_can_3:" << slope_can_3 << endl;
// if(g>=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<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_color_blue;
rects_color_blue.reserve(64);
vector<RotatedRect> rects_color_yellow;
rects_color_yellow.reserve(64);
vector<CPlate> plates_blue;
plates_blue.reserve(64);
vector<CPlate> 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<CPlate> &candPlates, int img_index) {
std::vector<Mat> channelImages;
std::vector<Color> 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<vector<RotatedRect>> plateRRectsVec;
vector<vector<CPlate>> platesVec;
vector<Mat> src_b_vec;
Mat channelImage = channelImages.at(i);
Mat image = scaleImage(channelImage, Size(scale_size, scale_size), scale_ratio);
// vector<RotatedRect> rects;
mserSearch(image, src_b_vec, platesVec, usePlateMser, plateRRectsVec, img_index, false);
for (size_t j = 0; j < flags.size(); j++) {
vector<CPlate>& plates = platesVec.at(j);
Mat& src_b = src_b_vec.at(j);
Color color = flags.at(j);
vector<RotatedRect> rects_mser;
rects_mser.reserve(64);
std::vector<CPlate> deskewPlate;
deskewPlate.reserve(64);
std::vector<CPlate> 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<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_sobel_all;
rects_sobel_all.reserve(256);
vector<CPlate> plates;
plates.reserve(32);
vector<Rect_<float>> bound_rects;
bound_rects.reserve(256);
sobelFrtSearch(src, bound_rects);
vector<Rect_<float>> 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_<float> 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_<float> 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_<float> safe_bound_rect(x, y, width, height);
Mat bound_mat = src(safe_bound_rect);
vector<RotatedRect> 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_<float> 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_<float> safe_bound_rect(x, y, width, height);
Mat bound_mat = src(safe_bound_rect);
vector<RotatedRect> 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<Mat> &resultVec, int index) {
vector<CPlate> 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<CPlate> &resultVec, int index) {
vector<CPlate> 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;
}
}

@ -0,0 +1,105 @@
#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<CPlate> &plateVecOut, int img_index) {
// resize to uniform sizes
float scale = 1.f;
Mat img = uniformResize(src, scale);
// 1. plate detect
std::vector<CPlate> 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<std::string> &licenseVec) {
vector<CPlate> plates;
int resultPR = plateRecognize(src, plates, 0);
for (auto plate : plates) {
licenseVec.push_back(plate.getPlateStr());
}
return resultPR;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -9,5 +9,4 @@ namespace easypr {
}
return instance_;
}
}/*! \namespace easypr*/
//这段代码是一个单例模式的实现。CParams类的instance_成员变量被初始化为nullptr然后instance()函数用来获取CParams类的唯一实例。如果instance_为空就会创建一个新的实例并返回否则直接返回现有的实例。这样可以确保在程序运行期间只有一个CParams类的实例存在。
}/*! \namespace easypr*/

@ -11,7 +11,7 @@ namespace easypr {
m_type = 0;
m_showDetect = false;
}
// 这段代码是CPlateDetect类的构造函数初始化了一些成员变量包括m_plateLocate指针、m_maxPlates、m_type和m_showDetect。
CPlateDetect::~CPlateDetect() { SAFE_RELEASE(m_plateLocate); }
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int type,
@ -24,11 +24,6 @@ namespace easypr {
mser_Plates.reserve(16);
std::vector<CPlate> all_result_Plates;
all_result_Plates.reserve(64);
// 这部分代码定义了CPlateDetect类的析构函数和plateDetect方法。
// 在plateDetect方法中使用了OpenMP并行处理来同时进行Sobel、颜色和MSER三种车牌定位方法。
// 最后使用NMS非极大值抑制来判断车牌并返回结果。
// plateDetect方法还有一个重载版本其中showDetectArea参数为false。
// LoadSVM方法用于加载SVM模型。
#pragma omp parallel sections
{
#pragma omp section
@ -37,16 +32,12 @@ namespace easypr {
m_plateLocate->plateSobelLocate(src, sobel_Plates, img_index);
}
}
// 这部分代码使用了OpenMP并行处理来进行Sobel车牌定位方法。
// 如果type为0或者包含PR_DETECT_SOBEL标志位则调用m_plateLocate的plateSobelLocate方法。
// plateSobelLocate方法用于进行Sobel算子定位车牌。
#pragma omp section
{
if (!type || type & PR_DETECT_COLOR) {
m_plateLocate->plateColorLocate(src, color_Plates, img_index);
}
}
//这段代码是使用OpenMP并行处理来调用m_plateLocate的plateColorLocate方法用于进行颜色定位车牌。根据type参数的值判断是否需要进行颜色定位
#pragma omp section
{
if (!type || type & PR_DETECT_CMSER) {
@ -73,11 +64,7 @@ namespace easypr {
showDectectResults(src, resultVec, m_maxPlates);
return 0;
}
// 这部分代码是CPlateDetect类的plateDetect方法的一部分。
// 首先通过OpenMP并行处理调用plateMserLocate方法来进行MSER车牌定位。
// 然后将定位结果分别设置为对应的类型SOBEL、COLOR、CMSER并存入all_result_Plates中。
// 最后使用NMS非极大值抑制来判断车牌并将结果存入resultVec中。
// 代码中的if (0)条件语句似乎没有实际作用,可能需要进一步确认其意图。
int CPlateDetect::plateDetect(Mat src, std::vector<CPlate> &resultVec, int img_index) {
int result = plateDetect(src, resultVec, m_type, false, img_index);
return result;
@ -87,6 +74,4 @@ namespace easypr {
PlateJudge::instance()->LoadModel(path);
}
}
// 这段代码中的plateDetect方法是CPlateDetect类的成员函数用于调用另一个重载版本的plateDetect方法并返回结果。
// LoadSVM方法用于加载SVM模型其中调用了PlateJudge类的LoadModel方法。
}

@ -6,47 +6,43 @@
#include "easypr/core/params.h"
namespace easypr { //这部分代码实现了单例模式确保PlateJudge类只有一个实例
//定义了一个静态成员变量instance_它是PlateJudge类的指针初始化为nullptr。这是单例模式的关键所有的PlateJudge实例都将共享这个变量
PlateJudge* PlateJudge::instance_ = nullptr;
PlateJudge* PlateJudge::instance_ = nullptr;
//确保PlateJudge类只有一个实例当需要使用PlateJudge类时只需要调用PlateJudge::instance()即可获取到这个唯一的实例
PlateJudge* PlateJudge::instance() { //定义了一个静态成员函数instance()它返回一个指向PlateJudge实例的指针
if (!instance_) { //检查instance_是否为nullptr
instance_ = new PlateJudge; //如果是那么就创建一个新的PlateJudge实例并将instance_设置为指向这个新创建的实例
PlateJudge* PlateJudge::instance() {
if (!instance_) {
instance_ = new PlateJudge;
}
return instance_; //返回instance_即指向PlateJudge实例的指针
return instance_;
}
PlateJudge::PlateJudge() { //PlateJudge决定了使用哪种特征提取方法
bool useLBP = false; //定义一个布尔变量useLBP并初始化为false
if (useLBP) { //如果useLBP为true即使用LBP特征提取方法
LOAD_SVM_MODEL(svm_, kLBPSvmPath); //加载LBP的SVM模型
extractFeature = getLBPFeatures; //设置特征提取函数为getLBPFeatures
PlateJudge::PlateJudge() { //PlateJudge决定了使用哪种特征提取方法
bool useLBP = false;
if (useLBP) {
LOAD_SVM_MODEL(svm_, kLBPSvmPath);
extractFeature = getLBPFeatures;
}
else { //如果useLBP为false即使用直方图特征提取方法
LOAD_SVM_MODEL(svm_, kHistSvmPath); //加载直方图的SVM模型
extractFeature = getHistomPlusColoFeatures; //设置特征提取函数为getHistomPlusColoFeatures
else {
LOAD_SVM_MODEL(svm_, kHistSvmPath);
extractFeature = getHistomPlusColoFeatures;
}
}
void PlateJudge::LoadModel(std::string path) { //LoadModel函数用于加载SVM模型函数接收一个字符串参数path这个参数是SVM模型文件的路径
if (path != std::string(kDefaultSvmPath)) { //检查输入的路径path是否与默认的SVM路径kDefaultSvmPath不同。如果不同那么就需要加载新的SVM模型
if (!svm_->empty()) //检查当前的SVM模型是否为空。如果不为空那么在加载新的SVM模型之前需要先清空当前的SVM模型
svm_->clear(); //清空当前的SVM模型
LOAD_SVM_MODEL(svm_, path); //加载新的SVM模型。LOAD_SVM_MODEL是一个宏它接收两个参数一个是SVM模型的指针另一个是SVM模型文件的路径
void PlateJudge::LoadModel(std::string path) { //LoadModel函数用于加载SVM模型
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) { //plateSetScore函数用于设置车牌的评分接收一个CPlate类型的引用作为参数
Mat features; //定义一个Mat类型的变量features用于存储特征
extractFeature(plate.getPlateMat(), features); //调用extractFeature方法提取车牌图像的特征结果存储在features中
float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT); //使用SVM模型对特征进行预测得到的结果存储在score中
int PlateJudge::plateSetScore(CPlate& plate) { //plateSetScore函数用于设置车牌的评分
Mat features;
extractFeature(plate.getPlateMat(), features);
float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT);
//std::cout << "score:" << score << std::endl;
//这是一个调试用的代码块如果条件为真此处为0所以不会执行则显示车牌图像等待用户按键然后销毁窗口
if (0) {
imshow("plate", plate.getPlateMat());
waitKey(0);
@ -55,15 +51,14 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
// score is the distance of marginbelow 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; //如果评分小于0.5返回0表示这是一个车牌否则返回-1表示这不是一个车牌
if (score < 0.5) return 0;
else return -1;
}
//定义了一个名为plateJudge的成员函数它属于PlateJudge类。该函数接收一个Mat类型的常量引用参数plateMat这个参数是需要进行车牌判断的图像
int PlateJudge::plateJudge(const Mat& plateMat) { //plateJudge函数用于判断输入的图像是否为车牌。
CPlate plate; //这行创建了一个CPlate类型的对象plate。CPlate是一个类它可能包含车牌的相关信息如车牌图像、车牌位置等
plate.setPlateMat(plateMat); //调用了CPlate类的setPlateMat成员函数将输入的图像plateMat设置为plate对象的车牌图像
return plateSetScore(plate); //调用了PlateJudge类的plateSetScore成员函数对plate对象进行评分然后返回评分结果
CPlate plate;
plate.setPlateMat(plateMat);
return plateSetScore(plate);
}
int PlateJudge::plateJudge(const std::vector<Mat> &inVec, //inVec是输入的图像向量resultVec是输出的结果向量。
@ -112,25 +107,18 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
return 0; //结束循环并返回0表示方法执行成功
}
// non-maximum suppression -->非极大值抑制
//函数接收三个参数一个CPlate对象的向量inVec输入的车牌向量一个CPlate对象的向量resultVec输出的车牌向量以及一个double类型的overlap重叠阈值
// non-maximum suppression
void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) { //NMS函数实现了非极大值抑制用于消除重叠的车牌。
std::sort(inVec.begin(), inVec.end()); //首先对输入的车牌向量进行排序
//然后遍历输入的车牌向量,对每一个车牌对象,获取其位置的边界矩形
std::sort(inVec.begin(), inVec.end());
std::vector<CPlate>::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<CPlate>::iterator itc = it + 1;
for (; itc != inVec.end();) {
CPlate plateComp = *itc;
Rect rectComp = plateComp.getPlatePos().boundingRect();
//计算两个边界矩形的交并比Intersection over UnionIoU如果IoU大于设定的重叠阈值那么就从输入的车牌向量中删除当前的车牌对象否则继续处理下一个车牌对象
float iou = computeIOU(rectSrc, rectComp);
if (iou > overlap) {
itc = inVec.erase(itc);
@ -140,23 +128,19 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
}
}
}
resultVec = inVec; //最后,将处理后的车牌向量赋值给输出的车牌向量
resultVec = inVec;
}
// judge plate using nms --> 使用非极大值抑制进行车牌识别
// 定义了一个名为plateJudgeUsingNMS的成员函数它属于PlateJudge类。该函数使用非极大值抑制进行车牌识别接收一个CPlate对象的向量inVec输入的车牌向量、一个CPlate对象的向量resultVec输出的车牌向量和一个整数maxPlates最大车牌数量作为参数
// judge plate using nms
int PlateJudge::plateJudgeUsingNMS(const std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, int maxPlates) { //plateJudgeUsingNMS函数使用非极大值抑制进行车牌识别。
std::vector<CPlate> plateVec; // 创建一个CPlate对象的向量用于存储识别出的车牌
int num = inVec.size(); // 获取输入向量的大小
bool useCascadeJudge = true; // 定义一个布尔变量,表示是否使用级联判断
std::vector<CPlate> plateVec;
int num = inVec.size();
bool useCascadeJudge = true;
// 遍历输入向量中的每一个元素
for (int j = 0; j < num; j++) {
CPlate plate = inVec[j]; // 获取当前的CPlate对象
Mat inMat = plate.getPlateMat(); // 获取当前CPlate对象的车牌图像
int result = plateSetScore(plate); // 对当前的CPlate对象进行评分
// 如果评分结果为0表示这是一个车牌
CPlate plate = inVec[j];
Mat inMat = plate.getPlateMat();
int result = plateSetScore(plate);
if (0 == result) {
if (0) {
imshow("inMat", inMat);
@ -164,22 +148,17 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
destroyWindow("inMat");
}
if (plate.getPlateLocateType() == CMSER) { // 如果plate的定位类型为CMSER
int w = inMat.cols; // 获取图像的宽度
int h = inMat.rows; // 获取图像的高度
// 对图像进行裁剪
if (plate.getPlateLocateType() == CMSER) {
int w = inMat.cols;
int h = inMat.rows;
Mat tmpmat = inMat(Rect_<double>(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); // 设置plate的车牌图像为调整后的图像
// 如果使用级联判断
Mat tmpDes = inMat.clone();
resize(tmpmat, tmpDes, Size(inMat.size()));
plate.setPlateMat(tmpDes);
if (useCascadeJudge) {
int resultCascade = plateSetScore(plate); // 对调整后的图像进行评分
// 如果plate的定位类型不是CMSER将plate的车牌图像设置为原图像
int resultCascade = plateSetScore(plate);
if (plate.getPlateLocateType() != CMSER)
plate.setPlateMat(inMat);
// 如果级联评分结果为0将plate添加到plateVec中
if (resultCascade == 0) {
if (0) {
imshow("tmpDes", tmpDes);
@ -189,24 +168,22 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
plateVec.push_back(plate);
}
}
else // 如果不使用级联判断直接将plate添加到plateVec中
else
plateVec.push_back(plate);
}
else // 如果plate的定位类型不是CMSER直接将plate添加到plateVec中
else
plateVec.push_back(plate);
}
}
std::vector<CPlate> reDupPlateVec; // 创建一个CPlate对象的向量用于存储非极大值抑制后的结果
double overlap = 0.5; // 定义重叠阈值
std::vector<CPlate> reDupPlateVec;
double overlap = 0.5;
// double overlap = CParams::instance()->getParam1f();
// use NMS to get the result plates
// 使用非极大值抑制处理plateVec结果存储在reDupPlateVec中
NMS(plateVec, reDupPlateVec, overlap);
// sort the plates due to their scores --> 根据评分对reDupPlateVec进行排序
// sort the plates due to their scores
std::sort(reDupPlateVec.begin(), reDupPlateVec.end());
// output the plate judge plates
// 遍历reDupPlateVec将结果添加到resultVec中直到达到最大车牌数量
std::vector<CPlate>::iterator it = reDupPlateVec.begin();
int count = 0;
for (; it != reDupPlateVec.end(); ++it) {
@ -220,6 +197,6 @@ namespace easypr { //这部分代码实现了单例模式确保PlateJudge类
if (count >= maxPlates)
break;
}
return 0; // 返回0表示函数执行成功
return 0;
}
}

@ -11,17 +11,14 @@ const float DEFAULT_ERROR = 0.9f; // 0.6
const float DEFAULT_ASPECT = 3.75f; // 3.75
CPlateLocate::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;
@ -29,7 +26,6 @@ CPlateLocate::CPlateLocate() {
}
void CPlateLocate::setLifemode(bool param) {
//若参数param为真设置成员变量为特定值为假则设为初始值
if (param) {
setGaussianBlurSize(5);
setMorphSizeWidth(10);
@ -50,8 +46,6 @@ void CPlateLocate::setLifemode(bool param) {
}
bool CPlateLocate::verifySizes(RotatedRect mr) {
//验证图像中给定的车牌区域大小是否满足预设的大小限制
//主要是在宽度、高度、面积满足返回true
float error = m_error;
// Spain car plate size: 52x11 aspect 4,7272
// China car plate size: 440mm*140mmaspect 3.142857
@ -70,14 +64,12 @@ bool CPlateLocate::verifySizes(RotatedRect mr) {
float rmax = aspect + aspect * error;
float area = mr.size.height * mr.size.width;
//r为宽高比
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
@ -86,32 +78,29 @@ bool CPlateLocate::verifySizes(RotatedRect mr) {
//! mser search method
int CPlateLocate::mserSearch(const Mat &src, vector<Mat> &out,
//在输入的图像src中搜索并找到车牌区域
// 并将找到的蓝色和黄色的车牌信息及其旋转矩形框信息和匹配后的灰度图像返回。
vector<vector<CPlate>>& out_plateVec, bool usePlateMser, vector<vector<RotatedRect>>& out_plateRRect,
int img_index, bool showDebug) {
vector<Mat> match_grey;
//存储匹配后的灰度图像。
vector<CPlate> plateVec_blue;
plateVec_blue.reserve(16);
vector<RotatedRect> plateRRect_blue;
plateRRect_blue.reserve(16);
//创建两个向量来存储蓝色的车牌信息和其旋转矩形框信息。
vector<CPlate> plateVec_yellow;
plateVec_yellow.reserve(16);
//创建两个向量来存储黄色的车牌信息和其旋转矩形框信息。
vector<RotatedRect> plateRRect_yellow;
plateRRect_yellow.reserve(16);
mserCharMatch(src, match_grey, plateVec_blue, plateVec_yellow, usePlateMser, plateRRect_blue, plateRRect_yellow, img_index, showDebug);
//调用MSER算法的函数输入源图像和各种参数输出匹配后的灰度图像以及两种颜色的车牌信息和其旋转矩形框信息。
out_plateVec.push_back(plateVec_blue);
out_plateVec.push_back(plateVec_yellow);
//将找到的蓝色和黄色的车牌信息添加到out_plateVec向量中。
out_plateRRect.push_back(plateRRect_blue);
out_plateRRect.push_back(plateRRect_yellow);
//将找到的蓝色和黄色的车牌的旋转矩形框信息添加到out_plateRRect向量中。
out = match_grey;
return 0;
@ -122,26 +111,21 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
vector<RotatedRect> &outRects) {
Mat match_grey;
//在输入的图像src中搜索特定颜色r的区域
// 并返回找到的区域RotatedRect的列表
// 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);
//将输入图像·src转换为灰度图像match_grey
SHOW_IMAGE(match_grey, 0);
Mat src_threshold;
threshold(match_grey, src_threshold, 0, 255,
CV_THRESH_OTSU + CV_THRESH_BINARY);
//使用阈值函数threshold对匹配后的图像进行处理
// 将像素值从0到255进行二值化处理
Mat element = getStructuringElement(
MORPH_RECT, Size(color_morph_width, color_morph_height));
morphologyEx(src_threshold, src_threshold, MORPH_CLOSE, element);
//morphologyEx进行形态学闭运算
// 主要用于去除噪声以及填充空洞
//if (m_debug) {
// utils::imwrite("resources/image/tmp/color.jpg", src_threshold);
//}
@ -155,20 +139,17 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
//indContours函数会找到所有外部轮廓即所有的区域都会被找到。
vector<vector<Point>>::iterator itc = contours.begin();
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
//对找到的每个轮廓执行minAreaRect函数得到该轮廓的最小外接矩形mr
if (!verifySizes(mr))
itc = contours.erase(itc);
else {
++itc;
outRects.push_back(mr);
}
//最后通过verifySizes函数检查得到的最小外接矩形是否满足预设的条件。
// 如果满足条件则将该矩形添加到outRects列表中如果不满足条件则删除该轮廓
}
return 0;
@ -177,15 +158,11 @@ int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,
int CPlateLocate::sobelFrtSearch(const Mat &src,
vector<Rect_<float>> &outRects) {
//主要功能是在输入的图像src中搜索边缘
// 并返回找到的区域RotatedRect的列表。
Mat src_threshold;
//对输入图像进行边缘检测后的结果。
sobelOper(src, src_threshold, m_GaussianBlurSize, m_MorphSizeWidth,
m_MorphSizeHeight);
//调用sobelOper函数对输入图像src进行边缘检测
// 并将结果存储在src_threshold中
//该函数还使用了高斯模糊和形态学操作来增强边缘检测的效果
vector<vector<Point>> contours;
findContours(src_threshold,
contours, // a vector of contours
@ -193,22 +170,19 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
vector<vector<Point>>::iterator itc = contours.begin();
//调用findContours函数找到边缘检测后的轮廓。
vector<RotatedRect> first_rects;
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
//计算其最小外接矩形minAreaRect函数
if (verifySizes(mr)) {
//检查这个矩形是否满足预设的条件verifySizes函数
// 如果满足条件将这个矩形加入到first_rects列表中
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;
//计算其面积和宽高比r。如果r小于1则重新计算宽高比。
}
++itc;
@ -216,14 +190,11 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
for (size_t i = 0; i < first_rects.size(); i++) {
RotatedRect roi_rect = first_rects[i];
Rect_<float> safeBoundRect;
if (!calcSafeRect(roi_rect, src, safeBoundRect)) continue;
//对于每一个在first_rects中的矩形
// 计算其在原图中的安全边界矩形
//如果计算失败,则跳过当前循环。
outRects.push_back(safeBoundRect);
//将每个安全边界矩形加入到outRects列表中。
}
return 0;
}
@ -231,23 +202,20 @@ int CPlateLocate::sobelFrtSearch(const Mat &src,
int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
vector<RotatedRect> &outRects) {
//功能在输入的图像bound中搜索边缘并返回找到的区域RotatedRect的列表outRects
Mat bound_threshold;
sobelOperT(bound, bound_threshold, 3, 6, 2);
//使用sobelOperT函数进行边缘检测其中3、6、2是sobel算子的大小。
Mat tempBoundThread = bound_threshold.clone();
clearLiuDingOnly(tempBoundThread);
int posLeft = 0, posRight = 0;
if (bFindLeftRightBound(tempBoundThread, posLeft, posRight)) {
//使用bFindLeftRightBound函数查找图像的左右边界
// find left and right bounds to repair
if (posRight != 0 && posLeft != 0 && posLeft < posRight) {
// 如果左边界不为0且右边界不为0且左边界小于右边界
// 则在图像中央位置的左右边界之间填充白色
int posY = int(bound_threshold.rows * 0.5);
for (int i = posLeft + (int) (bound_threshold.rows * 0.1);
i < posRight - 4; i++) {
@ -256,16 +224,13 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
}
utils::imwrite("resources/image/tmp/repaireimg1.jpg", bound_threshold);
//保存处理后的图像
// remove the left and right boundaries
for (int i = 0; i < bound_threshold.rows; i++)
{
//每个行上将左边界和右边界的像素设置为0。
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);
}
@ -274,28 +239,23 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
contours, // a vector of contours
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE); // all pixels of each contours
//调用findContours函数找到边缘检测后的轮廓。
vector<vector<Point>>::iterator itc = contours.begin();
vector<RotatedRect> second_rects;
while (itc != contours.end()) {
RotatedRect mr = minAreaRect(Mat(*itc));
second_rects.push_back(mr);
//对于每个轮廓计算其最小外接矩形minAreaRect函数
//并将结果添加到second_rects列表中。
++itc;
}
for (size_t i = 0; i < second_rects.size(); i++) {
RotatedRect roi = second_rects[i];
if (verifySizes(roi)) {
//对于second_rects中的每个矩形
// 如果满足条件verifySizes函数则计算其中心、大小和角度
Point2f refcenter = roi.center + refpoint;
Size2f size = roi.size;
float angle = roi.angle;
//创建一个新的RotatedRect对象然后将其添加到outRects列表中。
RotatedRect refroi(refcenter, size, angle);
outRects.push_back(refroi);
}
@ -307,8 +267,6 @@ int CPlateLocate::sobelSecSearchPart(Mat &bound, Point2f refpoint,
int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint,
vector<RotatedRect> &outRects) {
//功能在输入的图像bound中搜索边缘并返回找到的区域RotatedRect的列表outRects
//函数内部的函数功能和sobelSecSearch函数差不多
Mat bound_threshold;
@ -349,24 +307,19 @@ int CPlateLocate::sobelSecSearch(Mat &bound, Point2f refpoint,
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);
//对输入图像in进行高斯模糊处理并将结果存储在mat_blur中
Mat mat_gray;
if (mat_blur.channels() == 3)
cvtColor(mat_blur, mat_gray, CV_RGB2GRAY);
//如果是彩色图,则转换为灰度图像
//否则直接将mat_blur作为灰度图像。
else
mat_gray = mat_blur;
int scale = SOBEL_SCALE;
int delta = SOBEL_DELTA;
int ddepth = SOBEL_DDEPTH;
//三个变量分别表示Sobel算子中的尺度、偏差和深度。
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
@ -374,31 +327,30 @@ int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW,
Sobel(mat_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
//调用Sobel函数对灰度图像进行Sobel算子运算得到梯度图像grad_x。
Mat grad;
addWeighted(abs_grad_x, SOBEL_X_WEIGHT, 0, 0, 0, grad);
//将x方向的梯度图像和y方向的梯度图像加权叠加得到最终的梯度图像grad。
Mat mat_threshold;
double otsu_thresh_val =
threshold(grad, mat_threshold, 0, 255, CV_THRESH_OTSU + CV_THRESH_BINARY);
//对梯度图像grad进行阈值处理得到二值化图像mat_threshold
Mat element = getStructuringElement(MORPH_RECT, Size(morphW, morphH));
morphologyEx(mat_threshold, mat_threshold, MORPH_CLOSE, element);
//对二值化图像mat_threshold进行形态学闭运算以填充孔洞和连接断开的边缘
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_<double>(w * 0.15, h * 0.1, w * 0.7, h * 0.7));
Color plateType;
@ -408,42 +360,36 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
else {
plateType = color;
}
//根据输入的颜色参数,确定要保留的颜色类型。
//如果输入颜色为未知UNKNOWN则通过getPlateType函数确定颜色类型。
Mat img_threshold;
if (BLUE == plateType) {
img_threshold = input_grey.clone();
//若车牌是蓝色克隆灰度图像input_grey到img_threshold
Mat tmp = input_grey(Rect_<double>(w * 0.15, h * 0.15, w * 0.7, h * 0.7));
// 在input_grey中截取中心70%的部分进行阈值分割
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);
// 应用计算出的最佳阈值进行二值化即低于阈值的像素点变为0高于阈值的变为255
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
//保存二值化处理后的图像
} else if (YELLOW == plateType) {// 如果车牌类型是黄色
} else if (YELLOW == plateType) {
img_threshold = input_grey.clone();
Mat tmp = input_grey(Rect_<double>(w * 0.1, h * 0.1, w * 0.8, h * 0.8));
// 在input_grey中截取中心80%的部分进行阈值分割
int threadHoldV = ThresholdOtsu(tmp);
// 使用Otsu方法计算最佳阈值
threshold(input_grey, img_threshold, threadHoldV, 255,
CV_THRESH_BINARY_INV);
// 应用计算出的最佳阈值进行反向二值化即高于阈值的像素点变为0低于阈值的变为255
utils::imwrite("resources/image/tmp/inputgray2.jpg", img_threshold);
// threshold(input_grey, img_threshold, 10, 255, CV_THRESH_OTSU +
// CV_THRESH_BINARY_INV);
} else// 如果既不是蓝色也不是黄色的车牌
} else
threshold(input_grey, img_threshold, 10, 255,
CV_THRESH_OTSU + CV_THRESH_BINARY);
// 直接使用固定阈值10进行OTSU二值化处理
//img_threshold = input_grey.clone();
//spatial_ostu(img_threshold, 8, 2, plateType);
@ -453,13 +399,13 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
int top = 0;
int bottom = img_threshold.rows - 1;
clearLiuDing(img_threshold, top, bottom);
//清除图像中噪声
if (0) { //用于调试使用
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) {
@ -468,7 +414,6 @@ void deleteNotArea(Mat &inmat, Color color = UNKNOWN) {
destroyWindow("inmat");
}
}
// 如果找到了图像的左右边界,则将图像裁剪为这个边界内的部分。
}
@ -477,21 +422,20 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
vector<CPlate> &outPlates, bool useDeteleArea, Color color) {
Mat mat_debug;
src.copyTo(mat_debug);
//创建一个新的Mat对象并将源图像复制到这个新对象。
for (size_t i = 0; i < inRects.size(); i++) { //遍历输入的旋转句型
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;//计算旋转矩形的宽高比和角度。
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);
}
//如果宽高比小于1说明矩形是竖直的需要调整角度和宽高。
if (m_debug) { //调试模式,绘制旋转矩形的边界
if (m_debug) {
Point2f rect_points[4];
roi_rect.points(rect_points);
for (int j = 0; j < 4; j++)
@ -504,14 +448,14 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
// rotation < m_angel;
// m_angle=60
if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) { //旋转矩形的角度在合理范围时
if (roi_angle - m_angle < 0 && roi_angle + m_angle > 0) {
Rect_<float> safeBoundRect;
bool isFormRect = calcSafeRect(roi_rect, src, 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);
@ -524,16 +468,13 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
if ((roi_angle - 5 < 0 && roi_angle + 5 > 0) || 90.0 == roi_angle ||
-90.0 == roi_angle) {
deskew_mat = bound_mat;
//如果矩形角度接近0度或90度直接使用截取的子图像
} else {//倾斜调整
} else {
Mat rotated_mat;
Mat rotated_mat_b;
if (!rotation(bound_mat, rotated_mat, roi_rect_size, roi_ref_center, roi_angle))
continue;
//对bound_mat应用旋转旋转后存储在rotated_mat中
//若旋转失败,继续下一次循环
if (!rotation(bound_mat_b, rotated_mat_b, roi_rect_size, roi_ref_center, roi_angle))
continue;
@ -542,9 +483,7 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
// imshow("1roated_mat",rotated_mat);
// imshow("rotated_mat_b",rotated_mat_b);
if (isdeflection(rotated_mat_b, roi_angle, roi_slope)) {
// 检查rotated_mat_b是否需要倾斜调整
affine(rotated_mat, deskew_mat, roi_slope);
//应用仿射变换
} else
deskew_mat = rotated_mat;
}
@ -555,23 +494,18 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
// haitungaga addaffect 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) {
//检查经过校正的图像deskew_mat的长宽比是否在某个范围内
if (deskew_mat.cols >= WIDTH || deskew_mat.rows >= HEIGHT)
// 如果校正后的图像的宽cols或高rows超出预设的阈值WIDTH或HEIGHT
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_AREA);
// 使用INTER_AREA区域插值方法缩小图像保持plate_mat.size()大小
else
resize(deskew_mat, plate_mat, plate_mat.size(), 0, 0, INTER_CUBIC);
// 如果宽或高没有超出阈值则使用INTER_CUBIC三次插值方法放大图像保持plate_mat.size()大小
CPlate plate; //存储车牌信息
CPlate plate;
plate.setPlatePos(roi_rect);
plate.setPlateMat(plate_mat);
if (color != UNKNOWN) plate.setPlateColor(color); // 如果车牌颜色已知,则设置车牌颜色
outPlates.push_back(plate); //将包含车牌信息的对象输出
if (color != UNKNOWN) plate.setPlateColor(color);
outPlates.push_back(plate);
}
}
}
@ -581,7 +515,7 @@ int CPlateLocate::deskew(const Mat &src, const Mat &src_b,
bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
const Point2f center, const double angle) {
if (0) {//通过打印imshow信息调试
if (0) {
imshow("in", in);
waitKey(0);
destroyWindow("in");
@ -589,33 +523,33 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
Mat in_large;
in_large.create(int(in.rows * 1.5), int(in.cols * 1.5), in.type());
//创建一个1.5倍in图像的行数、1.5倍in图像的列数且同类的图像
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;
//计算原始图像 in 在放大图像 in_large 中的位置,确保其居中且不超出 in_large 的边界。
/*assert(width == in.cols);
assert(height == in.rows);*/
if (width != in.cols || height != in.rows) return false;
//如果计算得到的宽度和高度与原图 in 不匹配,函数返回 false。
Mat imageRoi = in_large(Rect_<float>(x, y, width, height));//合并原图并放大
Mat imageRoi = in_large(Rect_<float>(x, y, width, height));
addWeighted(imageRoi, 0, in, 1, 0, imageRoi);
//选择 in_large 中的一个区域 imageRoi将原图 in 覆盖到这个区域上。
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);
//使用 OpenCV 函数 getRotationMatrix2D 生成旋转矩阵 rot_mat。
/*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);
//使用 warpAffine 函数将旋转矩阵应用于放大后的图像 in_large结果存储在 mat_rotated 中
/*imshow("mat_rotated", mat_rotated);
waitKey(0);*/
@ -624,9 +558,8 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
new_center, img_crop);
out = img_crop;
//使用 getRectSubPix 函数根据指定的尺寸和中心点从旋转后的图像中裁剪出区域,
//结果存储在 img_crop 中,然后赋值给输出参数 out。
if (0) { //调试代码
if (0) {
imshow("out", out);
waitKey(0);
destroyWindow("out");
@ -641,9 +574,7 @@ bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size,
bool CPlateLocate::isdeflection(const Mat &in, const double angle,
double &slope) { /*imshow("in",in);
waitKey(0);*/
//用于检测输入图像 in 是否有偏转,并计算斜率 slope
if (0) { //用于调试
if (0) {
imshow("in", in);
waitKey(0);
destroyWindow("in");
@ -653,16 +584,16 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
int nCols = in.cols;
assert(in.channels() == 1);
//获取图像的行数 nRows 和列数 nCols并确认图像是单通道灰度图
int comp_index[3];
int len[3];
// 分别计算1/4、1/2、3/4高度处的行索引
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<uchar>(index);
@ -672,7 +603,6 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
while (0 == value && j < nCols) value = int(p[j++]);
len[i] = j;
}
// cout << "len[0]:" << len[0] << endl;
@ -688,15 +618,19 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
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:"<<angle<<endl;
// cout<<"g:"<<g<<endl;
// cout << "slope_can_1:" << slope_can_1 << endl;
// cout << "slope_can_2:" << slope_can_2 << endl;
// cout << "slope_can_3:" << slope_can_3 << endl;
// if(g>=0)
slope = abs(slope_can_1 - g) <= abs(slope_can_2 - g) ? slope_can_1
: slope_can_2;
// cout << "slope:" << slope << endl;
@ -706,23 +640,21 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
}
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) {
//如果斜率 slope > 0变换将图像向右倾斜。
// right, new position is xiff/2
@ -734,7 +666,7 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
dstTri[1] = Point2f(width - 1 - xiff / 2, 0);
dstTri[2] = Point2f(xiff / 2, height - 1);
} else {
//如果斜率 slope < 0变换将图像向左倾斜。
// left, new position is -xiff/2
plTri[0] = Point2f(0 + xiff, 0);
@ -747,18 +679,15 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
}
Mat warp_mat = getAffineTransform(plTri, dstTri);
//使用 OpenCV 的 getAffineTransform 函数,根据源点和目标点计算仿射变换矩阵 warp_mat。
Mat affine_mat;
affine_mat.create((int) height, (int) width, TYPE);
if (in.rows > HEIGHT || in.cols > WIDTH)//根据输入图像的大小,选择不同的插值方法:
if (in.rows > HEIGHT || in.cols > WIDTH)
//如果图像的大小超过预设的 HEIGHT 或 WIDTH使用 CV_INTER_AREA 插值,这个通常用于缩小。
warpAffine(in, affine_mat, warp_mat, affine_mat.size(),
CV_INTER_AREA);
else
//否则使用 CV_INTER_CUBIC 插值,这个插值方法在放大时可以提供平滑的边界。
warpAffine(in, affine_mat, warp_mat, affine_mat.size(), CV_INTER_CUBIC);
out = affine_mat;
@ -766,24 +695,22 @@ void CPlateLocate::affine(const Mat &in, Mat &out, const double slope) {
int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
int index) {
//初始化两个 RotatedRect 类型的向量 rects_color_blue 和 rects_color_yellow以及两个 CPlate 类型的向量 plates_blue 和 plates_yellow
vector<RotatedRect> rects_color_blue;
rects_color_blue.reserve(64);
vector<RotatedRect> rects_color_yellow;
rects_color_yellow.reserve(64);
//这些向量用于存储找到的蓝色和黄色车牌的位置和信息。
vector<CPlate> plates_blue;
plates_blue.reserve(64);
vector<CPlate> plates_yellow;
plates_yellow.reserve(64);
Mat src_clone = src.clone();
//使用 OpenMP 并行处理,分别对蓝色和黄色车牌进行搜索和倾斜矫正。
//这是通过调用 colorSearch 和 deskew 函数完成的。
Mat src_b_blue;
Mat src_b_yellow;
#pragma omp parallel sections
{
{
#pragma omp section
{
colorSearch(src, BLUE, src_b_blue, rects_color_blue);
@ -795,7 +722,6 @@ int CPlateLocate::plateColorLocate(Mat src, vector<CPlate> &candPlates,
deskew(src_clone, src_b_yellow, rects_color_yellow, plates_yellow, true, YELLOW);
}
}
//将找到的蓝色和黄色车牌信息添加到 candPlates 向量中。
candPlates.insert(candPlates.end(), plates_blue.begin(), plates_blue.end());
candPlates.insert(candPlates.end(), plates_yellow.begin(), plates_yellow.end());
@ -891,13 +817,10 @@ int CPlateLocate::plateMserLocate(Mat src, vector<CPlate> &candPlates, int img_i
int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
int morphH) {
//在输入图像src中使用 MSER最大稳定极值区域方法定位车牌
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);
@ -911,25 +834,24 @@ int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
int scale = SOBEL_SCALE;
int delta = SOBEL_DELTA;
int ddepth = SOBEL_DDEPTH;
//对灰度图像应用 Sobel 操作,得到 x 和 y 方向的梯度。
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);
//将 x 方向的梯度转换为绝对值,然后与 y 方向的梯度合并(假设 y 方向的梯度为0
Mat grad;
addWeighted(abs_grad_x, 1, 0, 0, 0, grad);
utils::imwrite("resources/image/tmp/graygrad.jpg", grad);
//使用 Otsu 的阈值法对得到的梯度图像进行二值化
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);
@ -943,7 +865,6 @@ int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,
int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
int index) {
vector<RotatedRect> rects_sobel_all;
//引用传递的 CPlate 类的矢量,用于存储最后识别为候选车牌的结果。
rects_sobel_all.reserve(256);
vector<CPlate> plates;
@ -953,13 +874,11 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
bound_rects.reserve(256);
sobelFrtSearch(src, bound_rects);
//使用 Sobel 算子处理输入的图像 src 并返回可能的边界矩形 bound_rects。
vector<Rect_<float>> bound_rects_part;
bound_rects_part.reserve(256);
// enlarge area
//通过扩大每个边界矩形的面积进行进一步处理,这通常是为了使候选区域更大,
//以包含整个车牌。代码通过改变矩形的 x 坐标,宽度,和 y 坐标,高度来实现此目的。
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) {
@ -982,8 +901,6 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
}
// second processing to split one
//使用 OpenMP 并行处理进行第二次搜索。pragma omp parallel for 使循环并行执行,
//以加快处理速度。在每次循环中,对于每个边界矩形:
#pragma omp parallel for
for (int i = 0; i < (int)bound_rects_part.size(); i++) {
Rect_<float> bound_rect = bound_rects_part[i];
@ -999,13 +916,12 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
Rect_<float> safe_bound_rect(x, y, width, height);
Mat bound_mat = src(safe_bound_rect);
//调用 sobelSecSearchPart 函数,它可能进一步处理提取的子图并返回可能的车牌候选区域 rects_sobel
vector<RotatedRect> rects_sobel;
rects_sobel.reserve(128);
sobelSecSearchPart(bound_mat, refpoint, rects_sobel);
#pragma omp critical
//确保当多个线程尝试将其搜索结果添加到 rects_sobel_all 集合时,不会发生冲突。
{
rects_sobel_all.insert(rects_sobel_all.end(), rects_sobel.begin(), rects_sobel.end());
}
@ -1038,15 +954,13 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
}
Mat src_b;
//调用 sobelOper 函数来执行 Sobel 操作。
sobelOper(src, src_b, 3, 10, 3);
//每个可能的矩形区域都发送给 deskew 函数,这个函数可能旨在纠正候选车牌的偏斜。
deskew(src, src_b, rects_sobel_all, plates);
//for (size_t i = 0; i < plates.size(); i++)
// candPlates.push_back(plates[i]);
//得到的车牌从 plates 转移至 candPlates
candPlates.insert(candPlates.end(), plates.begin(), plates.end());
return 0;
@ -1054,15 +968,12 @@ int CPlateLocate::plateSobelLocate(Mat src, vector<CPlate> &candPlates,
int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
//对输入图像src执行车牌定位并将定位到的车牌图像放入resultVec中
vector<CPlate> all_result_Plates;
//三个函数分别使用颜色定位、Sobel边缘检测和MSER算法来识别车牌
plateColorLocate(src, all_result_Plates, index);
plateSobelLocate(src, all_result_Plates, index);
plateMserLocate(src, all_result_Plates, index);
//循环通过all_result_Plates对于每个CPlate对象
//调用getPlateMat()获取车牌对应的图像并将其添加到resultVec向量中。
for (size_t i = 0; i < all_result_Plates.size(); i++) {
CPlate plate = all_result_Plates[i];
resultVec.push_back(plate.getPlateMat());
@ -1072,14 +983,12 @@ int CPlateLocate::plateLocate(Mat src, vector<Mat> &resultVec, int index) {
}
int CPlateLocate::plateLocate(Mat src, vector<CPlate> &resultVec, int index) {
//对输入图像src执行车牌定位将定位到的车牌对象(CPlate)放入resultVec中
vector<CPlate> all_result_Plates;
plateColorLocate(src, all_result_Plates, index);
plateSobelLocate(src, all_result_Plates, index);
plateMserLocate(src, all_result_Plates, index);
//循环通过all_result_Plates将每一个CPlate对象直接添加到resultVec向量中。
for (size_t i = 0; i < all_result_Plates.size(); i++) {
resultVec.push_back(all_result_Plates[i]);
}

@ -7,10 +7,7 @@ namespace easypr {
CPlateRecognize::CPlateRecognize() {
m_showResult = false;
}
// 这段代码是C++中的类定义定义了CPlateRecognize类的构造函数初始化了m_showResult成员变量为false。
// 该类包含plateRecognize方法用于车牌识别包括车牌检测和字符识别两部分。
// 还包含了一些Load方法用于加载模型文件。
// 代码中使用了OpenCV库中的Mat类和一些自定义的类和方法。
// main method, plate recognize, contain two parts
// 1. plate detect
@ -23,9 +20,6 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
// 1. plate detect
std::vector<CPlate> plateVec;
int resultPD = plateDetect(img, plateVec, img_index);
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于车牌识别。
// 首先对输入图像进行统一尺寸的调整然后进行车牌检测将结果存储在plateVec中。
// 返回值resultPD表示车牌检测的结果0表示成功非0表示失败。
if (resultPD == 0) {
size_t num = plateVec.size();
for (size_t j = 0; j < num; j++) {
@ -37,12 +31,7 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
item.setPlateScale(scale);
RotatedRect rect = item.getPlatePos();
item.setPlatePos(scaleBackRRect(rect, 1.f / scale));
// 如果车牌检测成功resultPD == 0则对每个检测到的车牌进行处理
// 1. 获取车牌图像并显示
// 2. 调整车牌位置和大小
// 3. 获取车牌颜色并识别字符
// 4. 将识别结果存储,并根据识别结果设置车牌字符串
// 5. 根据显示类型展示检测结果或识别结果
// get plate color
Color color = item.getPlateColor();
if (color == UNKNOWN) {
@ -53,10 +42,7 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
if (0) {
std::cout << "plateColor:" << plateColor << std::endl;
}
// 这段代码用于获取车牌颜色并进行处理。
// 首先获取车牌颜色如果颜色为UNKNOWN则调用getPlateType方法获取颜色并设置到item中。
// 然后将颜色转换为字符串形式并存储在plateColor中。
// 最后通过条件判断如果条件为0则输出plateColor到控制台。
// 2. chars recognize
std::string plateIdentify = "";
int resultCR = charsRecognise(item, plateIdentify);
@ -73,11 +59,6 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
if (0) std::cout << "resultCR:" << resultCR << std::endl;
}
}
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于字符识别。
// 首先定义了一个空字符串plateIdentify并调用charsRecognise方法进行字符识别结果存储在resultCR中。
// 如果resultCR为0则将plateColor和plateIdentify拼接成license字符串并设置到item中然后将item存入plateVecOut中。
// 否则只将plateColor设置到license字符串中然后将item存入plateVecOut中。
// 最后根据条件判断如果条件为0则输出resultCR到控制台。
if (getResultShow()) {
// param type: 0 detect, 1 recognize;
int showType = 1;
@ -89,9 +70,7 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<CPlate> &plateVe
}
return resultPD;
}
// 这段代码是C++中的类方法CPlateRecognize::plateRecognize的一部分用于根据getResultShow()的返回值决定是否展示检测结果或识别结果。
// 如果getResultShow()返回true则根据showType的值选择展示检测结果或识别结果。
// 最后返回resultPD表示车牌检测的结果。
void CPlateRecognize::LoadSVM(std::string path) {
PlateJudge::instance()->LoadModel(path);
}
@ -99,12 +78,7 @@ void CPlateRecognize::LoadSVM(std::string path) {
void CPlateRecognize::LoadANN(std::string path) {
CharsIdentify::instance()->LoadModel(path);
}
// 评注该代码
// 这部分代码定义了CPlateRecognize类的两个方法LoadSVM和LoadANN。
// LoadSVM方法用于加载SVM模型调用PlateJudge类的实例的LoadModel方法加载模型。
// LoadANN方法用于加载ANN模型调用CharsIdentify类的实例的LoadModel方法加载模型。
// 这些方法用于在车牌识别过程中加载相关的机器学习模型。
void CPlateRecognize::LoadChineseANN(std::string path) {
CharsIdentify::instance()->LoadChineseModel(path);
}
@ -116,9 +90,7 @@ void CPlateRecognize::LoadGrayChANN(std::string path) {
void CPlateRecognize::LoadChineseMapping(std::string path) {
CharsIdentify::instance()->LoadChineseMapping(path);
}
// 这部分代码定义了CPlateRecognize类的三个方法LoadChineseANN、LoadGrayChANN和LoadChineseMapping。
// 这些方法用于在字符识别过程中加载相关的中文字符识别模型和映射文件。
// 分别调用CharsIdentify类的实例的LoadChineseModel、LoadGrayChANN和LoadChineseMapping方法加载模型和映射文件。
// deprected
int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &licenseVec) {
vector<CPlate> plates;
@ -130,8 +102,4 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &li
return resultPR;
}
}
// 这段代码定义了CPlateRecognize类的一个新方法plateRecognize接受一个输入图像和一个字符串向量作为参数。
// 在方法内部首先创建了一个CPlate类型的向量plates并调用了原有的plateRecognize方法来进行车牌识别。
// 然后遍历plates向量中的每个车牌将其车牌字符串存入传入的licenseVec向量中。
// 最后返回了车牌识别的结果resultPR。
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

@ -1,257 +1,187 @@
#include <numeric>//包含了C++标准库中的<numeric>头文件,提供了数值计算的相关函数和模板。
#include <ctime>//包含了C++标准库中的<ctime>头文件,提供了关于时间和日期的相关函数和类型。
#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(); // 创建一个MLPMultilayer Perceptron多层感知器对象用于字符识别
type = 1; // 初始化type为1可能表示某种类型或模式
kv_ = std::shared_ptr<Kv>(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可能是为了增加一些额外的输入节点
#include <numeric>
#include <ctime>
#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<Kv>(new Kv);
kv_->load("resources/text/province_mapping");
extractFeature = getGrayPlusProject;
}
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<int>(0) = input_number; // 设置输入层节点数量
layers.at<int>(1) = hidden_number; // 设置隐藏层节点数量
layers.at<int>(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<int>(0) = input_number; // 设置输入层节点数量
layers.at<int>(1) = first_hidden_neurons; // 设置第一层隐藏层节点数量
layers.at<int>(2) = second_hidden_neurons; // 设置第二层隐藏层节点数量
layers.at<int>(3) = output_number; // 设置输出层节点数量
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<int>(0) = input_number;
layers.at<int>(1) = hidden_number;
layers.at<int>(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<int>(0) = input_number;
layers.at<int>(1) = first_hidden_neurons;
layers.at<int>(2) = second_hidden_neurons;
layers.at<int>(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);
}
// 设置神经网络层的大小
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<std::string, std::string> AnnChTrain::identifyGrayChinese(cv::Mat input) {
// 定义特征向量
Mat feature;
// 从输入图像中提取特征
extractFeature(input, feature);
// 初始化最大值为-2
float maxVal = -2;
// 初始化结果为0
int result = 0;
// 定义输出矩阵大小为1行kChineseNumber列数据类型为CV_32FC132位浮点型
cv::Mat output(1, kChineseNumber, CV_32FC1);
// 使用神经网络模型进行预测输入特征向量输出结果到output矩阵中
ann_->predict(feature, output);
// 遍历输出矩阵中的每一个值
for (int j = 0; j < kChineseNumber; j++) {
// 获取当前位置的值
float val = output.at<float>(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_samplesTODO: 这里需要解释这个变量名和变量的具体含义)
cv::Mat train_samples;
// 定义训练图像、验证图像的存储容器TODO: 这里需要解释这些变量名和变量的具体含义)
std::vector<cv::Mat> train_images, val_images;
std::vector<int> train_label, val_labels;
// 设置训练验证集分割比例为0.770%用于训练30%用于验证)
float percentage = 0.7f;
// 设置类别数为kChineseNumberTODO: 需要解释这个变量的具体含义)直接把代码改成评注形式
// 循环遍历每个字符类别
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<cv::Mat> 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);
}
std::pair<std::string, std::string> 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<float>(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);
}
// 生成合成图像
// 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::test() {
//TODO
}
void AnnChTrain::trainVal(size_t number_for_count) {
assert(chars_folder_);
cv::Mat train_samples;
std::vector<cv::Mat> train_images, val_images;
std::vector<int> 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<cv::Mat> 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);

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

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

@ -1,81 +1,81 @@
#include "easypr/util/kv.h" // 引入kv头文件
#include "easypr/util/util.h" // 引入util头文件
#include "easypr/util/kv.h"
#include "easypr/util/util.h"
namespace easypr { // 定义easypr命名空间
namespace easypr {
Kv::Kv() { } // Kv类构造函数
Kv::Kv() { }
void Kv::load(const std::string &file) { // 加载文件
this->clear(); // 清空数据
std::ifstream reader(file); // 创建文件读取流
assert(reader); // 断言文件读取流创建成功
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; // 定义字符串变量line
std::getline(reader, line); // 读取一行数据到line
if (line.empty()) continue; // 如果line为空则跳过本次循环
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(); // 清空临时变量
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); // 将当前字符添加到临时变量
tmp.push_back(ch);
}
if (i == len - 1) { // 如果当前字符是最后一个字符
value = tmp; // 将临时变量赋值给值
if (i == len - 1) {
value = tmp;
}
}
return std::make_pair(key, value); // 返回键值对
return std::make_pair(key, value);
};
auto kv = parse(line); // 解析行数据
this->add(kv.first, kv.second); // 添加键值对
auto kv = parse(line);
this->add(kv.first, kv.second);
}
reader.close(); // 关闭文件读取流
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 ""; // 返回空字符串
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); // 返回键对应的值
return data_.at(key);
}
void Kv::add(const std::string &key, const std::string &value) { // 添加键值对
if (data_.find(key) != data_.end()) { // 如果键已存在
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()); // 输出错误信息
value.c_str());
} else {
std::string v(value);
#ifdef OS_WINDOWS
v = utils::utf8_to_gbk(value.c_str());()); // 如果是Windows系统将值转换为gbk编码
v = utils::utf8_to_gbk(value.c_str());
#endif
data_[key] = v; // 添加键值对
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; // 返回
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); // 删除键值对
data_.erase(key);
}
void Kv::clear() { // 清空数据
data_.clear(); // 清空数据
void Kv::clear() {
data_.clear();
}
}

@ -3,429 +3,423 @@
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(const std::string& msg) : _msg(msg) {}
const char* ParseError::what() const throw() {
std::string msg;
msg.append("Command line parse error: ").append(_msg).push_back('.');
return msg.c_str();
}
ParseError::~ParseError() throw() {}// 是ParseError类的析构函数
ParseError::~ParseError() throw() {}
// class Generator
//定义了一个名为Generator的类该类用于生成和管理命令行选项解析器和子程序。
Generator::Generator() : parser_(nullptr) {//Generator类的构造函数
current_subroutine_ = Subroutine::get_default_name();//初始化parser_为nullptr设置当前子程序为默认子程序
add_subroutine(current_subroutine_.c_str());//添加这个子程序。
Generator::Generator() : parser_(nullptr) {
current_subroutine_ = Subroutine::get_default_name();
add_subroutine(current_subroutine_.c_str());
}
Generator::~Generator() {//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。
delete it->second;
it->second = nullptr;
}
}
}
Generator& Generator::make_usage(const char* first_line) {//是一个成员函数
get_subroutine()->set_first_line(first_line);//它设置当前子程序的第一行
return *this;//并返回this指针。
Generator& Generator::make_usage(const char* first_line) {
get_subroutine()->set_first_line(first_line);
return *this;
}
Parser* Generator::make_parser() {//是一个成员函数
Parser* Generator::make_parser() {
if (parser_) delete parser_;
parser_ = new Parser;//它创建一个新的Parser对象
parser_->set_usage_subroutines(&subroutines_);//设置其使用的子程序
return parser_;//并返回这个Parser对象。
parser_ = new Parser;
parser_->set_usage_subroutines(&subroutines_);
return parser_;
}
Generator& Generator::add_subroutine(const char* name) {//是成员函数
add_subroutine(name, "");//它们添加一个新的子程序。
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()) {//如果子程序已经存在,它们不会添加。
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});//添加一个新的子程序。
current_subroutine_ = name;
Subroutine* routine = new Subroutine(name, description);
subroutines_.insert({current_subroutine_, routine});
}
return *this;
}
std::map<std::string, std::string> Generator::get_subroutine_list() {//是一个成员函数,它返回一个包含所有子程序名称和描述的映射。
std::map<std::string, std::string> Generator::get_subroutine_list() {
std::map<std::string, std::string> kv;
for (auto pr : subroutines_) {//遍历所有的子程序
Subroutine* subroutine = pr.second;//遍历所有的子程序
if (subroutine->get_name() != Subroutine::get_default_name())//如果子程序的名称不是默认名称
kv[subroutine->get_name()] = subroutine->get_description();//将子程序的名称和描述添加到映射中。
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);//返回一个包含所有子程序名称和描述的映射
return std::move(kv);
}
bool Generator::add_usage_line(const char* option, const char* default_value,
const char* description) {// 是一个成员函数,它添加一个使用行到当前子程序。
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 row;
row.oshort(option_short);
row.olong(option_long);
row.value(default_value);
row.desc(description);
////设置其短选项、长选项、默认值和描述
get_subroutine()->add_usage_line(row);//将这个Row对象添加到当前子程序的使用行中。
get_subroutine()->add_usage_line(row);
return true;
}
return false;
}
std::ostream& operator<<(std::ostream& out, Generator& generator) {// 是一个输出运算符重载函数,它打印所有子程序的名称和描述。
for (auto pr : generator.subroutines_) {//遍历所有的子程序
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;//打印子程序的使用信息。
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) {}//ParseItem 类的构造函数
//它接受一个 std::string 类型的参数 value。在构造函数体中将传入的 value 直接赋值给类的成员变量 value_。
//这个构造函数用于创建一个 ParseItem 对象,并初始化其 value_ 成员变量。
ParseItem::ParseItem(const std::string& value) : value_(value) {}
// class Parser
ParseItem* Parser::get(const std::string& key) {//Parser 类的 get 方法,它接受一个 std::string 类型的参数 key。
if (pr_->find(key) != pr_->end()) {//如果 key 在 pr_ 中存在
return (*pr_)[key];//那么返回对应的 ParseItem 指针
ParseItem* Parser::get(const std::string& key) {
if (pr_->find(key) != pr_->end()) {
return (*pr_)[key];
}
return nullptr;//返回 nullptr
return nullptr;
}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}//
//Parser 类的构造函数,它使用初始化列表将 subroutines_ 和 pr_ 成员变量初始化为 nullptr。
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}
Parser::~Parser() { this->cleanup(); }
//这是 Parser 类的析构函数,它调用 cleanup 方法来清理资源。
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {//Parser 类的 parse 方法,它接受命令行参数的数量 argc 和参数值 argv
if (!this->init(argc, argv)) {//它调用 init 方法来初始化解析过程
return nullptr;//如果初始化失败,那么返回 nullptr
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {
if (!this->init(argc, argv)) {
return nullptr;
}
auto ibegin = args_.begin() + 1; // 忽略第一个命令名
auto ibegin = args_.begin() + 1; // ignore the first cmd name
auto iend = args_.end();
auto it = ibegin;//定义开始变量名
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++; // 忽略子程序名
it++; // ignore the subroutine name
} else {
subroutine_name_ = args_[1];
}
} else {
// 没有选项以及子程序名
// 例如,./exec
// 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);//声明变量
std::string block;
std::string previous(*ibegin);
for (; it != iend; ++it) {// 遍历所有的命令行参数
block.assign(*it);// 将当前参数赋值给 block
for (; it != iend; ++it) {
block.assign(*it);
switch (block.size()) {//// 根据 block 的大小进行不同的处理
case 1://// 如果 block 的大小为 1
if (block == "-") {//// 如果 block 是一个单独的 "-"
throw ParseError("single '-' is not allowed");//// 抛出异常,因为单独的 "-" 是不允许的
switch (block.size()) {
case 1:
if (block == "-") {
throw ParseError("single '-' is not allowed");
}
break;
case 2:// // 如果 block 的大小为 2
if (block[0] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
throw ParseError("option '--' is incomplete");//// 抛出异常,因为 "--" 是不完整的选项
} else if (block[1] == '=') {//// 如果 block 的第二个字符是 "="
throw ParseError("option '-=' is invalid");//// 抛出异常,因为 "-=" 是无效的选项
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] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
size_t pos_equal = block.find('=');//// 查找 "=" 在 block 中的位置
if (pos_equal == std::string::npos) {//// 如果没有找到 "="
// 长格式选项
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;//// 将选项添加到 pr_ 中,值为 nullptr
(*pr_)[block.substr(2)] = nullptr;
} else {
if (pos_equal > 3) {// 如果 "=" 的位置大于 3
if (pos_equal > 3) {
// e.g, ./exec --op[..=]value
std::string key(block.substr(2, pos_equal - 2));// 获取选项名
if (block.size() > 5)//// 如果 block 的大小大于 5
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));// 将选项和值添加到 pr_ 中
(*pr_)[key] = new ParseItem(block.substr(pos_equal + 1));
else
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
(*pr_)[key] = nullptr;
} else {
// 长格式选项但 = 是非法的
// a long format option but = is illegal
// e.g., ./exec --o=[...]
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
(*pr_)[block.substr(2)] = nullptr;
}
}
} else if (block[2] == '=') {// // 如果 block 的第三个字符是 "="
// 单个选项带有 =
} else if (block[2] == '=') {
// a single option with =
// e.g., ./exec -o=[...]
std::string key;
key.push_back(block[1]);// 获取选项名
if (block.size() > 3)// 如果 block 的大小大于 3
(*pr_)[key] = new ParseItem(block.substr(3));//// 将选项和值添加到 pr_ 中
key.push_back(block[1]);
if (block.size() > 3)
(*pr_)[key] = new ParseItem(block.substr(3));
else
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
(*pr_)[key] = nullptr;
} else {
// 组合选项
// a combination options
// e.g., ./exec -ab[...]
auto tbegin = block.begin() + 1; // 忽略第一个 '-'
auto tbegin = block.begin() + 1; // ignore the first '-'
auto tend = block.end();
auto t = tbegin;
for (; t != tend; ++t) { // 遍历 block 中的每个字符
for (; t != tend; ++t) {
std::string key;
key.push_back(*t);// // 获取选项名
(*pr_)[key] = nullptr; // 将选项添加到 pr_ 中,值为 nullptr
key.push_back(*t);
(*pr_)[key] = nullptr;
}
}
}
break;
} // switch
if (block[0] != '-' && previous != block // 如果 block 不是选项(不以 "-" 开头)并且不是第一个选项
if (block[0] != '-' && previous != block // not the first option
) {
if (previous[0] != '-') {//// 如果 previous 不是选项
// previous 不是一个选项,发生错误
if (previous[0] != '-') {
// previous is not an option, error occur
// e.g., ./exec abc def
throw ParseError("'" + block + "' is not allowed here");//抛出异常,因为在这里不允许非选项
throw ParseError("'" + block + "' is not allowed here");
}
std::string key;
if (previous[0] == '-' && previous[1] == '-') {//// 如果 previous 是一个长格式选项
if (previous[0] == '-' && previous[1] == '-') {
// previous is a long format option.
// e.g., ./exec --option value
key = previous.substr(2);//// 获取选项名
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));// // 获取选项名
key.push_back(*(previous.end() - 1));
}
if (pr_->find(key) != pr_->end()) {//// 如果选项在 pr_ 中存在
(*pr_)[key] = new ParseItem(block); // 将选项和值添加到 pr_ 中
if (pr_->find(key) != pr_->end()) {
(*pr_)[key] = new ParseItem(block);
}
}
previous = block;//// 更新 previous 为当前的 block
previous = block;
} // for
if (subroutines_) {
this->set_addition();// 如果存在子程序,调用 set_addition 方法处理额外的选项
this->set_addition();
}
return pr_;//返回解析结果 pr_
return pr_;
}
Parser::ParseResult* Parser::parse(const char* command_line) {//Parser 类的 parse 方法
int i = 0;//初始化计数器
std::string block;//用于存储单个命令行参数
std::vector<std::string> blocks;//声明用于存储所有命令行参数
char c;//声明用于存储当前字符
while ((c = command_line[i++]) != '\0') {// 遍历命令行字符串
if (c != ' ') {// 如果当前字符不是空格
block.push_back(c);//// 将当前字符添加到 block
Parser::ParseResult* Parser::parse(const char* command_line) {
int i = 0;
std::string block;
std::vector<std::string> blocks;
char c;
while ((c = command_line[i++]) != '\0') {
if (c != ' ') {
block.push_back(c);
} else {
if (!block.empty()) {// 如果 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
if (!block.empty()) {
blocks.push_back(block);
}
block.clear();//清空 block
block.clear();
}
}
if (!block.empty()) {// 如果最后一个 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
if (!block.empty()) {
blocks.push_back(block);
}
size_t size = blocks.size(); // argc
char** argv = new char*[size];// 创建一个新的 char* 数组
char** argv = new char*[size];
i = 0;
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {// 遍历 blocks
argv[i++] = const_cast<char*>(b.c_str());// 将每个 block 转换为 char* 并存储在 argv 中
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {
argv[i++] = const_cast<char*>(b.c_str());
});
auto pr =
this->parse(static_cast<const int>(size), const_cast<const char**>(argv)); // 调用 parse 方法解析命令行参数
this->parse(static_cast<const int>(size), const_cast<const char**>(argv));
delete[] argv;// 删除 argv
delete[] argv;
argv = nullptr;
return pr;// 返回解析结果
return pr;
}
bool Parser::has(const char* key) {//Parser 类的 has 方法,它接受一个 char 指针 key并检查 key 是否在 pr_ 中存在。
std::string skey(key);// 将 key 转换为 std::string
bool Parser::has(const char* key) {
std::string skey(key);
if (pr_ && !pr_->empty() && !skey.empty()) {//判断是否存在
if (pr_ && !pr_->empty() && !skey.empty()) {
if (skey[0] == '-') {
// 如果 skey 是一个组合选项,例如 "-xyz"
for (size_t i = 1; i < skey.size(); ++i) {// 遍历 skey 的每个字符
// 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()) { // 如果选项名在 pr_ 中不存在
tkey.push_back(skey[i]);
if (pr_->find(tkey) == pr_->end()) {
return false;
}
}
return true;
} else {
// 如果 skey 是一个单个选项,例如 "x"
return pr_->find(skey) != pr_->end();// 检查选项是否在 pr_ 中存在
// check single option, e.g., Parser::has("x")
return pr_->find(skey) != pr_->end();
}
}
return false;// 如果 pr_ 为空或 skey 为空,返回 false
return false;
}
//parser 类的 has_or 方法,它接受一个初始化列表 options并检查 options 中的任何一个 key 是否在 pr_ 中存在。
bool Parser::has_or(std::initializer_list<const char*> options) {
if (options.size() == 0) {// 如果 options 为空
if (options.size() == 0) {
return false;
}4
for (auto key : options) {// 遍历 options 中的每个选项
if (this->has(key)) return true;// 如果选项在 pr_ 中存在,返回 true
}
return false;// 如果 options 中的所有选项都不存在,返回 false
for (auto key : options) {
if (this->has(key)) return true;
}
return false;
}
bool Parser::has_and(std::initializer_list<const char*> options) { // Parser 类的 has_and 方法,接受一个初始化列表 options
if (options.size() == 0) {// 如果 options 为空
bool Parser::has_and(std::initializer_list<const char*> options) {
if (options.size() == 0) {
return false;
}
for (auto key : options) {// 遍历 options 中的每个选项
if (!this->has(key)) return false;// 如果选项在 pr_ 中不存在,返回 false
for (auto key : options) {
if (!this->has(key)) return false;
}
return true;// 如果 options 中的所有选项都存在,返回 true
return true;
}
bool Parser::init(const int argc, const char** argv) {
argc_ = argc;// 保存参数数量
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) {// 如果参数数量大于 0
this->cleanup(); // 清理之前的解析结果
if (argc > 0) {
this->cleanup();
args_.reserve(static_cast<size_t>(argc_));// 为 args_ 预留空间
args_.reserve(static_cast<size_t>(argc_));
for (int i = 0; i < argc_; ++i) {// 遍历所有的命令行参数
args_.push_back(argv[i]);// 将参数添加到 args_
for (int i = 0; i < argc_; ++i) {
args_.push_back(argv[i]);
}
pr_ = new Parser::ParseResult;// 创建新的解析结果
pr_ = new Parser::ParseResult;
return true;
}
return false;// 如果参数数量为 0返回 false
return false;
}
void Parser::cleanup() {// Parser 类的 cleanup 方法,用于清理解析结果
args_.clear();// 清空 args_
if (pr_) {// 如果 pr_ 不为空
void Parser::cleanup() {
args_.clear();
if (pr_) {
auto ibegin = pr_->begin();
auto iend = pr_->end();
auto it = ibegin;
for (; it != iend; ++it) {// 遍历 pr_ 中的每个元素
for (; it != iend; ++it) {
ParseItem* item = it->second;
if (item) delete item;// 删除元素
if (item) delete item;
}
delete pr_;// 删除 pr_
delete pr_;
pr_ = nullptr;
}
}
void Parser::set_addition() {// Parser 类的 set_addition 方法,用于处理额外的选项
if (subroutines_->find(subroutine_name_) != subroutines_->end()) {// 如果子程序名在 subroutines_ 中存在
for (const Row& row : *(subroutines_->at(subroutine_name_))) {// 遍历子程序中的每一行
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_; // 获取解析结果
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());// 检查长选项是否存在
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));// 如果长选项不为空,也将默认值赋给长选项
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;// 将长选项的值设为 nullptr
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));// 将默认值赋给长选项
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;// 将短选项的值设为 nullptr
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));// 如果短选项不为空,将默认值赋给短选项
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
@ -433,115 +427,114 @@ void Parser::set_addition() {// Parser 类的 set_addition 方法,用于处理
// class Row
Row::Row() : require_value(true) {} // Row 类的构造函数,初始化 require_value 为 true
Row::Row() : require_value(true) {}
// class Subroutine
Subroutine::Subroutine() : first_line_("") {}// Subroutine 类的默认构造函数,初始化 first_line_ 为空字符串
Subroutine::Subroutine() : first_line_("") {}
Subroutine::Subroutine(const char* name, const char* description)
: first_line_(""), description_(description), name_(name) { // Subroutine 类的构造函数,接受子程序名和描述作为参数
usages_.reserve(5);// 为 usages_ 预留空间
: first_line_(""), description_(description), name_(name) {
usages_.reserve(5);
}
void Subroutine::print_with_row(std::ostream& out) {// Subroutine 类的 print_with_row 方法,接受一个输出流作为参数
void Subroutine::print_with_row(std::ostream& out) {
// print the subroutine name and its description
// 打印子程序名和描述
if (strcmp(get_first_line(), "") != 0) {// 如果 first_line_ 不为空
if (strcmp(get_first_line(), "") != 0) {
// print the first line
out << get_first_line();
if (!usages_.empty()) {// 如果 usages_ 不为空
out << std::endl;// 打印换行符
if (!usages_.empty()) {
out << std::endl;
}
}
auto begin = usages_.begin(); // 获取 usages_ 的开始迭代器
auto end = usages_.end();// 获取 usages_ 的结束迭代器
auto begin = usages_.begin();
auto end = usages_.end();
std::vector<std::string> row_list;// 创建一个字符串向量用于存储行
row_list.reserve(usages_.size());// 为 row_list 预留空间
std::vector<std::string> 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) {// 遍历 usages_
std::stringstream ss;// 创建一个字符串流
ss << " ";// 向字符串流中添加两个空格
if (!row.oshort().empty()) {// 如果短选项不为空
ss << "-" << row.oshort() << " "; // 添加短选项
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.olong().empty()) {
if (!row.oshort().empty())
ss << "[ --" << row.olong() << " ] ";// 添加长选项
ss << "[ --" << row.olong() << " ] ";
else
ss << "--" << row.olong() << " "; // 添加长选项
ss << "--" << row.olong() << " ";
}
if (row.required()) {// 如果选项是必需的
ss << "arg "; // 添加 "arg "
if (!row.value().empty()) {// 如果选项值不为空
ss << "= " << row.value() << " ";// 添加选项值
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()));// 将字符串流的内容添加到 row_list
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();// 获取 usages_ 的大小
for (size_t i = 0; i < row_count; ++i) {// 遍历 usages_
std::string str_row(std::move(row_list[i]));// 获取当前行
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;// 打印当前行
out << str_row;
// print spaces
size_t spaces = 0;// 打印空格
size_t len = str_row.size();// 获取当前行的长度
if (max_len > len) spaces = max_len - len;// 计算需要打印的空格数量
size_t spaces = 0;
size_t len = str_row.size();
if (max_len > len) spaces = max_len - len;
while (spaces--) {// 打印空格
while (spaces--) {
out << " ";
}
// print description
out << usages_.at(i).desc() << std::endl;// 打印描述
out << usages_.at(i).desc() << std::endl;
}
}
void Subroutine::print_with_template(std::ostream& out) {// Subroutine 类的 print_with_template 方法,接受一个输出流作为参数
for (auto usage : usages_) {// 遍历 usages_
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)) { // 根据 order_ 中的值决定打印哪个字段
for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {
if (*t == '%') {
switch (*(order_.begin() + i)) {
case Row::kShort:
out << usage.oshort();// 打印短选项
out << usage.oshort();
break;
case Row::kLong:
out << usage.olong();// 打印长选项
out << usage.olong();
break;
case Row::kDefault:
out << usage.value();// 打印默认值
out << usage.value();
break;
case Row::kDescription:
out << usage.desc();// 打印描述
out << usage.desc();
break;
default:
break;
}
++i;
} else {
out << *t;// 如果当前字符不是 '%',直接打印
out << *t;
} // if %
} // for template_str_
out << std::endl;// 打印换行符
out << std::endl;
} // for usages_
}
std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) {// 重载 << 运算符,接受一个输出流和一个 Subroutine 对象作为参数
if (subroutine.template_str_.empty()) {// 如果模板字符串为空
subroutine.print_with_row(out);// 使用 print_with_row 方法打印
std::ostream& operator<<(std::ostream& out, Subroutine& subroutine) {
if (subroutine.template_str_.empty()) {
subroutine.print_with_row(out);
} else {
subroutine.print_with_template(out);// 使用 print_with_template 方法打印
subroutine.print_with_template(out);
}
return out;// 返回输出流
return out;
}
}

@ -1,127 +1,127 @@
#include "easypr/util/util.h"
#include <string>
// #ifdef OS_WINDOWS
#include <windows.h> // 包含windows.h头文件用于Windows平台的系统调用
#include <direct.h> // 包含direct.h头文件用于Windows平台的目录操作
#include <io.h> // 包含io.h头文件用于Windows平台的IO操作
#define PATH_DELIMITER '\\' // 定义路径分隔符为'\\'
#ifdef OS_WINDOWS
#include <windows.h>
#include <direct.h>
#include <io.h>
#define PATH_DELIMITER '\\'
#ifdef min
#undef min // 如果已经定义了min取消其定义
#undef min
#endif
#ifdef max
#undef max // 如果已经定义了max取消其定义
#undef max
#endif
#elif defined(OS_LINUX) || defined(OS_UNIX)
#include <cstring> // 包含cstring头文件用于字符串操作
#include <dirent.h> // 包含dirent.h头文件用于目录操作
#include <sys/stat.h> // 包含sys/stat.h头文件用于文件状态检查
#include <unistd.h> // 包含unistd.h头文件用于Unix标准的系统调用
#include <cstring>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#define PATH_DELIMITER '/' // 定义路径分隔符为'/'
#define PATH_DELIMITER '/'
#endif
#ifdef OS_UNIX
#include <sys/timeb.h> // 包含sys/timeb.h头文件用于时间操作
#include <sys/timeb.h>
#endif
#include <list> // 包含list头文件用于list数据结构
#include <opencv2/highgui/highgui.hpp> // 包含opencv的highgui模块用于图像IO操作
#include <list>
#include <opencv2/highgui/highgui.hpp>
namespace easypr { // 定义easypr命名空间
namespace easypr {
long Utils::getTimestamp() { // 定义获取时间戳的函数
long Utils::getTimestamp() {
#ifdef OS_WINDOWS
return static_cast<long>(cv::getTickCount()); // Windows平台下使用opencv的getTickCount函数获取时间戳
return static_cast<long>(cv::getTickCount());
#endif
#ifdef OS_LINUX
struct timespec ts; // 定义timespec结构体用于获取时间
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts); // 获取当前时间
clock_gettime(CLOCK_MONOTONIC, &ts);
return (ts.tv_sec * 1e3 + ts.tv_nsec / 1e6); // 返回毫秒级的时间戳
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; // 定义timeb结构体用于获取时间
ftime(&tb); // 获取当前时间
return long(tb.time * 1e3 + tb.millitm); // 返回毫秒级的时间戳
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('.'); // 获取路径中最后一个点的位置
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); // 如果没有找到正确的后缀点,直接返回文件名
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); // 如果路径有后缀,并且需要返回后缀,返回包含后缀的文件名
return path.substr(last_slash + 1);
}
// without postfix
return path.substr(last_slash + 1, last_dot - last_slash - 1); // 如果路径有后缀,但不需要返回后缀,返回不包含后缀的文件名
return path.substr(last_slash + 1, last_dot - last_slash - 1);
}
}
return ""; // 如果路径为空,返回空字符串
return "";
}
std::vector<std::string> Utils::splitString(const std::string &str,
const char delimiter) { // 定义字符串分割函数
std::vector<std::string> splited; // 定义存储分割结果的vector
std::string s(str); // 复制输入的字符串
size_t pos; // 定义分割位置
const char delimiter) {
std::vector<std::string> splited;
std::string s(str);
size_t pos;
while ((pos = s.find(delimiter)) != std::string::npos) { // 当找到分隔符时
std::string sec = s.substr(0, 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)); // 将子串添加到分割结果中
if (!sec.empty()) {
splited.push_back(s.substr(0, pos));
}
s = s.substr(pos + 1); // 更新待分割的字符串
s = s.substr(pos + 1);
}
splited.push_back(s); // 将最后一个子串添加到分割结果中
splited.push_back(s);
return splited; // 返回分割结果
return splited;
}
std::vector<std::string> Utils::getFiles(const std::string &folder,
const bool all /* = true */) { // 定义获取文件列表的函数
std::vector<std::string> files; // 定义存储文件列表的vector
std::list<std::string> subfolders; // 定义存储子文件夹的list
subfolders.push_back(folder); // 将输入的文件夹添加到子文件夹列表中
const bool all /* = true */) {
std::vector<std::string> files;
std::list<std::string> subfolders;
subfolders.push_back(folder);
#ifdef OS_WINDOWS
while (!subfolders.empty()) { // 当子文件夹列表不为空时
std::string current_folder(subfolders.back()); // 获取当前处理的文件夹
while (!subfolders.empty()) {
std::string current_folder(subfolders.back());
if (*(current_folder.end() - 1) != '/') {
current_folder.append("/*"); // 如果当前文件夹的路径不以'/'结尾,添加'/*'
current_folder.append("/*");
} else {
current_folder.append("*"); // 如果当前文件夹的路径以'/'结尾,添加'*'
current_folder.append("*");
}
subfolders.pop_back(); // 从子文件夹列表中移除当前处理的文件夹
subfolders.pop_back();
struct _finddata_t file_info; // 定义文件信息结构体
auto file_handler = _findfirst(current_folder.c_str(), &file_info); // 打开当前文件夹
struct _finddata_t file_info;
auto file_handler = _findfirst(current_folder.c_str(), &file_info);
while (file_handler != -1) { // 当文件夹打开成功时
while (file_handler != -1) {
if (all &&
(!strcmp(file_info.name, ".") || !strcmp(file_info.name, ".."))) {
if (_findnext(file_handler, &file_info) != 0) break;
@ -136,7 +136,7 @@ std::vector<std::string> Utils::getFiles(const std::string &folder,
folder.pop_back();
folder.append(file_info.name);
subfolders.push_back(folder.c_str()); // 如果是子文件夹,并且需要搜索子文件夹,将子文件夹添加到子文件夹列表中
subfolders.push_back(folder.c_str());
}
} else {
// it's a file
@ -145,24 +145,24 @@ std::vector<std::string> Utils::getFiles(const std::string &folder,
file_path.assign(current_folder.c_str()).pop_back();
file_path.append(file_info.name);
files.push_back(file_path); // 如果是文件,将文件路径添加到文件列表中
files.push_back(file_path);
}
if (_findnext(file_handler, &file_info) != 0) break;
} // while
_findclose(file_handler); // 关闭文件夹
_findclose(file_handler);
}
#elif defined(OS_LINUX) || defined(OS_UNIX)
while (!subfolders.empty()) { // 当子文件夹列表不为空时
std::string current_folder(subfolders.back()); // 获取当前处理的文件夹
while (!subfolders.empty()) {
std::string current_folder(subfolders.back());
if (*(current_folder.end() - 1) != '/') {
current_folder.push_back('/'); // 如果当前文件夹的路径不以'/'结尾,添加'/'
current_folder.push_back('/');
}
DIR* pdir = opendir(current_folder.c_str()); // 打开当前文件夹
DIR* pdir = opendir(current_folder.c_str());
subfolders.pop_back(); // 从子文件夹列表中移除当前处理的文件夹
subfolders.pop_back();
if (!pdir) {
continue;
@ -170,9 +170,9 @@ std::vector<std::string> Utils::getFiles(const std::string &folder,
dirent* dir = NULL;
while ((dir = readdir(pdir)) != NULL) { // 当读取到文件或文件夹时
while ((dir = readdir(pdir)) != NULL) {
// iterates the current folder, search file & sub folder
struct stat st; // 定义文件状态结构体
struct stat st;
if (all && (!strcmp(dir->d_name, ".") || !strcmp(dir->d_name, ".."))) {
// must ignore . & ..
@ -201,93 +201,93 @@ std::vector<std::string> Utils::getFiles(const std::string &folder,
std::string subfolder(current_folder);
subfolder.append(dir->d_name);
subfolders.push_back(subfolder.c_str()); // 如果是子文件夹,并且需要搜索子文件夹,将子文件夹添加到子文件夹列表中
subfolders.push_back(subfolder.c_str());
}
} else {
// it's a file
files.push_back(file_path); // 如果是文件,将文件路径添加到文件列表中
files.push_back(file_path);
}
} // while
closedir(pdir); // 关闭文件夹
closedir(pdir);
}
#endif
return files; // 返回文件列表
return files;
}
bool Utils::mkdir(const std::string folder) { // 定义创建文件夹的函数
std::string folder_builder; // 定义文件夹路径构造器
std::string sub; // 定义子路径
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) { // 遍历输入的文件夹路径
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); // 将子路径添加到文件夹路径构造器中
if (c == PATH_DELIMITER || it == folder.end() - 1) {
folder_builder.append(sub);
#ifdef OS_WINDOWS
if (0 != ::_access(folder_builder.c_str(), 0)) { // 如果文件夹不存在
if (0 != ::_access(folder_builder.c_str(), 0)) {
#else
if (0 != ::access(folder_builder.c_str(), 0)) { // 如果文件夹不存在
if (0 != ::access(folder_builder.c_str(), 0)) {
#endif
// this folder not exist
#ifdef OS_WINDOWS
if (0 != ::_mkdir(folder_builder.c_str())) { // 如果创建文件夹失败
if (0 != ::_mkdir(folder_builder.c_str())) {
#else
if (0 != ::mkdir(folder_builder.c_str(), S_IRWXU)) { // 如果创建文件夹失败
if (0 != ::mkdir(folder_builder.c_str(), S_IRWXU)) {
#endif
// create failed
return false; // 返回失败
return false;
}
}
sub.clear(); // 清空子路径
sub.clear();
}
}
return true; // 返回成功
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); // 写入图像
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) { // 定义UTF-8到GBK的转换函数
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); // 将UTF-8字符串转换为宽字符字符串
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); // 将宽字符字符串转换为GBK字符串
std::string strTemp(szGBK); // 将GBK字符串转换为std::string
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; // 删除宽字符数组
delete[] wszGBK;
if (szGBK)
delete[] szGBK; // 删除字符数组
return strTemp; // 返回转换结果
delete[] szGBK;
return strTemp;
}
#endif
std::size_t Utils::get_last_slash(const std::string &path) { // 定义获取路径中最后一个斜杠的位置的函数
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_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); // 如果路径中既有'\\'又有'/',取最后出现的一个
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; // 如果路径中只有'\\'或只有'/',取出现的那一个
(last_slash_1 == std::string::npos) ? last_slash_2 : last_slash_1;
}
#else
size_t last_slash = path.find_last_of('/'); // 获取路径中最后一个'/'的位置
size_t last_slash = path.find_last_of('/');
#endif
return last_slash; // 返回最后一个斜杠的位置
return last_slash;
}
} // namespace easypr

@ -0,0 +1,277 @@
#include <numeric>
#include <ctime>
#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<Kv>(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<int>(0) = input_number;
layers.at<int>(1) = hidden_number;
layers.at<int>(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<int>(0) = input_number;
layers.at<int>(1) = first_hidden_neurons;
layers.at<int>(2) = second_hidden_neurons;
layers.at<int>(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<std::string, std::string> 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<float>(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<cv::Mat> train_images, val_images;
std::vector<int> 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<cv::Mat> 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<float>(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<std::string, std::string> 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<std::string, std::string> 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<cv::ml::TrainData> AnnChTrain::tdata() {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> 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<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes);
}
}

@ -0,0 +1,356 @@
#include <numeric>
#include <ctime>
#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<Kv>(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<int>(0) = input_number;
layers.at<int>(1) = hidden_number;
layers.at<int>(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<int>(0) = input_number;
layers.at<int>(1) = first_hidden_neurons;
layers.at<int>(2) = second_hidden_neurons;
layers.at<int>(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<std::string, std::string> 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<float>(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<std::string, std::string> 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<float>(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<float> 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<std::pair<std::string, std::string>> 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<std::string, std::string> 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<size_t>(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<cv::ml::TrainData> AnnTrain::sdata(size_t number_for_count) {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> 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<cv::Mat> 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<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes);
}
cv::Ptr<cv::ml::TrainData> AnnTrain::tdata() {
assert(chars_folder_);
cv::Mat samples;
std::vector<int> 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<float>(i, labels[i]) = 1.f;
}
return cv::ml::TrainData::create(samples_, cv::ml::SampleTypes::ROW_SAMPLE,
train_classes);
}
}

@ -0,0 +1,88 @@
#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<unsigned char>(i, 0);
sum += img.at<unsigned char>(i, w-1);
}
for (int j = 0; j < w; ++j) {
sum += img.at<unsigned char>(0, j);
sum += img.at<unsigned char>(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_<double>(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;
}
}

Binary file not shown.

@ -0,0 +1,196 @@
#include "easypr/train/svm_train.h"
#include "easypr/util/util.h"
#include "easypr/config.h"
#ifdef OS_WINDOWS
#include <ctime>
#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<cv::ml::TrainData> SvmTrain::tdata() {
cv::Mat samples;
std::vector<int> 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

@ -0,0 +1,8 @@
#include "easypr/train/train.h"
namespace easypr {
ITrain::ITrain() {}
ITrain::~ITrain() {}
}

@ -0,0 +1,81 @@
#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();//用于清空所有的键值对。
}
}

@ -0,0 +1,547 @@
#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<std::string, std::string> Generator::get_subroutine_list() {//是一个成员函数,它返回一个包含所有子程序名称和描述的映射。
std::map<std::string, std::string> 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) {}//ParseItem 类的构造函数
//它接受一个 std::string 类型的参数 value。在构造函数体中将传入的 value 直接赋值给类的成员变量 value_。
//这个构造函数用于创建一个 ParseItem 对象,并初始化其 value_ 成员变量。
// class Parser
ParseItem* Parser::get(const std::string& key) {//Parser 类的 get 方法,它接受一个 std::string 类型的参数 key。
if (pr_->find(key) != pr_->end()) {//如果 key 在 pr_ 中存在
return (*pr_)[key];//那么返回对应的 ParseItem 指针
}
return nullptr;//返回 nullptr
}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}//
//Parser 类的构造函数,它使用初始化列表将 subroutines_ 和 pr_ 成员变量初始化为 nullptr。
Parser::~Parser() { this->cleanup(); }
//这是 Parser 类的析构函数,它调用 cleanup 方法来清理资源。
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {//Parser 类的 parse 方法,它接受命令行参数的数量 argc 和参数值 argv
if (!this->init(argc, argv)) {//它调用 init 方法来初始化解析过程
return nullptr;//如果初始化失败,那么返回 nullptr
}
auto ibegin = args_.begin() + 1; // 忽略第一个命令名
auto iend = args_.end();
auto it = ibegin;//定义开始变量名
if (argc >= 2 && args_[1][0] != '-') {
// 第二个块可能是一个子程序名
// e.g., ./exec pull --option
if (subroutines_ && (subroutines_->find(args_[1]) != subroutines_->end())) {
subroutine_name_ = args_[1];
it++; // 忽略子程序名
} else {
subroutine_name_ = args_[1];
}
} else {
// 没有选项以及子程序名
// 例如,./exec
subroutine_name_ = Subroutine::get_default_name();
}
std::string block;//声明变量
std::string previous(*ibegin);//声明变量
for (; it != iend; ++it) {// 遍历所有的命令行参数
block.assign(*it);// 将当前参数赋值给 block
switch (block.size()) {//// 根据 block 的大小进行不同的处理
case 1://// 如果 block 的大小为 1
if (block == "-") {//// 如果 block 是一个单独的 "-"
throw ParseError("single '-' is not allowed");//// 抛出异常,因为单独的 "-" 是不允许的
}
break;
case 2:// // 如果 block 的大小为 2
if (block[0] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
throw ParseError("option '--' is incomplete");//// 抛出异常,因为 "--" 是不完整的选项
} else if (block[1] == '=') {//// 如果 block 的第二个字符是 "="
throw ParseError("option '-=' is invalid");//// 抛出异常,因为 "-=" 是无效的选项
} else {
// 单个选项
// e.g., ./exec -s
(*pr_)[block.substr(1)] = nullptr;
}
}
break;
default: // >=3
if (block[0] == '-') {//// 如果 block 的第一个字符是 "-"
if (block[1] == '-') {//// 如果 block 的第二个字符也是 "-"
size_t pos_equal = block.find('=');//// 查找 "=" 在 block 中的位置
if (pos_equal == std::string::npos) {//// 如果没有找到 "="
// 长格式选项
// e.g., ./exec --option
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
} else {
if (pos_equal > 3) {// 如果 "=" 的位置大于 3
// e.g, ./exec --op[..=]value
std::string key(block.substr(2, pos_equal - 2));// 获取选项名
if (block.size() > 5)//// 如果 block 的大小大于 5
// e.g, ./exec --op=v
(*pr_)[key] = new ParseItem(block.substr(pos_equal + 1));// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// 长格式选项但 = 是非法的
// e.g., ./exec --o=[...]
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
}
}
} else if (block[2] == '=') {// // 如果 block 的第三个字符是 "="
// 单个选项带有 =
// e.g., ./exec -o=[...]
std::string key;
key.push_back(block[1]);// 获取选项名
if (block.size() > 3)// 如果 block 的大小大于 3
(*pr_)[key] = new ParseItem(block.substr(3));//// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// 组合选项
// e.g., ./exec -ab[...]
auto tbegin = block.begin() + 1; // 忽略第一个 '-'
auto tend = block.end();
auto t = tbegin;
for (; t != tend; ++t) { // 遍历 block 中的每个字符
std::string key;
key.push_back(*t);// // 获取选项名
(*pr_)[key] = nullptr; // 将选项添加到 pr_ 中,值为 nullptr
}
}
}
break;
} // switch
if (block[0] != '-' && previous != block // 如果 block 不是选项(不以 "-" 开头)并且不是第一个选项
) {
if (previous[0] != '-') {//// 如果 previous 不是选项
// previous 不是一个选项,发生错误
// e.g., ./exec abc def
throw ParseError("'" + block + "' is not allowed here");//抛出异常,因为在这里不允许非选项
}
std::string key;
if (previous[0] == '-' && previous[1] == '-') {//// 如果 previous 是一个长格式选项
// previous is a long format option.
// e.g., ./exec --option value
key = previous.substr(2);//// 获取选项名
} else {
// 它是前一个选项的值。
// e.g., ./exec -o [...]
// e.g., ./exec -opq [...]
key.push_back(*(previous.end() - 1));// // 获取选项名
}
if (pr_->find(key) != pr_->end()) {//// 如果选项在 pr_ 中存在
(*pr_)[key] = new ParseItem(block); // 将选项和值添加到 pr_ 中
}
}
previous = block;//// 更新 previous 为当前的 block
} // for
if (subroutines_) {
this->set_addition();// 如果存在子程序,调用 set_addition 方法处理额外的选项
}
return pr_;//返回解析结果 pr_
}
Parser::ParseResult* Parser::parse(const char* command_line) {//Parser 类的 parse 方法
int i = 0;//初始化计数器
std::string block;//用于存储单个命令行参数
std::vector<std::string> blocks;//声明用于存储所有命令行参数
char c;//声明用于存储当前字符
while ((c = command_line[i++]) != '\0') {// 遍历命令行字符串
if (c != ' ') {// 如果当前字符不是空格
block.push_back(c);//// 将当前字符添加到 block
} else {
if (!block.empty()) {// 如果 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
block.clear();//清空 block
}
}
if (!block.empty()) {// 如果最后一个 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
size_t size = blocks.size(); // argc
char** argv = new char*[size];// 创建一个新的 char* 数组
i = 0;
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {// 遍历 blocks
argv[i++] = const_cast<char*>(b.c_str());// 将每个 block 转换为 char* 并存储在 argv 中
});
auto pr =
this->parse(static_cast<const int>(size), const_cast<const char**>(argv)); // 调用 parse 方法解析命令行参数
delete[] argv;// 删除 argv
argv = nullptr;
return pr;// 返回解析结果
}
bool Parser::has(const char* key) {//Parser 类的 has 方法,它接受一个 char 指针 key并检查 key 是否在 pr_ 中存在。
std::string skey(key);// 将 key 转换为 std::string
if (pr_ && !pr_->empty() && !skey.empty()) {//判断是否存在
if (skey[0] == '-') {
// 如果 skey 是一个组合选项,例如 "-xyz"
for (size_t i = 1; i < skey.size(); ++i) {// 遍历 skey 的每个字符
std::string tkey;
tkey.push_back(skey[i]);// 获取选项名
if (pr_->find(tkey) == pr_->end()) { // 如果选项名在 pr_ 中不存在
return false;
}
}
return true;
} else {
// 如果 skey 是一个单个选项,例如 "x"
return pr_->find(skey) != pr_->end();// 检查选项是否在 pr_ 中存在
}
}
return false;// 如果 pr_ 为空或 skey 为空,返回 false
}
//parser 类的 has_or 方法,它接受一个初始化列表 options并检查 options 中的任何一个 key 是否在 pr_ 中存在。
bool Parser::has_or(std::initializer_list<const char*> options) {
if (options.size() == 0) {// 如果 options 为空
return false;
}4
for (auto key : options) {// 遍历 options 中的每个选项
if (this->has(key)) return true;// 如果选项在 pr_ 中存在,返回 true
}
return false;// 如果 options 中的所有选项都不存在,返回 false
}
bool Parser::has_and(std::initializer_list<const char*> options) { // Parser 类的 has_and 方法,接受一个初始化列表 options
if (options.size() == 0) {// 如果 options 为空
return false;
}
for (auto key : options) {// 遍历 options 中的每个选项
if (!this->has(key)) return false;// 如果选项在 pr_ 中不存在,返回 false
}
return true;// 如果 options 中的所有选项都存在,返回 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) {// 如果参数数量大于 0
this->cleanup(); // 清理之前的解析结果
args_.reserve(static_cast<size_t>(argc_));// 为 args_ 预留空间
for (int i = 0; i < argc_; ++i) {// 遍历所有的命令行参数
args_.push_back(argv[i]);// 将参数添加到 args_
}
pr_ = new Parser::ParseResult;// 创建新的解析结果
return true;
}
return false;// 如果参数数量为 0返回 false
}
void Parser::cleanup() {// Parser 类的 cleanup 方法,用于清理解析结果
args_.clear();// 清空 args_
if (pr_) {// 如果 pr_ 不为空
auto ibegin = pr_->begin();
auto iend = pr_->end();
auto it = ibegin;
for (; it != iend; ++it) {// 遍历 pr_ 中的每个元素
ParseItem* item = it->second;
if (item) delete item;// 删除元素
}
delete pr_;// 删除 pr_
pr_ = nullptr;
}
}
void Parser::set_addition() {// Parser 类的 set_addition 方法,用于处理额外的选项
if (subroutines_->find(subroutine_name_) != subroutines_->end()) {// 如果子程序名在 subroutines_ 中存在
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;// 将长选项的值设为 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;// 将短选项的值设为 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) {} // Row 类的构造函数,初始化 require_value 为 true
// class Subroutine
Subroutine::Subroutine() : first_line_("") {}// Subroutine 类的默认构造函数,初始化 first_line_ 为空字符串
Subroutine::Subroutine(const char* name, const char* description)
: first_line_(""), description_(description), name_(name) { // Subroutine 类的构造函数,接受子程序名和描述作为参数
usages_.reserve(5);// 为 usages_ 预留空间
}
void Subroutine::print_with_row(std::ostream& out) {// Subroutine 类的 print_with_row 方法,接受一个输出流作为参数
// print the subroutine name and its description
// 打印子程序名和描述
if (strcmp(get_first_line(), "") != 0) {// 如果 first_line_ 不为空
// print the first line
out << get_first_line();
if (!usages_.empty()) {// 如果 usages_ 不为空
out << std::endl;// 打印换行符
}
}
auto begin = usages_.begin(); // 获取 usages_ 的开始迭代器
auto end = usages_.end();// 获取 usages_ 的结束迭代器
std::vector<std::string> row_list;// 创建一个字符串向量用于存储行
row_list.reserve(usages_.size());// 为 row_list 预留空间
// 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) {// 遍历 usages_
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 "; // 添加 "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()));// 将字符串流的内容添加到 row_list
});
// show all rows and align description field
size_t row_count = usages_.size();// 获取 usages_ 的大小
for (size_t i = 0; i < row_count; ++i) {// 遍历 usages_
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) {// Subroutine 类的 print_with_template 方法,接受一个输出流作为参数
for (auto usage : usages_) {// 遍历 usages_
size_t i = 0;
for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {// 遍历模板字符串
if (*t == '%') {// 如果当前字符是 '%'
switch (*(order_.begin() + i)) { // 根据 order_ 中的值决定打印哪个字段
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) {// 重载 << 运算符,接受一个输出流和一个 Subroutine 对象作为参数
if (subroutine.template_str_.empty()) {// 如果模板字符串为空
subroutine.print_with_row(out);// 使用 print_with_row 方法打印
} else {
subroutine.print_with_template(out);// 使用 print_with_template 方法打印
}
return out;// 返回输出流
}
}

@ -0,0 +1,293 @@
#include "easypr/util/util.h"
#include <string>
// #ifdef OS_WINDOWS
#include <windows.h> // 包含windows.h头文件用于Windows平台的系统调用
#include <direct.h> // 包含direct.h头文件用于Windows平台的目录操作
#include <io.h> // 包含io.h头文件用于Windows平台的IO操作
#define PATH_DELIMITER '\\' // 定义路径分隔符为'\\'
#ifdef min
#undef min // 如果已经定义了min取消其定义
#endif
#ifdef max
#undef max // 如果已经定义了max取消其定义
#endif
#elif defined(OS_LINUX) || defined(OS_UNIX)
#include <cstring> // 包含cstring头文件用于字符串操作
#include <dirent.h> // 包含dirent.h头文件用于目录操作
#include <sys/stat.h> // 包含sys/stat.h头文件用于文件状态检查
#include <unistd.h> // 包含unistd.h头文件用于Unix标准的系统调用
#define PATH_DELIMITER '/' // 定义路径分隔符为'/'
#endif
#ifdef OS_UNIX
#include <sys/timeb.h> // 包含sys/timeb.h头文件用于时间操作
#endif
#include <list> // 包含list头文件用于list数据结构
#include <opencv2/highgui/highgui.hpp> // 包含opencv的highgui模块用于图像IO操作
namespace easypr { // 定义easypr命名空间
long Utils::getTimestamp() { // 定义获取时间戳的函数
#ifdef OS_WINDOWS
return static_cast<long>(cv::getTickCount()); // Windows平台下使用opencv的getTickCount函数获取时间戳
#endif
#ifdef OS_LINUX
struct timespec ts; // 定义timespec结构体用于获取时间
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; // 定义timeb结构体用于获取时间
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<std::string> Utils::splitString(const std::string &str,
const char delimiter) { // 定义字符串分割函数
std::vector<std::string> splited; // 定义存储分割结果的vector
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<std::string> Utils::getFiles(const std::string &folder,
const bool all /* = true */) { // 定义获取文件列表的函数
std::vector<std::string> files; // 定义存储文件列表的vector
std::list<std::string> subfolders; // 定义存储子文件夹的list
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) { // 定义UTF-8到GBK的转换函数
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); // 将UTF-8字符串转换为宽字符字符串
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); // 将宽字符字符串转换为GBK字符串
std::string strTemp(szGBK); // 将GBK字符串转换为std::string
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
Loading…
Cancel
Save