Compare commits

..

9 Commits

Binary file not shown.

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

@ -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,6 +24,11 @@ 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
@ -32,12 +37,16 @@ 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) {
@ -64,7 +73,11 @@ 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;
@ -74,4 +87,6 @@ namespace easypr {
PlateJudge::instance()->LoadModel(path);
}
}
}
// 这段代码中的plateDetect方法是CPlateDetect类的成员函数用于调用另一个重载版本的plateDetect方法并返回结果。
// LoadSVM方法用于加载SVM模型其中调用了PlateJudge类的LoadModel方法。

@ -641,6 +641,8 @@ 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) { //用于调试
imshow("in", in);
waitKey(0);
@ -654,13 +656,13 @@ bool CPlateLocate::isdeflection(const Mat &in, const double angle,
//获取图像的行数 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);
@ -686,19 +688,15 @@ 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;
@ -708,21 +706,23 @@ 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 +734,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,15 +747,18 @@ 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;
@ -763,22 +766,24 @@ 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);
@ -790,6 +795,7 @@ 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());
@ -885,10 +891,13 @@ 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);
@ -902,24 +911,25 @@ 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);
@ -933,6 +943,7 @@ 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;
@ -942,11 +953,13 @@ 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) {
@ -969,6 +982,8 @@ 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];
@ -984,12 +999,13 @@ 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());
}
@ -1022,13 +1038,15 @@ 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;
@ -1036,12 +1054,15 @@ 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());
@ -1051,12 +1072,14 @@ 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,7 +7,10 @@ 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
@ -20,6 +23,9 @@ 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++) {
@ -31,7 +37,12 @@ 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) {
@ -42,7 +53,10 @@ 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);
@ -59,6 +73,11 @@ 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;
@ -70,7 +89,9 @@ 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);
}
@ -78,7 +99,12 @@ 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);
}
@ -90,7 +116,9 @@ 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;
@ -102,4 +130,8 @@ int CPlateRecognize::plateRecognize(const Mat& src, std::vector<std::string> &li
return resultPR;
}
}
}
// 这段代码定义了CPlateRecognize类的一个新方法plateRecognize接受一个输入图像和一个字符串向量作为参数。
// 在方法内部首先创建了一个CPlate类型的向量plates并调用了原有的plateRecognize方法来进行车牌识别。
// 然后遍历plates向量中的每个车牌将其车牌字符串存入传入的licenseVec向量中。
// 最后返回了车牌识别的结果resultPR。

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

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

@ -1,81 +1,81 @@
#include "easypr/util/kv.h"
#include "easypr/util/util.h"
#include "easypr/util/kv.h" // 引入kv头文件
#include "easypr/util/util.h" // 引入util头文件
namespace easypr {
namespace easypr { // 定义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;
std::getline(reader, line);
if (line.empty()) continue;
if (reader.is_open()) { // 如果文件打开成功
while (!reader.eof()) { // 当未到达文件末尾时
std::string line; // 定义字符串变量line
std::getline(reader, line); // 读取一行数据到line
if (line.empty()) continue; // 如果line为空则跳过本次循环
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());
v = utils::utf8_to_gbk(value.c_str());()); // 如果是Windows系统将值转换为gbk编码
#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,423 +3,429 @@
namespace program_options {
// class ParseError
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类是一个用于处理命令行解析错误的异常类。
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::~ParseError() throw() {}// 是ParseError类的析构函数
// class Generator
Generator::Generator() : parser_(nullptr) {
current_subroutine_ = Subroutine::get_default_name();
add_subroutine(current_subroutine_.c_str());
//定义了一个名为Generator的类该类用于生成和管理命令行选项解析器和子程序。
Generator::Generator() : parser_(nullptr) {//Generator类的构造函数
current_subroutine_ = Subroutine::get_default_name();//初始化parser_为nullptr设置当前子程序为默认子程序
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;
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;
Generator& Generator::make_usage(const char* first_line) {//是一个成员函数
get_subroutine()->set_first_line(first_line);//它设置当前子程序的第一行
return *this;//并返回this指针。
}
Parser* Generator::make_parser() {
Parser* Generator::make_parser() {//是一个成员函数
if (parser_) delete parser_;
parser_ = new Parser;
parser_->set_usage_subroutines(&subroutines_);
return parser_;
parser_ = new Parser;//它创建一个新的Parser对象
parser_->set_usage_subroutines(&subroutines_);//设置其使用的子程序
return parser_;//并返回这个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);
////设置其短选项、长选项、默认值和描述
get_subroutine()->add_usage_line(row);//将这个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->get_description();
if (!subroutine->get_usage().empty()) {
out << std::endl;
}
out << *subroutine;
out << *subroutine;//打印子程序的使用信息。
}
return out;
}
// class ParseItem
ParseItem::ParseItem(const std::string& value) : value_(value) {}
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) {
if (pr_->find(key) != pr_->end()) {
return (*pr_)[key];
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;
return nullptr;//返回 nullptr
}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}
Parser::Parser() : subroutines_(nullptr), pr_(nullptr) {}//
//Parser 类的构造函数,它使用初始化列表将 subroutines_ 和 pr_ 成员变量初始化为 nullptr。
Parser::~Parser() { this->cleanup(); }
Parser::ParseResult* Parser::parse(const int argc, const char** argv) {
if (!this->init(argc, argv)) {
return nullptr;
//这是 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; // ignore the first cmd name
auto ibegin = args_.begin() + 1; // 忽略第一个命令名
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++; // ignore the subroutine name
it++; // 忽略子程序名
} else {
subroutine_name_ = args_[1];
}
} else {
// there is no options as well as subroutine name
// e.g., ./exec
// 没有选项以及子程序名
// 例如,./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);
for (; it != iend; ++it) {// 遍历所有的命令行参数
block.assign(*it);// 将当前参数赋值给 block
switch (block.size()) {
case 1:
if (block == "-") {
throw ParseError("single '-' is not allowed");
switch (block.size()) {//// 根据 block 的大小进行不同的处理
case 1://// 如果 block 的大小为 1
if (block == "-") {//// 如果 block 是一个单独的 "-"
throw ParseError("single '-' is not allowed");//// 抛出异常,因为单独的 "-" 是不允许的
}
break;
case 2:
if (block[0] == '-') {
if (block[1] == '-') {
throw ParseError("option '--' is incomplete");
} else if (block[1] == '=') {
throw ParseError("option '-=' is invalid");
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 {
// single option
// 单个选项
// e.g., ./exec -s
(*pr_)[block.substr(1)] = nullptr;
}
}
break;
default: // >=3
if (block[0] == '-') {
if (block[1] == '-') {
size_t pos_equal = block.find('=');
if (pos_equal == std::string::npos) {
// a long format option
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_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
} else {
if (pos_equal > 3) {
if (pos_equal > 3) {// 如果 "=" 的位置大于 3
// e.g, ./exec --op[..=]value
std::string key(block.substr(2, pos_equal - 2));
if (block.size() > 5)
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_)[key] = new ParseItem(block.substr(pos_equal + 1));// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// a long format option but = is illegal
// 长格式选项但 = 是非法的
// e.g., ./exec --o=[...]
(*pr_)[block.substr(2)] = nullptr;
(*pr_)[block.substr(2)] = nullptr;//// 将选项添加到 pr_ 中,值为 nullptr
}
}
} else if (block[2] == '=') {
// a single option with =
} else if (block[2] == '=') {// // 如果 block 的第三个字符是 "="
// 单个选项带有 =
// e.g., ./exec -o=[...]
std::string key;
key.push_back(block[1]);
if (block.size() > 3)
(*pr_)[key] = new ParseItem(block.substr(3));
key.push_back(block[1]);// 获取选项名
if (block.size() > 3)// 如果 block 的大小大于 3
(*pr_)[key] = new ParseItem(block.substr(3));//// 将选项和值添加到 pr_ 中
else
(*pr_)[key] = nullptr;
(*pr_)[key] = nullptr;// 将选项添加到 pr_ 中,值为 nullptr
} else {
// a combination options
// 组合选项
// e.g., ./exec -ab[...]
auto tbegin = block.begin() + 1; // ignore the first '-'
auto tbegin = block.begin() + 1; // 忽略第一个 '-'
auto tend = block.end();
auto t = tbegin;
for (; t != tend; ++t) {
for (; t != tend; ++t) { // 遍历 block 中的每个字符
std::string key;
key.push_back(*t);
(*pr_)[key] = nullptr;
key.push_back(*t);// // 获取选项名
(*pr_)[key] = nullptr; // 将选项添加到 pr_ 中,值为 nullptr
}
}
}
break;
} // switch
if (block[0] != '-' && previous != block // not the first option
if (block[0] != '-' && previous != block // 如果 block 不是选项(不以 "-" 开头)并且不是第一个选项
) {
if (previous[0] != '-') {
// previous is not an option, error occur
if (previous[0] != '-') {//// 如果 previous 不是选项
// previous 不是一个选项,发生错误
// 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] == '-') {
if (previous[0] == '-' && previous[1] == '-') {//// 如果 previous 是一个长格式选项
// 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_)[key] = new ParseItem(block);
if (pr_->find(key) != pr_->end()) {//// 如果选项在 pr_ 中存在
(*pr_)[key] = new ParseItem(block); // 将选项和值添加到 pr_ 中
}
}
previous = block;
previous = block;//// 更新 previous 为当前的 block
} // for
if (subroutines_) {
this->set_addition();
this->set_addition();// 如果存在子程序,调用 set_addition 方法处理额外的选项
}
return pr_;
return pr_;//返回解析结果 pr_
}
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);
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()) {
blocks.push_back(block);
if (!block.empty()) {// 如果 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
block.clear();
block.clear();//清空 block
}
}
if (!block.empty()) {
blocks.push_back(block);
if (!block.empty()) {// 如果最后一个 block 不为空
blocks.push_back(block);// 将 block 添加到 blocks
}
size_t size = blocks.size(); // argc
char** argv = new char*[size];
char** argv = new char*[size];// 创建一个新的 char* 数组
i = 0;
std::for_each(blocks.begin(), blocks.end(), [argv, &i](const std::string& b) {
argv[i++] = const_cast<char*>(b.c_str());
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));
this->parse(static_cast<const int>(size), const_cast<const char**>(argv)); // 调用 parse 方法解析命令行参数
delete[] argv;
delete[] argv;// 删除 argv
argv = nullptr;
return pr;
return pr;// 返回解析结果
}
bool Parser::has(const char* key) {
std::string skey(key);
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 (pr_ && !pr_->empty() && !skey.empty()) {//判断是否存在
if (skey[0] == '-') {
// check combination options, e.g., Parser::has("-xyz")
for (size_t i = 1; i < skey.size(); ++i) {
// 如果 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()) {
tkey.push_back(skey[i]);// 获取选项名
if (pr_->find(tkey) == pr_->end()) { // 如果选项名在 pr_ 中不存在
return false;
}
}
return true;
} else {
// check single option, e.g., Parser::has("x")
return pr_->find(skey) != pr_->end();
// 如果 skey 是一个单个选项,例如 "x"
return pr_->find(skey) != pr_->end();// 检查选项是否在 pr_ 中存在
}
}
return false;
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) {
if (options.size() == 0) {// 如果 options 为空
return false;
}4
for (auto key : options) {// 遍历 options 中的每个选项
if (this->has(key)) return true;// 如果选项在 pr_ 中存在,返回 true
}
for (auto key : options) {
if (this->has(key)) return true;
}
return false;
return false;// 如果 options 中的所有选项都不存在,返回 false
}
bool Parser::has_and(std::initializer_list<const char*> options) {
if (options.size() == 0) {
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) {
if (!this->has(key)) return false;
for (auto key : options) {// 遍历 options 中的每个选项
if (!this->has(key)) return false;// 如果选项在 pr_ 中不存在,返回 false
}
return true;
return true;// 如果 options 中的所有选项都存在,返回 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) {
this->cleanup();
if (argc > 0) {// 如果参数数量大于 0
this->cleanup(); // 清理之前的解析结果
args_.reserve(static_cast<size_t>(argc_));
args_.reserve(static_cast<size_t>(argc_));// 为 args_ 预留空间
for (int i = 0; i < argc_; ++i) {
args_.push_back(argv[i]);
for (int i = 0; i < argc_; ++i) {// 遍历所有的命令行参数
args_.push_back(argv[i]);// 将参数添加到 args_
}
pr_ = new Parser::ParseResult;
pr_ = new Parser::ParseResult;// 创建新的解析结果
return true;
}
return false;
return false;// 如果参数数量为 0返回 false
}
void Parser::cleanup() {
args_.clear();
if (pr_) {
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) {
for (; it != iend; ++it) {// 遍历 pr_ 中的每个元素
ParseItem* item = it->second;
if (item) delete item;
if (item) delete item;// 删除元素
}
delete pr_;
delete pr_;// 删除 pr_
pr_ = nullptr;
}
}
void Parser::set_addition() {
if (subroutines_->find(subroutine_name_) != subroutines_->end()) {
for (const Row& row : *(subroutines_->at(subroutine_name_))) {
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_;
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;
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));
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;
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));
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
@ -427,114 +433,115 @@ void Parser::set_addition() {
// class Row
Row::Row() : require_value(true) {}
Row::Row() : require_value(true) {} // Row 类的构造函数,初始化 require_value 为 true
// class Subroutine
Subroutine::Subroutine() : first_line_("") {}
Subroutine::Subroutine() : first_line_("") {}// Subroutine 类的默认构造函数,初始化 first_line_ 为空字符串
Subroutine::Subroutine(const char* name, const char* description)
: first_line_(""), description_(description), name_(name) {
usages_.reserve(5);
: first_line_(""), description_(description), name_(name) { // Subroutine 类的构造函数,接受子程序名和描述作为参数
usages_.reserve(5);// 为 usages_ 预留空间
}
void Subroutine::print_with_row(std::ostream& out) {
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) {
// 打印子程序名和描述
if (strcmp(get_first_line(), "") != 0) {// 如果 first_line_ 不为空
// print the first line
out << get_first_line();
if (!usages_.empty()) {
out << std::endl;
if (!usages_.empty()) {// 如果 usages_ 不为空
out << std::endl;// 打印换行符
}
}
auto begin = usages_.begin();
auto end = usages_.end();
auto begin = usages_.begin(); // 获取 usages_ 的开始迭代器
auto end = usages_.end();// 获取 usages_ 的结束迭代器
std::vector<std::string> row_list;
row_list.reserve(usages_.size());
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) {
std::stringstream ss;
ss << " ";
if (!row.oshort().empty()) {
ss << "-" << row.oshort() << " ";
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.olong().empty()) {// 如果长选项不为空
if (!row.oshort().empty())
ss << "[ --" << row.olong() << " ] ";
ss << "[ --" << row.olong() << " ] ";// 添加长选项
else
ss << "--" << row.olong() << " ";
ss << "--" << row.olong() << " "; // 添加长选项
}
if (row.required()) {
ss << "arg ";
if (!row.value().empty()) {
ss << "= " << row.value() << " ";
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()));
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();
for (size_t i = 0; i < row_count; ++i) {
std::string str_row(std::move(row_list[i]));
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;
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) {
for (auto usage : usages_) {
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)) {
for (auto t = template_str_.begin(); t != template_str_.end(); ++t) {// 遍历模板字符串
if (*t == '%') {// 如果当前字符是 '%'
switch (*(order_.begin() + i)) { // 根据 order_ 中的值决定打印哪个字段
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) {
if (subroutine.template_str_.empty()) {
subroutine.print_with_row(out);
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);
subroutine.print_with_template(out);// 使用 print_with_template 方法打印
}
return out;
return out;// 返回输出流
}
}

@ -1,127 +1,127 @@
#include "easypr/util/util.h"
#include <string>
#ifdef OS_WINDOWS
#include <windows.h>
#include <direct.h>
#include <io.h>
#define PATH_DELIMITER '\\'
// #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
#undef min // 如果已经定义了min取消其定义
#endif
#ifdef max
#undef max
#undef max // 如果已经定义了max取消其定义
#endif
#elif defined(OS_LINUX) || defined(OS_UNIX)
#include <cstring>
#include <dirent.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring> // 包含cstring头文件用于字符串操作
#include <dirent.h> // 包含dirent.h头文件用于目录操作
#include <sys/stat.h> // 包含sys/stat.h头文件用于文件状态检查
#include <unistd.h> // 包含unistd.h头文件用于Unix标准的系统调用
#define PATH_DELIMITER '/'
#define PATH_DELIMITER '/' // 定义路径分隔符为'/'
#endif
#ifdef OS_UNIX
#include <sys/timeb.h>
#include <sys/timeb.h> // 包含sys/timeb.h头文件用于时间操作
#endif
#include <list>
#include <opencv2/highgui/highgui.hpp>
#include <list> // 包含list头文件用于list数据结构
#include <opencv2/highgui/highgui.hpp> // 包含opencv的highgui模块用于图像IO操作
namespace easypr {
namespace easypr { // 定义easypr命名空间
long Utils::getTimestamp() {
long Utils::getTimestamp() { // 定义获取时间戳的函数
#ifdef OS_WINDOWS
return static_cast<long>(cv::getTickCount());
return static_cast<long>(cv::getTickCount()); // Windows平台下使用opencv的getTickCount函数获取时间戳
#endif
#ifdef OS_LINUX
struct timespec ts;
struct timespec ts; // 定义timespec结构体用于获取时间
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;
ftime(&tb);
return long(tb.time * 1e3 + tb.millitm);
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('.');
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;
std::string s(str);
size_t pos;
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);
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;
std::list<std::string> subfolders;
subfolders.push_back(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());
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) {
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);
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;
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
Loading…
Cancel
Save