From ff064b2dca9a780118e74f93f02ca399b6c6a3cd Mon Sep 17 00:00:00 2001 From: ycl <529768926@qq.com> Date: Wed, 2 Aug 2017 00:40:03 +0800 Subject: [PATCH] document plate locate --- easyPR-doc/algorithm.md | 4 + easyPR-doc/classDemo.md | 9 ++ easyPR-doc/core/chars_identify.md | 34 +++++ easyPR-doc/core/feature.md | 85 +++++++++++++ easyPR-doc/core/plate.md | 16 +++ easyPR-doc/core/plate_detect.md | 29 +++++ easyPR-doc/core/plate_judge.md | 71 +++++++++++ easyPR-doc/core/plate_locate.md | 198 +++++++++++++++++++++++++++++ easyPR-doc/core/plate_recognize.md | 0 easyPR-doc/easyPR.md | 10 ++ easyPR-doc/img/SVM.png | Bin 0 -> 22892 bytes easyPR-doc/test/main.md | 0 easyPR-doc/util/Kv.md | 53 ++++++++ easyPR-doc/util/Utils.md | 84 ++++++++++++ easyPR-doc/util/program_options.md | 157 +++++++++++++++++++++++ 15 files changed, 750 insertions(+) create mode 100644 easyPR-doc/algorithm.md create mode 100644 easyPR-doc/classDemo.md create mode 100644 easyPR-doc/core/chars_identify.md create mode 100644 easyPR-doc/core/feature.md create mode 100644 easyPR-doc/core/plate.md create mode 100644 easyPR-doc/core/plate_detect.md create mode 100644 easyPR-doc/core/plate_judge.md create mode 100644 easyPR-doc/core/plate_locate.md create mode 100644 easyPR-doc/core/plate_recognize.md create mode 100644 easyPR-doc/easyPR.md create mode 100644 easyPR-doc/img/SVM.png create mode 100644 easyPR-doc/test/main.md create mode 100644 easyPR-doc/util/Kv.md create mode 100644 easyPR-doc/util/Utils.md create mode 100644 easyPR-doc/util/program_options.md diff --git a/easyPR-doc/algorithm.md b/easyPR-doc/algorithm.md new file mode 100644 index 00000000..8fa945e6 --- /dev/null +++ b/easyPR-doc/algorithm.md @@ -0,0 +1,4 @@ + - NMS(非极大值抑制) + - 选取邻近区域中得分最高的矩形块,以区分多个矩形重叠的情况 + - SVM(支持向量机) + - diff --git a/easyPR-doc/classDemo.md b/easyPR-doc/classDemo.md new file mode 100644 index 00000000..9bcd3898 --- /dev/null +++ b/easyPR-doc/classDemo.md @@ -0,0 +1,9 @@ +### Class + +#### Properties + +#### Functions + +*** + +*** diff --git a/easyPR-doc/core/chars_identify.md b/easyPR-doc/core/chars_identify.md new file mode 100644 index 00000000..a5d4e2e9 --- /dev/null +++ b/easyPR-doc/core/chars_identify.md @@ -0,0 +1,34 @@ +### Class CharsIdentify + +#### Properties + - (private:) + - [`annCallback extractFeature`](#extractFeature) + - [`static CharsIdentify* instance_`](#instance_) + - [`cv::Ptr ann_`](#ann_) + - [`cv::Ptr annChinese_`](#annChinese_) + - [`cv::Ptr annGray_`](#annGray_) + - [`std::shared_ptr kv_`](#kv_) + +#### Functions + - (public:) + - [`static CharsIdentify* instance()`](#instance) + - [`int classify(cv::Mat f, float& maxVal, bool isChinses = false, bool isAlphabet = false)`](#classify1) + - [`void classify(cv::Mat featureRows, std::vector& out_maxIndexs,std::vector& out_maxVals, std::vector isChineseVec)`](#classify2) + - [`void classify(std::vector& charVec)`](#classify3) + - [`void classifyChinese(std::vector& charVec)`](#classifyChinese) + - [`void classifyChineseGray(std::vector& charVec)`](#classifyChineseGray) + - [`std::pair identify(cv::Mat input, bool isChinese = false, bool isAlphabet = false)`](#identify1) + - [`int identify(std::vector inputs, std::vector>& outputs,std::vector isChineseVec)`](#identify2) + - [`std::pair identifyChinese(cv::Mat input, float& result, bool& isChinese)`](#identifyChinese) + - [`std::pair identifyChineseGray(cv::Mat input, float& result, bool& isChinese)`](#identifyChineseGray) + - [`bool isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese = false)`](#isCharacter) + - [`void LoadModel(std::string path)`](#LoadModel) + - [`void LoadChineseModel(std::string path)`](#LoadChineseModel) + - [`void LoadGrayChANN(std::string path)`](#LoadGrayChANN) + - [`void LoadChineseMapping(std::string path)`](#LoadChineseMapping) + - (private:) + - [`CharsIdentify()`](#CharsIdentify) + +*** + +*** diff --git a/easyPR-doc/core/feature.md b/easyPR-doc/core/feature.md new file mode 100644 index 00000000..d51fd9ce --- /dev/null +++ b/easyPR-doc/core/feature.md @@ -0,0 +1,85 @@ +### feature + +#### Functions + - [`cv::Mat getHistogram(cv::Mat in)`](#getHistogram) + - [`typedef void (*svmCallback)(const cv::Mat& image, cv::Mat& features)`](#svmCallback) + - [`typedef void (*annCallback)(const cv::Mat& image, cv::Mat& features)`](#annCallback) + - [`void getGrayPlusProject(const cv::Mat& grayChar, cv::Mat& features)`](#getGrayPlusProject) + - [`void getHistogramFeatures(const cv::Mat& image, cv::Mat& features)`](#getHistogramFeatures) + - [`void getSIFTFeatures(const cv::Mat& image, cv::Mat& features)`](#getSIFTFeatures) + - [`void getHOGFeatures(const cv::Mat& image, cv::Mat& features)`](#getHOGFeatures) + - [`void getHSVHistFeatures(const cv::Mat& image, cv::Mat& features)`](#getHSVHistFeatures) + - [`void getLBPFeatures(const cv::Mat& image, cv::Mat& features)`](#getLBPFeatures) + - [`void getColorFeatures(const cv::Mat& src, cv::Mat& features)`](#getColorFeatures) + - [`void getHistomPlusColoFeatures(const cv::Mat& image, cv::Mat& features)`](#getHistomPlusColoFeatures) + - [`cv::Mat charFeatures(cv::Mat in, int sizeData)`](#charFeatures) + - [`cv::Mat charFeatures2(cv::Mat in, int sizeData)`](#charFeatures2) + - [`void getLBPplusHistFeatures(const cv::Mat& image, cv::Mat& features)`](#getLBPplusHistFeatures) + - [`void getGrayCharFeatures(const cv::Mat& grayChar, cv::Mat& features)`](#getGrayCharFeatures) + - [`void getGrayPlusLBP(const Mat& grayChar, Mat& features)`](#getGrayPlusLBP) + +*** + +#### cv::Mat getHistogram(cv::Mat in) + - 获得车牌的特征数 + + +#### typedef void (*svmCallback)(const cv::Mat& image, cv::Mat& features) + - EasyPR的getFeatures回调函数 + - 用于从车牌的image生成svm的训练特征features + + +#### typedef void (*annCallback)(const cv::Mat& image, cv::Mat& features) + - EasyPR的getFeatures回调函数 + - convert from images to features used by gray char ann + + +#### void getGrayPlusProject(const cv::Mat& grayChar, cv::Mat& features) + - gray and project feature + + +#### void getHistogramFeatures(const cv::Mat& image, cv::Mat& features) + - EasyPR的getFeatures回调函数 + - 本函数是获取垂直和水平的直方图图值 + + +#### void getSIFTFeatures(const cv::Mat& image, cv::Mat& features) + - 本函数是获取SIFT特征子 + + +#### void getHOGFeatures(const cv::Mat& image, cv::Mat& features) + - 本函数是获取HOG特征子 + + +#### void getHSVHistFeatures(const cv::Mat& image, cv::Mat& features) + - 本函数是获取HSV空间量化的直方图特征子 + + +#### void getLBPFeatures(const cv::Mat& image, cv::Mat& features) + - LBP feature + + +#### void getColorFeatures(const cv::Mat& src, cv::Mat& features) + - color feature + + +#### void getHistomPlusColoFeatures(const cv::Mat& image, cv::Mat& features) + - color feature and histom + + +#### cv::Mat charFeatures(cv::Mat in, int sizeData) + - get character feature + + +#### cv::Mat charFeatures2(cv::Mat in, int sizeData) + - get character feature + + +#### void getLBPplusHistFeatures(const cv::Mat& image, cv::Mat& features) + - LBP feature + Histom feature + + +#### void getGrayCharFeatures(const cv::Mat& grayChar, cv::Mat& features) + + +#### void getGrayPlusLBP(const Mat& grayChar, Mat& features) diff --git a/easyPR-doc/core/plate.md b/easyPR-doc/core/plate.md new file mode 100644 index 00000000..6a13154a --- /dev/null +++ b/easyPR-doc/core/plate.md @@ -0,0 +1,16 @@ +### Class + +#### Properties + - (private:) + - [`Mat m_plateMat`](#m_plateMat) + - [`RotatedRect m_platePos`](#m_platePos) + - [`double m_score`](#m_score) + +#### Functions + +*** + +####double m_score + - 保存了`SVM`后的分数,越大证明越可能是车牌 + +*** diff --git a/easyPR-doc/core/plate_detect.md b/easyPR-doc/core/plate_detect.md new file mode 100644 index 00000000..a789f78c --- /dev/null +++ b/easyPR-doc/core/plate_detect.md @@ -0,0 +1,29 @@ +### Class CPlateDetect + +#### Properties + - (priavte:) + - [`int m_maxPlates`](#m_maxPlates) + - [`CPlateLocate* m_plateLocate`](#m_plateLocate) + - [`int m_type`](#m_type) + - [`static std::string m_pathSvm`](#m_pathSvm) + - [`bool m_showDetect`](#m_showDetect) + +#### Functions + - (public:) + - [`int plateDetect(Mat src, s td::vector &resultVec, int type,bool showDetectArea, int img_index = 0)`](#plateDetect1) + - [`int plateDetect(Mat src, std::vector &resultVec, int img_index = 0)`](#plateDetect2) + - [`void LoadSVM(std::string s)`](#LoadSVM) + +*** + +#### int m_maxPlates(`get|set`) + + +#### CPlateLocate* m_plateLocate + - 用于车牌定位的对象,完成了所有车牌定位的操作 + + +#### m_type(`set`) + - + +*** diff --git a/easyPR-doc/core/plate_judge.md b/easyPR-doc/core/plate_judge.md new file mode 100644 index 00000000..bd24c327 --- /dev/null +++ b/easyPR-doc/core/plate_judge.md @@ -0,0 +1,71 @@ +### Class PlateJudge + +#### Properties + - (private:) + - [`static PlateJudge* instance_`](#instance_) + - [`svmCallback extractFeature`](#extractFeature) + - [`cv::Ptr svm_`](#svm_) + +#### Functions + - (public:) + - [`static PlateJudge* instance()`](#instance) + - [`void LoadModel(std::string path)`](#LoadModel) + - [`int plateJudgeUsingNMS(const std::vector&, std::vector&, int maxPlates = 5)`](#plateJudgeUsingNMS) + - [`int plateSetScore(CPlate& plate)`](#plateSetScore) + - [`int plateJudge(const Mat& plateMat)`](#plateJudge1) + - [`int plateJudge(const std::vector &inVec,std::vector &resultVec)`](#plateJudge2) + - [`int plateJudge(const std::vector &inVec,std::vector &resultVec)`](#plateJudge3) + - [`void NMS(std::vector &inVec, std::vector &resultVec, double overlap)`](#NMS) + - [`int PlateJudge::plateJudgeUsingNMS(const std::vector &inVec, std::vector &resultVec, int maxPlates)`](#plateJudgeUsingNMS) + - (private:) + - [`PlateJudge()`](#PlateJudge) + +*** + +#### static PlateJudge* instance_ + - 静态变量`instance_`,用于单例模式 + +*** + +#### static PlateJudge* instance() + - 单例模式初始化方法,当`instance_`为`nullptr`时,初始化一个`PlateJudge` + - 返回`instance_` + + +#### PlateJudge() + - 加载`svm`的模型,在`config.h`中配置模型路径 + - 可选择是否使用`LBP`模型,因为暂时没有这个模型,所以使用的是`Hist` + + +#### void LoadModel(std::string path) + - 输入参数为路径`path` + - 如果`path`与默认`kDefaultSvmPath`路径不同,则加载`path`下的模型,如果相同,那么加载的模型和`kDefaultSvmPath`路径下的相同 + + +#### int plateSetScore(CPlate& plate) + - 这个函数通过`SVM`为输入的`CPlate`计算分数,小于0且越小为车牌的可能性越大 + - 方法判定`score`大于`0.5`的`CPlate`为不是车牌 + - 返回值为`0`表示是车牌,返回值为`-1`表示不是车牌 + + +#### int plateJudge(const Mat& plateMat) + - 调用`plateSetScore`得到`0/-1`并返回,`0`代表是车牌,`1`表示不是车牌 + + +#### int plateJudge(const std::vector &inVec,std::vector &resultVec) + - 输入为`Mat`的集合`inVec`,代表了需要判断的`Mat`的集合 + - 输出为`resultVec`,代表了所有可能是车牌的集合 + - `return 0` + + +#### int plateJudge(const std::vector &inVec,std::vector &resultVec) + - 输入为`CPlate`的集合`inVec`,代表了需要判断的`CPlate`的集合 + - 输出为`resultVec`,代表了所有可能是车牌的集合 + - 和`int plateJudge(const std::vector &inVec,std::vector &resultVec)`不同的是,在这个方法中会进行一次矩形的缩小再判断 + - `return 0` + + +#### void NMS(std::vector &inVec, std::vector &resultVec, double overlap) + + +#### int PlateJudge::plateJudgeUsingNMS(const std::vector &inVec, std::vector &resultVec, int maxPlates) diff --git a/easyPR-doc/core/plate_locate.md b/easyPR-doc/core/plate_locate.md new file mode 100644 index 00000000..b4109812 --- /dev/null +++ b/easyPR-doc/core/plate_locate.md @@ -0,0 +1,198 @@ +### Class CPlateLocate + +#### Properties + - (protected:) + - [`int m_GaussianBlurSize`](#m_GaussianBlurSize) + - [`int m_MorphSizeWidth`](#m_MorphSizeWidth) + - [`int m_MorphSizeHeight`](#m_MorphSizeHeight) + - [`float m_error`](#m_error) + - [`float m_aspect`](#m_aspect) + - [`int m_verifyMin`](#m_verifyMin) + - [`int m_verifyMax`](#m_verifyMax) + - [`int m_angle`](#m_angle) + - [`bool m_debug`](#m_debug) + +#### Functions + - (public:) + - [`bool CPlateLocate::verifySizes(RotatedRect mr)`](#verifySizes) + - [`int CPlateLocate::mserSearch(const Mat &src,vector &out,vector>& out_plateVec, bool usePlateMser, vector>& out_plateRRect,int img_index, bool showDebug)`](#mserSearch) + - [`void CPlateLocate::setLifemode(bool param)`](#setLifemode) + - [`int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,vector &outRects)`](#colorSearch) + - [`int CPlateLocate::sobelFrtSearch(const Mat &src,vector> &outRects)`](#sobelFrtSearch) + - [`int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW, int morphH)`](#sobelOper) + - [`int CPlateLocate::deskew(const Mat &src, const Mat &src_b,vector &inRects,vector &outPlates, bool useDeteleArea, Color color)`](#deskew) + - [`bool CPlateLocate::isdeflection(const Mat &in, const double angle,double &slope)`](#isdeflection) + - [`bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size, const Point2f center, const double angle)`](#rotation) + - [`int CPlateLocate::plateColorLocate(Mat src, vector &candPlates,int index)`](#plateColorLocate) + - [`int CPlateLocate::plateMserLocate(Mat src, vector &candPlates, int img_index)`](#plateMserLocate) + - [`int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,int morphH)`](#sobelOperT) + - [`int CPlateLocate::plateSobelLocate(Mat src, vector &candPlates,int index)`](#plateSobelLocate) + - [`int plateLocate(Mat, std::vector&, int = 0)`](#plateLocate1) + - [`int plateLocate(Mat, std::vector&, int = 0)`](#plateLocate2) + +*** + +#### int m_GaussianBlurSize(`get|set`) + - 高斯模糊所用的变量 + + +#### int m_MorphSizeWidth(`get|set`) + - 选定闭操作时的宽度 + + +#### int m_MorphSizeHeight(`get|set`) + - 选定闭操作时的长度 + + +#### float m_error(`get|set`) + - 允许的误差比 + + +#### float m_aspect(`get|set`) + - 车牌的长宽比 + + +#### int m_verifyMin(`set`) + - 筛选长宽比时的最小比例 + + +#### int m_verifyMax(`set`) + - 筛选长宽比时的最大比例 + + +#### int m_angle(`set`) + - 角度判断所用变量,正负超过这个角度的筛选出来的矩形都会被舍弃 + + +#### bool m_debug(`get|set`) + - 是否开启调试模式,0关闭,非0开启 + +*** + +#### bool CPlateLocate::verifySizes(RotatedRect mr) + - 输入旋转矩阵`mr` + - 函数内部设置了车牌的长宽比,通过长宽比来确定是不是车牌区域 + - 如果是,就返回true,否则就返回false + + +#### mserSearch(const Mat &src,vector &out,vector>& out_plateVec, bool usePlateMser, vector>& out_plateRRect,int img_index, bool showDebug) + - MSER区域提取原理:对图像取阈值在[0,255]之间的灰度化,当选取区域大小最为稳定的区域 + - mser in opencv + - 该函数对输入图像进行了`MSER`区域提取并输出 + + +#### void CPlateLocate::setLifemode(bool param) + - 如果有`param`传入,对`GaussianBlurSize`等一系列参数进行设置 + - 如果没有参数传入,就采用默认的参数 + + +#### int CPlateLocate::colorSearch(const Mat &src, const Color r, Mat &out,vector &outRects) + - 此函数的作用是:对于输入的图片`src`和颜色`r`,对`src`进行`r`的颜色匹配 + - 匹配流程为(省略具体参数) + - `colorMatch`:灰度化 + - `threshold`:二值化 + - `getStructuringElement`:规定`10x2`的`kernel` + - `morphologyEx`:针对`kernel`进行闭操作(先erode再dilate) + - `findContours`:闭操作完寻找轮廓以确定(蓝牌或黄牌)的区域 + - `out`里存储的是二值化结果,`outRects`里存储的是寻找到的区域 + - 调用`verifySizes`筛选出符合长宽比的区域 + - `return 0` + + +#### int CPlateLocate::sobelFrtSearch(const Mat &src,vector> &outRects) + - 输入: + - 原始图片`Mat &src` + - 输出集合`vector> &outRects` + - 执行过程 + - `sobelOper`:对图像进行预处理 + - `verifySizes`:对处理完的矩形集合进行筛选 + - 存储到outRects中 + + +#### int CPlateLocate::sobelOper(const Mat &in, Mat &out, int blurSize, int morphW, int morphH) + - 输入 + - 原始图片`Mat &in` + - 输出图片`Mat &out` + - 滤波窗口大小`blurSize` + - 闭操作`kernel`的宽度`morphW` + - 闭操作`kernel`的长度`morphH` + - 函数流程(省略具体参数): + - `GaussianBlur`:高斯滤波 + - `cvtColor`:灰度化 + - `Sobel`:sobel算子 + - `threshold`:二值化 + - `morphologyEx`:闭操作 + - 将结果`Mat`赋值到`out`里 + - `return 0` + + +#### int CPlateLocate::deskew(const Mat &src, const Mat &src_b,vector &inRects,vector &outPlates, bool useDeteleArea, Color color) + - 输入: + - `Mat &src`:原始图片 + - `Mat &src_b`:二值化后的图片 + - `vector &inRects`:闭操作识别颜色后的区域矩形块。 + - `vector &outPlates`:输出`vector` + - `bool useDeteleArea`: + - `Color color`:输入的颜色(蓝牌或者黄牌) + - 对识别的区域进行旋转后,删除其中角度大于`m_angle`的,并对矩形进行划线(0,255,255)(BGR),之后存储到`CPlate类型中`,再保存到`outPlates`中 + + +#### bool CPlateLocate::isdeflection(const Mat &in, const double angle,double &slope) + - 判断是否倾斜 + + +#### bool CPlateLocate::rotation(Mat &in, Mat &out, const Size rect_size, const Point2f center, const double angle) + - 判断旋转 + + +#### int CPlateLocate::plateColorLocate(Mat src, vector &candPlates,int index) + - 输入: + - `Mat src`:原始图片,`vector &candPlates`:存储颜色筛选完并旋转的图 + - 对`src`进行黄牌和蓝牌的并行计算 + - `return 0` + + +#### int CPlateLocate::plateMserLocate(Mat src, vector &candPlates, int img_index) + - 基本操作类似于`color`和`sobel` + - 采取的核心算法是`mser` + - 最终结果存储在`candPlates`中 + + +#### int CPlateLocate::sobelOperT(const Mat &in, Mat &out, int blurSize, int morphW,int morphH) + - 输入 + - 原始图片`Mat &in` + - 输出图片`Mat &out` + - 滤波窗口大小`blurSize` + - 闭操作`kernel`的宽度`morphW` + - 闭操作`kernel`的长度`morphH` + - 处理等同于[`sobelOper`](#sobelOper),只不过在每一步操作时,都将分步的图片存储到了`/resources/image/tmp/`目录下 + + +#### int CPlateLocate::plateSobelLocate(Mat src, vector &candPlates,int index) + - 采取sobel算子进行筛选 + - 输入 + - `Mat src`:输入图像 + - `vector &candPlates`:输出图像集合 + - 执行过程 + - `sobelFrtSearch`:矩形筛选 + - 放大矩形区域 + - 二次切割加入 + - `deskew`旋转筛选 + - 存储到`candPlates`中 + + +#### int plateLocate(Mat src, std::vector &resultVec, int index = 0) + - 输入`src`和`resultVec` + - 将`src`进行车牌定位检测,检测到的结果放置到`resultVec`中 + - 检测过程如下: + - `plateColorLocate` | `plateSobelLocate` | `plateMserLocate` + - 三种检测手段得到的车牌候选矩形都会被放进`resultVec`中 + - `resultVec`中放置的类型是`Mat` + - `return 0` + + +#### int plateLocate(Mat src, std::vector &resultVec, int = 0) + - 输入`src`和`resultVec` + - 将`src`进行车牌定位检测,检测到的结果放置到`resultVec`中 + - `resultVec`中放置的类型是`CPlate` + - `return 0` diff --git a/easyPR-doc/core/plate_recognize.md b/easyPR-doc/core/plate_recognize.md new file mode 100644 index 00000000..e69de29b diff --git a/easyPR-doc/easyPR.md b/easyPR-doc/easyPR.md new file mode 100644 index 00000000..82019f63 --- /dev/null +++ b/easyPR-doc/easyPR.md @@ -0,0 +1,10 @@ +### document index + - `class Kv` + - `class Utils` + - `class Generator` + - `class plate` + - `class CPlateLocate` + - `class PlateJudge` + - `class CPlateDetect` + - `feature` + - `config.h`:配置文件 diff --git a/easyPR-doc/img/SVM.png b/easyPR-doc/img/SVM.png new file mode 100644 index 0000000000000000000000000000000000000000..e3bda3d8bc39a49262f2827e909e00ff3f2a145f GIT binary patch literal 22892 zcmcG$bx@Sy_dX2E(zQ$1(g=!3*V3@Ch@^BQ(jd8nG$LIhSV)I7N+Yd+h)79Fr!F88 zN=V4>UO(UXy??zk@60>1GctJZbJsc7xz2UYLxR38nv#r#3=a>F@}?&0HXa_t0uK*A zgoFtEhD?ob9sGmucN?vWS2N7EiHE0*cN3*z6okK%LmWzPeEL@}@}aIs!86TWovjBc zH+1m(I;Bit{tJa|+W{BVO2=K3{t>N-@rg5i#xNMHoB>tB!})~{4kP*UF7l7kNDwjq zvyhCJ>rbzaoc)o1@j_g@G;~DgV9{^5{cf^06s7$C{j;i<&s|qV|8(b%j3?VuhMhgt z?6U7NFQPAGVa-jF{lk4nDw#WBnjADM^LWJy*Fm7gLP0UhV!#)bTNBJYpl8-l(Oeof)f8_oUimfW{OHM*T*wltcT1AWvj)$nIi zU?(>fgS?}|nJR&Z`-`t$cbzvk@-G9@RnCG9QABI{+~+iHdTf(4u;Cg{3e~)B*$uh@ zH}3RtOh!TS8}hB?=YO6WwJ$Zby{RX#&%sVq$7rZFE3Y4L^%_WrIP8fWnW{)D|IgJ$ zMZyKrj(j`Db_*NYc)n08vmF-dL}6VrS5%&lea#mizme%FXMtHg;7r{^iRp?TDBAQ_ zkB@zNw@>CGY&2m}>e&*zU9=d54f^__RZOpRxNn~bOAQ-jqxO*)yr0tH)%?UTCJ)Rg z(G8)m<)GJaVK+h(8PWyeJC;mb(jE<{*86JN@2_?#YiH zQVnc{^}j7N^X{^Gwx{ZTY8;jCAE>V#>DyBBEB@NXwsKzr^HXnzy;0B3l7oftf}{dZ zwwjMhCacdc{lMSGeK#T*fplTJ?-v_oCyQAxLyT%-t>aS}c0{p^F!OmvWH`|rKPS?& zLArnzhNu(dYWhR?(bYDNDyUOx0$EHna}bQY^Z&S-q$A&l->*~=BL=V5D-Ib&AoxPBmA@zaAJJwo-padz;=mx)Ah+1yHiRkt5 zC><9Kg_B7)?a;=juJ_&HA9=r@##JL+pW^tqk2`*ctofU!48>RS`4Ia{M;A&A#GbXq zE_b`}idt99jTkfPe`ka!`z>zmBCn(?vK z{R~(RFH%eb=@yc&bb!b3(NQ-uRhzajA@%IaR?xay-Hxu6=j}%%htCD&dcf(!EWu-mM1RDy$L84=l(i2=;3e zDK4$ep}wGLB`)+qrWHwNE3VU7^Op%Z$Z5}{xLj&%>5MA7Q1zZws;(872_YD%V#C>2 zVz)Hs5L_KszF_gx>+m(@i4IdSVwoSVsv#dba;AQhjKls=AQrWKwLtZ?iAGL=v+fQn zM@+1$CD;F+KqQG(WRoxI`@HIGJKpziu{mAj{n=O6GUt}Kbfuk?b;&u#R^P^qUjNx` zqL$G@TPHeGcSG{WLlVnWDnyDeWhwFx-JOflTzNsV>*FNETR;PZBG${}y0Pf5Yi>MpFmW!5Eh`+U~7O|axx zCH^M zQw)r#tOuCIX(D2eHFm6C0WDh}kNE%Yw3}o;{^-l*3WjM-X)-Px?*eiPLqdImMdlZk zAqcv~!SXuqq;)XEJ#q}01zi9^4(~T(p6fYrm^ucvJe6!Riso6(<<dgAZ^4q%tP< z2DP<3-CWP8Qo(P13j@mwk_bQeXnIunM+j0 z(weHVa-~XH6UtVzQU$1SsAaQM6UbKMQ`N+=$6_PH3523*qMsitj81c1u+GWH^O*Z{ zrLr8w{9gyo*L_tK$%v7eRkbS1bz7t3$;nL1e_HTquOOdvv}jZfhE9@68Mx5=FxK)F zh7d)nrm0C5Xg*EomE%NOe)eOSz%RTYn79 z7a)vo^5mvSl1Dt6>M*JQ4O z3*8wL%AMo)Pw%7ab7l5PGnscFC16LPXgv4&@R*Lfcl%Rw_^#lbb(sND9?qf>u{WQEBap4Sq8g8pAL)oAo>`?BpYi4b@ zS;0Z}G2gs)&(V@A+9p-|+MNi_-!c-j>Fln@*1WzBr2-+;jH3m$DLgkb?_Kw*35yg$ zJhvW|Kxg?KE6Ld9yj_gGFDFDmd>LCPt8%=|w)=`LK#t|DUN&d(xajun{;Ph&>@#GSI}Ks%9uJAzXBh9!Fb&{HvvQz*#ZQx zzO{y}ZP@k!znp<3us{g(#=H0_C9-1dKN=3viY!tW%vX||9g)=6L#C3?HKH57trTv; z5OCc-{}`FVM)8IVGE1bjc9E{iFuZzS=3D&Zact1NI2nsW=HB#rGYR}@b+OdgT8gG; z_if0&N>RvtZu1j(=gGJY^DKJQ?Y;^91fYWry4SGMy}#CadfDqmqIoynd)b z&%Lmjay;2Ql646?rLzZ2&F(%6;V$ObQ-Xq>=$TFeYr@0GaH=t<&}P|-Uu-|V9?vEa z*ZL7lP-hYNJWQZ6zjHJqz#qbsqbKg$`e>cKNj!~!U67Oil>vG&9?`>M5^ug@d#}Bh1w3|v48xV;OZE6WDV~HrVYZPB(DaD{Y zZzZw4vo(tuiCYT}K1g}z##USTky`ad`YZ9eEf-KyYFQdt7LBB%ox@TYX__taFiHi& zv5IhTD)q>tpiY@77RCsM9!jq@7G1+HSBnVL;}UrcFigAA!KyC{-1GIgkk1|J;fW6D z^p7DUiJ052+%O}E8QvwijCns?Zu>A_Our4d>%0!<*VL)qNS-f7b19l#%t&go=CC`Y z^lJUto@@4jv$pvWYM768`9Jj)u;%eaXo=XH%Vm%6VYb9}qM4f6{Obp=|6oYQwl1*04Q zji=+5CC9wdRkgf}*Ehz2l)IZB8b5r~G5&~Cf~1w;6qsFcX#tmh(A$tlCfVgOslAO4 z?eEuZhFVFJaxYcD(8E~$G3@+UZPt5&8Qyp*do>ppx&dGTQC-0I@SwTjr<R+&1-SKNM*>$tS^jodHDNal)5mxwCKG7r$rdn?_=(@6Cql)Df+nqSk85% z8KS^Ozc`>qIapf2#661K{L)c$zPr^?dez7V`H*ZJ{AYJ z%0%2FGnC>y$*oc7U*=eqc%Gmp$sQ_13j+-OjVV%c{**H+^;^vzEpUW4@h1zbepe58%u*5bU%N;tTqc}a?L=6(EiyDBKPNG?yK1xs4&Q!(m_`Sh3U0w2kS ztmSFQNfFA)T7l&>c%O;VU|~MiQ#r7lUe8gN_&+|q$@j<8>vCj%FuWm^3C26U+IzJ( zFh7KA?&GC7mamMUH-bf4O_!%WslYiqVU`i+mKm{OZ=@=4CTo9({`pW#`iK9DV?MwC{S_k{8~$k8Is&UuE$YCwGGxWyk|WSebY+~Y6lz>0Vu zS8xF%cfyyr;9h2^1taN=q>OYAJ{~CoI&N-_T8uu zF(zu4F)iWA0n80#r!Y4%pPZ?$qcZVSa~&}B5*E*fFz10(>`_G8&JyQbctXx)G`4sE z#kmI+ar`K(p-_9_w|s}>Uv}NA89DA)8!C72!S?&hmc=LuBsx7=cQ~}nqBx_VA*42J z(&umJ4<+I;^NMv%07^W%ilk4dttVJ5Q=c}HBIKkop5pm)`I?7?h5@puZPI&j5?Sgj zVsJeCcF*sOOk=AJI4AfY4Tdupy6kW-w!RPU@E;(|#hd0D!2N$WE%eQe${VjJSb12^ z?evVyp3vdLN#yZgSC5f}#*s#nK(o6qyc+^mpU7E_3;d&0)3lF@HcoHcCIm3z0U_D+ zzIw#}wSV>>*3XfiOpU##>&YI-;1|a$18e9ehUSJI43Li{SM;YM&TKg(&^iDENJ#LP zkQUEvun9^ZmB=)eDqe6ehd}P%zkeC6xmWqK{nzl_POmB!0dPgEkWT-GQMH$%Ln7VM z3MH}e%8{hYNro)pc_V$?CtXmc=&T!Hmv*$+wqY zC&Qq~4bmo7~I9kfOa3*E$ZCLMiY#Xj%CTEAqM;i7+|5Q!l8FQRhiYU=ADmmH>v`n;Q|faad{$a8_t4kFd0P} z@6h9S!DRfmCc;QI-yEH8zgAj(CRL6Av%ceAc1M%DBN+XZ8;BRY1{l727LG)1HLxvViZf)Mzuv{6V9>pAK`{vkF`x!nz=_NWr+sA z^t`gS*F){RcI)=9I>*7kvqOhQ3pg9h7l3pIHi#VVU7Nb^z(}b29T%U}I29CfYU?}S z3kE6%=v5oh;%-ks5r0X}qIn@;PRwo~Vm%!lt{*Djss@gO9x6f;CfmuY@G`N^_!U?n zP*HFzc>g|rid|d9ibc=+(=9Mg6sl^QSYcXY^(VJCGv5l#gyOspeX!~hmhAO0RND&h z#${i~$!!crCtFz6jQNdS0b@Nd<_aXryJ6Wu2rq>}6h5X7c5{_4a%q};#p8mf2t&Qaf9~IA!MCgc`=e4u0E@;JirENl<~%6*Bwecyp9F*@WcZ zHozV9xbapai)l@wz*9tNl{Mq?R#)$fF6EQsme;)jg{-*5oZ3liG~DpHA5j*}4Kss` zC>*n)b1dzC|7nhj$&Uqha+0cS$0l@+Z=15Xz1hZY6piZUhf*Ps7>=wuulW|A%Fr*o zlHjP6q1s?v)%Yp~Wq9}&ti+wvDrW3@$Mk69LQx{{fXs*BLDdHvgI!SNN%oK*wZ9)n zeo+8ROW*;ijJ3#CsL4XB|K+2W+nM$MHj;-M&+6H7hj{PX9S3)p1JOzpAffL`?0C1s zU3&IbdZ_6Ga)$po*E9+A!x)+W=hXK0_N0!fn-#A~PIB+#v|~2j_#gtMyu&pH}=svCL%Ud^RCeb}qSgidL*vAowcp z13ZoT@Yv8lM@0XIfJ4b=tN#4?^Rqp4#{mJgy#83ALOD@FBr)moN~U(QQ{#j1@9(xM zKjH3ApEpWb)?(TwWG5UZYXI_yTS>e{IKOzTvaEMuUY7p#+_^bPVuo0r+zBK ziv8){FT+w@%9~OyPxv9+-dFI?6>Xv?7>DJHfLIP(gE*>N)?(w+6U$hyQS0-uFt-?z zpl98^3A7_BJN0{KWi#fBI~tQ#zzs+sQGN{~S(4=2D!z%!h2vmNq*o&K1&`PGzU0mO z4hj*|SY_5Rg0wa&6Hby{Hj1*|JVn6T=rTMbx>GAVBAwe|bhUA0kT{GC8aOXzLY0<$3&~FYjQQ0~eEMtPi5|Eosy>olqYm$KATYg;%Mbh0I4;G*ccm^)`jr+L9jM&e zz(c)Bpg8P4OrOU&6wtkLKDclDfaQWl_j#6XRUQoKmj@tr^4ie?x@F5i;$H-%`k$eB!3n!ILk48i2?VEVEp5ajkOS+B- zqo#k(@>1CjA(ZU2L2&f9Cyv_og$MSU9`u2R-NE|6ll_J}a-nbkSdY%u9D(zO{u>)0 zmtC#5FVrS|j3Z0H2LqEAqvxsT1Lp4S^4HL9T*ncppFA)ZH2G4^(n_<(v44^qFXS(G zIalpv0n|%^dw)$$U2)*lb-`H3IXFKt7xbeja zn>}Hp^gjHR6acoaLzsqGNEEKK{W~8NBr33BkJ?TF`S+YNpmX**B;ksP&nl|(TgCPF zx=dhhfFGwTFC&EH{aZ*7EVa?%kz zsW@ta^Qs4$%Bc*&h)as8T%`Y2J7+lFgcDp78bwNbrJ;M*I0t~u(iQHAjjOnOu>oQc zxk8L2FL|x&&nLQu3hLcEP#9@$ZGDrC-;iY6f{p1U2rnQ;+|0KMFfm!qBRZY4y>$E8 zzI}s-A>q92>Mkc7MDkMd-!fjER5R0$<$q>k^@mO)5?5yNTfQM( zBrsyJ+xKtEmWeh{eu_R}h-WO&c{F}P_=7cmS5fG5cuK~1WmAR#1JdEfcM7(uwrUa4 z460trIr&4dKffQ;#C=dnui~fSS6L_^&`;NpaoX(+Q@6n$lZ#B=dHUMyj<2up`Jz#y z9+XDQZhNZw8*pQZ*E%^_qrREsVg9W& zcEk4-pvdA}WoJ(WO{Ma}j$W`;=YuQy*#bA~41HT}7#9(U^P@Y^snzFHXo(^o)qb8Y zYP{{zkH^ei7&agB+-thK^mdMnnC)F{HBA5!P*IC^dIjbJ)BoPg^c!A3_8#p`?@s6a zB=r={|3wNZ(w6TU0BQ1Alkczf3*@E3e(YG)rr7zVSn9)@a`-2SCw;~Fl^W1&b_yt` zY<_b@^q0rw6Blr!8`oW8l${(MUs$)zGy~2Eys>-sdVS!ojkw3mo5xNCXQ=KGD3$`{ zOETk>`sN(}l#6btyt`byisg2=zlPhphxI-!?RJm-Cn%LRU1m%;**sR;gw;<`3 zelDWFg(j#9#c!c2DN$6qtVz*~s-(j9XU^Et(L7aG18Ls|RhO;l*ItU}dp%|O7!YLg zT_A%`g~T^EH)Ff>aGQ8e>W%uw6+eEv&!^4|Z&%2T8qLih#HFcc^PhvwKA5RZW9<{aZ(>C zLElPPsOH34YHS-<6S?m4)hKaR%6;ndb@RI3JyuzpLBoj+q%gqZsP0%wR_(P9R4756|8mRpOc(Xk|LXsA{Oy&Z4Y>=nC)lAKnV# zV)^C7TB(XDmQri@u`5*?an!fS;F`6alQbzxC-v@Y;uGTw>p`v)iLy`ORES#~Se(&dDFt z&pgzE6z&h#@S{JGN$Mbwd!t`xo9?~rOqYA^VhXkA+xvUA`_gT)g845yE+@3-79lbn zBVADnFBNY<^rtBgan=wZ34zVrAm_=6f7GO;%92qN8%{|XD?_6`h7Ix&iRIB>`B2{N z#g^b&5SD6j&#!o{ZM*i@jovz}i%;IuJ5;`@cgDi%|U8+0@WNsMJ0&0h--zc2;OzZ<0P~yzk)01!KhNFp)9A7T!mCR zoUqudwft*DNqV8x=Uz3)GXZqF#QM!A(%Z~xQ8fh49tELyDYeac( zN5}uiUZ~FwWL`x2$15Ef&kfBG)-!^9J{Up(X_U=f+!MqxLA#pyO>(pphWPAPkfP5E zZUZO1eR%;Etx|NSCpPx-I^W&;8_LjPy06GMW*hTeZV&DdmK2b)bk}MhzBE%VW;yvdrxyM zHuc%vvwJR=!ZUwa3)P+y`p0dljHn)wEG*TI81T(uCmd%1dj4gcwz26w%>G-6?;jOT zl|HszaMz>i5!FTQP|P<~Qx!&)s=K^vqY63JQ*ZS%iZ;5(8(a zz{&b6&{TGX;NYxBdaP45)^g`G2oN#pc=veNzLc*t7z$%~%0xmq_~X8#*;(4dHGxg;nP9Rd`( z{vEg1dleoYo*v&fO&(wEEgF^?aY=wOFC-Kh2sdw@` zN*yx8@?pP^8VE30K1{;e?K39pCXchMEwjfDKOdKwrP*YBO!ZPBt9RQ{)!B#tfuyR% zeJnA2STrH^Q|##5(-~i6JP)d2DZIHhJfLsnzqCUxjYw;fy{A%mXMnl`(IxzO(b z@|)meK%ruPxL=t-8x3P-itpHeE0VJ455w;W|6c@2)C|<&6z{iTykY#V^08{um~b$| z>c_jp*rm~-N8=xV*khTU?Hj_J1pHt!XFuvGZ4+@ZAzn8!)g*?D{_{#toRDb1lwx~! zE{{dEWpiqH(J)}4z0t}#4yvUO4J}QTGx8l_h$5F%THTyGOWElMVUUpxwf;`M#MXZP zqn`=hM>!;}Y{o@Jhb-%C>Z>JZTs0}H$oiYGK`%NQsVVz_SBKwJKDNk6Ahr-ls-@A* zc^a*A$ec4*s);r6{`2wYUD1E3k@)-a<6f~@s_UQ5WVxN^1b#TErE5^`xa)ci<}8k< zsz(}Be;<13oXVl(dBFo`0AW^~bcV+w^d+Qp?vGSV3^`+#N;DC3jejOC1JP2JCLZjD4v^ah*bebbRITxKSD7lc1d-=j{Rd96{`O#`-ibta`{}N+VS^r zG(iJNt$IezT`Za~UepzJBl<6iPv(|2)(RVj^~Ku6k3Y#N@G4M&y}0+V=D{oF^;5z4 zqg(N*<~OJl#eg|HJZa!snryoN%W1sn-rSpdBv2DM@y`-^CM!)}4(G^O+Um|Wd%fnT zKHLX{hxVk9so8hJnGc0d)F_SQxp7IoYQ&L~;HLlN(KS0uqQd${w!}Vc{!YS*s?9H9 z?4z~W@3mdSK0oF+FkCs@HReeUY#Tsy1`KjL_tBfHPehlPM<*ZEdkU|e(8vEB9U85N zA&lqD;_&r5^i}mdWD`FiKRCF4H8=kgBDyYomfJlj+~pzkd!yM807dr;=wjHhb;i@D zPE%FpcL}d9)ZUGId$O7|(yo6smtUVi@9EFMA)Q~LY7=;HO*npVOPvU$(QUGB>g7Ye zZG9jTp&||AY(w{B=oh1gO}fG_djHc#282IZ3wXYS+r-B#mIQCDn*d5VyUTm#=dJv( zqg>;qdU@kfw+PX=JOGu?2?i71H$!6@PLOa#13qA|G8uR|*ICwvHrCg(??>In4Z~$v zz72(;u#^lhT@0@X#L;DK$l8aL`rOY7zmorJrflfCi>#~65pzqXo(Ya2 zfT;ozH5QFqpJ}!JQ|cE`R!%j+XItW!cx}5BdbZR2ZTofCUqyouu+^#EUcuMx_Avo4 zs(&*^y(j%RcI~0wpM#B|S32o7nl>MGkbDruE8Xc;%;HYZZ90Nq5K*$6w3N7I;gZ7M zxJb5YRT-1lHVZ%I-_}18#Arbus4c(r>J$}s9+m(Km&njdX=>e`$=kT+3lM?>jOnhl zY_6fttbKF~jE1O0`fLF5;K(B7;Ua4h<DeNN^U$ zn*Dw%@@s@Zx5q+33M^z;qD9{~M0QH7vXl0r`(%GjPVs2wuI-bnhE5_+3+Ca!Qd&m? z!3oiSDWTX`o~!47%J`h|ak_3oWE|;roAw!yzJ3XwOpSY4P?4nkg~SfJ&A~KeK~?=> zaJpV2jlF}n6j&;x=RD+(>-nm@}zcId;H#YP45Pf58%tezICfNr|0f( zzIKLRn7+1As1Mkg<*xbS3g2Ul8c{s^9J=?8cuLcxFOmLYQl?P@x~ln4xPTuyC~CZE z<(v!In|gS*_PnF+03Scag?=nyC`)qu!3j6hWsBqJ6wG3%*I)Ndrz<3V zu0#^TBS&}1k=wHI)bhhq8Sw>d2n5t#3z7tlD;xkA zG4woXMM2wdDH|+x%mr55KOoW0mZ_D?uCmGu!jCCel{Fve*&TQ+QAn@W?D#J&3oti5 z5Dn}k5Cx5<>n1 zFULE-RIM{?&=lmBq790FPwxKOEX`xl<@xsQTUK^9oBM^<7r|nPIwwAq=`d$kxXQTl zDdh2$$k)^LRp~kRjyAr%y5E5)DZeBaZXYEX-%3hH3!U4x{A`-@e0e+T9OLzd#04d& z6(P=jUVJk{Tk7veZ&Xr|nN_pz-m*%)&)Nrpz^mrab~J9Y)6Nw>2PDh%$iSX~Ru1w- z)2VX9(jA;C%l8`dWStlX5x=0M?V3~rwLHuQ3+KrN1<)NBtK^PeZFFYh!j zNj)bMB_N{MGwCz7glmf6SM_}A#ZGdU-TPYS7$F((V+IJ6#dXJ5@r7cAi|@AiZ#$V( znce9Ke@MQnbb0P4I1vpTIe#fjGqaVHD}T94YIw`f$U8 z-sh!m5|F@W43^?3S)IUY8W)Na`CfFdr749gvQ>5Ho5~w6s_B|Mmh0~C!`bBU(C5z$ zxnjL?RaK4Gt$1I;}Wj?S-dL)HfT z#;VZU>dOQCc~uFGO-aiRl4y@AIT7O%mx`@I-)I$O9t+VUarXVjDYt!JSqo9samTCt z-DSJ=PD5gptWpvPxd83^VMh3HO+I{W+SJ&LMuPF^jb(?8j{{ncUeHnx018E~nVJ6r z-B#>`Pw0bns}b}z&P)yaZTQ;ax>>#24Z^{4{Pz0RLS@V%`YfuJT1x{e5|&7@)^Xvx zcN&G?;9Q35W-JABVPtXC1zl$6icQ2c*OWaaYH!T6peR0iX7|-gPYc7~YoAo0_Wb$q z>KXG*E%+V<*s9#(;1&KsoW*^3+5M1|Hn(43>Pe9OIUg#p>&oIEK_z&63b_+QE?hsh@y8#{l0Tkc2}ZZxwShd|K4QJjafU4sv| z99bk>U@b?L@80o=+&@_VbaLJmb4(WyopH$j@ZO1`w`lJ4^r{ew;?6JIjoR{* zltSU@!C&1`n2Ol{!$C#7?TQNI5R&0uc7l+ZZGBrbSU}~&Ab#IIi-OXKc<9|IZ77X; zw+{ZTyr9*D8m7K5VEY>|R(C8=XBTyi+Mc{7=}l73M>ib(_fypvbK+fUglYGB_v>07~FDGKUV7Lz$Gq&oAE%v?6w1BpFl{csJ?(DY@qs*F_3F zE$>NArucO*DEv~$vMICT*)&M3Ug_o8RLEoMfwTZFGJH&(@#TidbA;|iy^!|GU3tH{%EZnug>#AB_Ft(W z;JWyDPBwDub3+0q0>c;S4m(gJ{uyy3c68f}ph0B;((k5ZbOuD;@G-SXQ)BgR?tDp$ z?r!xy;H!-qO~J=M5#u}up~Zju7;Pms2A@e6X{3k+nU^cx=6<9PNj~iad3sOTE}3SQ z{=}zw6;68hhBq?`v`LHKi&zk~4BOIXNr!>xCk=v-Ty7Xv(jq`_h4E{m%0!s~k9fr2 zP`{(E&M$KnLM+HrcHFb)cIKKbeUdC1-5jyc0SOud-WImGI&NgSkA z;9mx<-g8a&oc34yVv9_y+X5u5()uC-LT~R^RMvO=!|Yf$U~y`Ytke!gQ}u9g&i`}Xa( z?kIv+0A0U5zI7u3Sz@MX0%&R9mF_rktV5&6*DpBTX6AUC2p&%yg_5)Iu46|lpw-0p z$a3J`EeC4`UK?BxPc9g?lqzWpdczwOU28WSL`57juIQ{g&=cx>UsDA@xAwr`Ma%PU z@e%oPiloKB?%GqX-24s-!m5|XIN1%Iq`dl1NbL?BPPO`(_ zkh*M*URjy({p9^A^QwuJP)+}PjtAq;(;&rR5~%}R^Oep^snYRA9>_j7K=SMgb}@%| z$m0*>6$e*Eqk?o1IJNidbA;K$m|dZLqm>c(%EbzesWzwxqH!dost9mI$Lge&%oT8c zl(BW>R%4xEA>(iEs0mW^PE}j#xdyysSj=asY>swq29L~hsu(LC+$ntw$L?``K#RJ4oy^YMP z(al?vHwJbqN0ct-_l)(EPi<=L`_@gB`4W{OBmm@jE>;{mV!NL4KMHe}F|VZ2Pp+fG zHwB^oX;BDxe`Vbm8LFCm$t!(t)}e1k>uJ&Zm{SV;?#<7SSwO$mX0GlxM%&OkH62G5 zdU8p88;?Gn0jC2+rYO$e({h^=cR<} zW56B%L0UoeN<0F+AVULapQs@03gSA_qHdj5f8uyu+%%z&U!M(4{(AI}cy?8rXyW0G zMP@lXyK_@M$KO`ic6Cx){9L#q zu+?&?-+iHZB(#qR)<2{?yo`gruka9{*>0b|sU`9gu zD}}PFC~KFh1=TstaJ-?o>(9tbt}9FjoOFH=z)ea?(oJ=-1FONwn5pX4=ZFc;><=J;sB+-e)$! z<&v!%(N4jwq~JN5Kgls0JAHe!{JQeQw%BhF4WjI-6{bE5n2U&8J4vr5jtM^SU2_B# z;Ww{HYhjaFiT3ftbHJCJn1)v-O>G+=l;Ki##gcoENd>k-9!FKSw9qQ=jS&MrL`O9h z>YxbKHL;l|H;Cn4HUyd4fp`DGLh@vVv3L&XsXv9->+<`4O4Q@?eb;{dE#S8bZ`+M6 zLP^;aLucfFkE&e}6x?Q&F)zY3m05+}()Z+Ej=jtZX_ zcDICn&-T*|6*IZP4YLC~flT}fg`|(I!U_15t93mM>Hl*A=Olag2mx=Zwb|&C%(1$> ziYsy{;Oiis<`($EqQ;>oX^DpQ7pI4EIj<0KZ*hFZH~Sx0z08bzZ{_O%CP!{GO|`4f zX+R8=8BtpOpt$D-o2qbuY>?S94x^nH(2;px{kNxHfFxBsXwUB0SYMI`p92P3{-Sk_ zXm9J`sQMCdg~|^oQ%D;5BD1^Ka(C1#ka5;7RSAN^f1)~2f@-}t?QyDAQKumfI<=wj zAn$(6ZB(}c^j|($Q>l@{GW8%(A?8PJr-9D-P36?5N@vE^S~U3TVR|$!^oDMbP?mtL ziE<~va;GX=fd}hv$v=tu*TVpEzadT2cx4nM`IZ!*FFm8o!K#AX+MRTZFMj;+P?9pd zM}z-AU6G)r&0uL^g0)`Fe()HXs01rDR3cr5GUH_Ja1wCqW;sz2nCvZTyJM zHK!S2F&uNHQ2caahAW~@03GUy#oucSYX1}{chTCZw?Sp&5Ti+IrSnmdTvnboYF4Sw z!D=dzS+8Fug7o#{nD4#mGJ8+%Zj#w-sdWKBr{__TDrn}WC>2fNmSxCztG~Onv7SFX zsvFEtf07o7N2d-Jn#5iFtDO5y_(gD!Furl6iR83xL`S)PfoerXp)a`3LU8l+gvpQ9 zBcUz%W(G@#=H9IMHH_JcVXa>q-JfkvlxJ*?6~)a}l$`>p?jdlZFK89;Es)lf+;6%Z562E>`)Kb;r@$FDh?2n0yzR}&@Yd0bxyFy7a$EX-<`Z}_$nD~S?HFeECSAt z%&Dt#5{{c4DxjssjE}r{-Eov9>$@JmIbNFPI##IuX{!(3o4Oou+=KmJ zQD@Z!>{$Hz*|wVn;6>0Qn)=jGheQfk-MgO=jfyyPI5hAkz^{213~1R9VA|Tvo(tN5 zA1~+&O{xINmk!w56G*P-Au-t2Fl!Ghwfq&QmwTY75EN(l-x5?-w3; z?kN*A3-~K>>zVcV$=7;kR-{F>MWYyKuL{uVFoFXM`T{Q5I5_sxyCR&C6(PPCx%nkN zf-ky)o+D}C-iHR;6LkL(e+fyfNOZ>Ujx)q6pW96YDoQE&u4}4?QKQn@UZ-c**))6R zxX!#Ourc}B`O_c;#pwwa8>2rAqNq`gm3hyR&zZ|xX&&{kK(8fefm0kcht&8h9dU}K z(XNOH!A~Ku*=ngoWqneC14M8xk9jl^rC2gar^CSG{BQ=ym?v%`>9(eHL6a&G(9@XR zxO@R}_s1CQRanyTqOQD(k&B6o3vy$n@W?0P%SlpE73N=!L1t})wW%W`h2wn#8jE<(B05_{`j4cGO5#Vs{j%oJ& z=b#jat1+6?Sh<3(m@4IHXPc5tBe1YzqjYNBx97?GeIvlqg!a7qKEDtj!m&G0_wWLS zN6*ITRt|~{t^ne7i%!DhBCf|#Fxx8x=r{|`@=PbMw%_R?+Kzb*3W1`yOvf4YupBeH zj?m*Sl73-%TPyTrCC&+)F>s$j3U>J!Ac~Lg;lFHXknr0qd6osB?X>m6EgOI(=6}S< zq;U1fk-fBymtIT0KRWR6m|1Xt3!u|A#e1K+>>lWilXjn~5(P}Ww%qh}F3zjVfg%}Z zVaW(z=J}mHT(VZRhq$rD#9_Pvgl9=i16F_Nxo8i|dr6db3SH9m_B3V~I)Hy#d zF(QqkghkQ8fzrjbEc@919UBXQPAYi`-FU);n=~&H50(zc#gxO(MvMS!Ilgz@- z(iVb3x)I@PZ2>!V?^*ZoW3--JGp_(_bA_btS#p8uV#uz7-vHk;rl#$W={!?(tC9Ew zzflFxkhlHmqo6c6Rn41u=5diJ8+^qg3FIXAe{!f&4E>(u0~xZ%S203f|C_IU5umui zTD?>RYA+cn1~iJHsv8>{=Cg6>{;6!u_vWS=T*lsK1^C@O2{-iPgN~0 zDNj1pLmDWg7WD(d&e_P{t*)rSZH2Pa(kvh@l5OI5hIjz(GSzH!!}q(U3E&nR4)pIo zQJy-I~ovBnczn671hDb|V_~xA9jg^dV`7ia9;TC@KD0GCdG8P_u`FOxrQsaY6_+gA z$0*b`)DrLubfZoc1}e(;fH!?ltX`}0C)<8J@lMugn4j{iNF(9R1Go$GLz1D2@1u2tOZw(wM_VIN zWeZbFcoM$i+x&JAHv2jh?o3Dg9z#ztiYoel7QPclg{bTGe~Ec>eq@*Yii-Bg;PwZ6 z&^sqMF=iFr=sdzc_^tv(;ev$`r7FMg!;%!6-J6s(;^`$%@h-3Wb!dave5xV>%Bxef zzGJvbr%qyGk``{c{V>%f@axE6eFbO!_wvgnipw|}o zKc{VXJpa!tC+#R@{99}DmMN^#M+<8UJ{m?_KU#?53kWn)*~)RkML;_I+H||65Y27+peQ-{c`ivt(G!x2PlkZ~je#@c%X)JlEQwz;_(In2 z)nR^=O|_w0HtA60*c`K6=Kj#X6K>@~hRV;CfTcsv0oizZc{VdHv@S6L7(T_4;K1?6 zfq9*L5HzL?N#`*&Ir18 zS%96{Y}ZCknBpp2W3H~QK+sflI^3EpNe$^cyr2AXsx5FgvrB)rO`$LgbnX;C25xQi z#q=v2m!P4p=W4AN9)e2C3u?ua{N7=Poa>7t(l=b-?WL`@Pw2sWd$=qhvS49?b{X3T z+=!Ckv6ynn=Qilc`$Jrvs>v=PTR+Z9;V$Lw}R8 zoBE^Q2;N19aVUni6n37i7b){62tTxWV;A+cGpuc3V4yaYr-MA5T4`@nHJqwtgt5_` zcaWb03f^3RXmbk52?|DdWiwP0Juk|#(E{4%Fh2zdpP&#>;;{K)?(Oq1fbZWx?M4dZ z#8c#jgHHsVJ8I&%Oh!IniEk>w^+{uz@7<)wx$##={DO5sA_Nbq5DTXujlpho9}!-+d1d+KJW8B-wn-;Kr25=#HqRFr5$pK zH!ZQZ9S7&O29h{XI%T9{=Tf)Osi@Jh=%GtWDYiziHa@VKrPRHZJ)DO+f z52izf%h1pLCr__EsF-D0kArk2p-A8g_kZlI3_rei}>^)5}5I{#PU|P-+ z8f#SN!dpFyM^(-_0W9>`oU@FK2X!ogPjZV}@SK6ih0esI#ul`}t?+nRnY)j%B0O%H znS*o&@J9r68xnE~JEZGJq$}EY0YN{zDR&k9M4iK28!XqZqV7EJv$U%D4&~6e11!G> z3@MDD|E+U+SNHB_Bt5;bTg9UQgakTQ9SmNurVUb)FfgDmg}+}SxV1B@d{-nVyoJ>^ zTq#2T=14$Toq6M=*-Yqie}U!eTQTV^$PUm<@1dC%8u{pBVWuarIa}6dnvMwBcFyP% z#{dWafla-L?0N@uFv3LIlh0hJfE?KWujt~n;WJ^^<_z4+UeXZFj>>t1L+)$32N*sH zhEKV{KGgLCAl$8-}V6N#L6)(3!G`hjaG-b6ni0n`U7apoV`alV41i}>-_n-?3CL0tE!}6aP zr^$j#t;Fd>iLeF|X4iwq6XlBb$wHL^_|Wq4j@(#cJM&>7B;Tw>`k_ND_&rSP8cb!R zB@VHj8J_bavkhlGk&&Q3X*kI_%5!llF?3lK>fof8*L^<#Ay>ouB=OhQZieYDrEo`n z;yC5|)F3-Tp7OB8*>KI!{5U{lrmlM15e@t9@qb994bky3s5?RtG2no#0w-rK zyJSfjQSE6onhwaMFyxY>tktQZ#=p`2dl!2E)POHo1LF(^s71|g3DN5S)olx~z1trN z=aci}8ygyq3I3%ZE&cYdrz1vKwoOO%I3JneX?^r`)V9_696|(7~`ot@`|PEg*Ywv)Dp{y z2e9whrz}M#dwF@Kh7hX=;st-42--bf3F!nuDsKNiz(5y`OIWBwV*n3s$-YXsaLY+G zUd_Hsh6J=eTjaO%AC}^iAk4*6X!dInr5Z_mHZDX$g@kJtPA2iN0py^*oMTfmA>Oom zl%}@R3a+UC3+kKBMZLZFEjbB*lDOzgZ}4$H(d5oQ%F%?F(+|hdoba7IWGXe`9o*(Q zaf%u{#FY~1fRhz^x=3)VG)zXmejo%kR3qHgvI@A^S9%2fXuu?#c{I3%clcXjB#c@n zUCXo}##CtGskb^Z(p;h69Mf+x*JGT~qg7O@8cxauN=sH?Fdt$7LxyuQKYJm9Svd`Q zydWLGLYEF#X+uDxyV^e9-t^{;!qU>x2vlVVT(i|QWASFU1c$!z4*Tw+MtgcypL5Ql z%YoN%`jDC(75JJS`RC|e;&cKqy?YcLr%J4|RAFI4Xkj;#$i0$1Af@0`LkFqKRelE8{)Wh^k#QYt;KW2PW+QnL;xv~xjDinc3 zz0Vk$e3gj%m@v8&V1MrIiig^~lIPr$z47VFu(KXus)zn?GtDz3w=<0iOY_QA*YCVx z&uqpx;BHdH=A+fxx@)leusRPfl;{A+npmeOR8oD38RoIM>3D-AFM z%4t&6(pRSSH9Ljt;+CQHIbS`kZK78A$&0|wIU%aQl~7Cbp}KN0DSgRiOHUS929XDQ zOx?-G;ZAU&W_$ys<8zkI({q(N;54mplWj}OxOOW8is0g)Cl85vBY7<>6!V}V{Kaq{qS zC&1SV_Fd!ILa}7S4vhaT%mDX;#5tXeLX{Y#0^Q-C3L!}{*i^W`5Bdg6cOCPZb3Ms2L z$|<|Kd}Cc5`aCdo&7IC|o?tdan=DD{{%Y*ch~xL_=N4)n+o`hW1aX$eg9jS9zhy>) zs$|CG@)?dq4*r+`(pV@XdfP=MT-Y${0)Vb?=b{jiF9GwRHZ`%IK*7f%XEw^8<<+RR z;NX9JonGZj$mVIjs;#m5M9%m&ICm5Zn`Z~LS6d-}RP}nAH4{9Vk9k2z0WgKo`<6#1 zzeEjA_f>3fj$~~a4z26t!P}kSbU;7$@_an0dDpLEiwxC}Io-$o<4Pj%&nCkM@Q$$+ zegF ziWD{Jf_7TIHvF4mXKb`w?5m-(cs}*Rvvp??NKu=p;T|L{8z?3#CY4Mu?0W5Z&1d<57iS zJl?)I6^^5*UioCv2==cjeDTe zz>JltX*|FW?ScO&wX;3*Ei1FtEd1yRuWf*x)*OjgFaZ3k?3y$9iV1EeUg^Ut!G2x5 z`t}qAFNWDRg{I6+O05mGUqeatMvLznmEEQuGFCvVAp4>8>P0ot;4%m2BCAvBT$;in z{?0o|(GLwE2TC^RmBjgRjLChv&#jfah_}f-n|}JEoapmJaioY3QddDK5VjTbB|U3K z%rC=?MY-!B${j9L2?eBARG=;Pq0H!FcH%nnXJskr1xBETw9z$z40$X51Q-VkimS83 z8&3=OzM@&TGe6gdncbyxRp^bb{}RF{pf>W>Wr)5cHVaKw?=(zfD|p@MqYJ={#G?`$ zbOxD(#bC=kcmX)DQZ_I7n?e*1Y$ zQe0fzle31QtQnzYN}>nYePM^??PA6ug=H5J_kSLO@(c9$9KvYyKr)cj9KVrSz1JuQ zvVsvYT}rTv>ZMJlto%%!Lj{HH0A&J^&dovov_5~oJ81s;HjwcIR7=8lrdaOVG0ODnl1`4DuZlq!ZzF$xH^Po%- zyNC+NB)z78KjK;~jlSC$n{0sp_khf`z_ljJHFzQ3uCId&)^UBDP|WJ?M-NuJ!gR~1 z+6Mc74FH-lNP+(UqW^(CN)fO#5XJYn@{(@+Pj`=#zVZEY4;q%FC1Wi2+ J>d6ZM{{{9fiP!)D literal 0 HcmV?d00001 diff --git a/easyPR-doc/test/main.md b/easyPR-doc/test/main.md new file mode 100644 index 00000000..e69de29b diff --git a/easyPR-doc/util/Kv.md b/easyPR-doc/util/Kv.md new file mode 100644 index 00000000..fd01d8f6 --- /dev/null +++ b/easyPR-doc/util/Kv.md @@ -0,0 +1,53 @@ +### Class Kv + +#### Properties + - [`std::map data_`](#data) + +#### Functions + - [`Kv()`](#Kv) + - [`void load(const std::string &file)`](#load) + - [`std::string get(const std::string &key)`](#get) + - [`void add(const std::string &key,const std::string &value)`](#add) + - [`void remove(const std::string &key)`](#remove) + - [`void clear()`](#clear) + +*** + + +#### std::map< std::string,std::string > data_ + - 存储了`map< string string >`类型的数据 + +*** + + +#### Kv() + - 无 + + +#### void load(const std::string &file) + - 参数为`file`,即要读入的文件名 + - 解析之前会先将`data_`清空,即调用`this.clear()` + - 内部解析文件,文件的存储格式都为`string(空格)string\n` + - 将`space`前的`string`存储在`data_`的第一个`string里`,将空格后的`string`存储在`data_`的第二个`string`里 + + +#### std::string get(const std::string &key) + - 参数为在`data_`中的`key` + - 返回值为`key`对应的`value` + - 当`value`不存在时,会打印`[Kv] cannot find ${key}`,并返回`""` + + +#### void add(const std::string &key,const std::string &value) + - 参数为`string key`和`string value` + - 在`data_`中添加`key:value` + - 如果在`data_`中已经存在`key`时,打印`[Kv] find duplicate: %s = %s , ignore\n` + + +#### void remove(const std::string &key) + - 参数为`string key` + - 删除`data_`中`key`为`string key`的值 + - 如果没有这个`key`,打印`[Kv] cannot find ${key}` + + +#### void clear() + - 执行`data_.clear()`,清空`data_` diff --git a/easyPR-doc/util/Utils.md b/easyPR-doc/util/Utils.md new file mode 100644 index 00000000..46975b28 --- /dev/null +++ b/easyPR-doc/util/Utils.md @@ -0,0 +1,84 @@ +### Class Utils + +#### Properties + - + +#### Functions + - [`static long getTimestamp()`](#getTimestamp) + - [`static std::string getFileName(const std::string &path,const bool postfix = false)`](#getFileName) + - [`static std::vector splitString(const std::string &str,const char delimiter)`](#splitString) + - [`static T min(const T &v1, const T &v2)`](#min) + - [`static std::vector getFiles(const std::string &folder,const bool all = true)`](#getFiles) + - [`static void print_str_lines(const char** lines)`](#print_str_lines1) + - [`static void print_str_lines(const std::initializer_list &lines)`](#print_str_lines2) + - [`static void print_file_lines(const std::string &file)`](#print_file_lines) + - [`static unsigned int levenshtein_distance(const T &s1, const T &s2)`](#levenshtein_distance) + - [`static bool mkdir(const std::string folder)`](#mkdir) + - [`static bool imwrite(const std::string &file, const cv::Mat &image)`](#imwrite) + - [`(private)static std::size_t get_last_slash(const std::string &path)`](#get_last_slash) + +*** + +*** + + +#### long getTimestamp() + - windows: + - 返回`cv::getTickCount()`,即系统启动到当前的时间 + - `cv::getTickCount()`经常用于`opencv`下计算代码运行时间 + - linux: + - 使用linux时间结构体`timespes`,`CLOCK_MONOTONIC`为系统启动时间 + - 以毫秒的形式返回系统启动到当前的时间 + - macOS: + - 使用了1970/1/1来代替计算时间 + + +#### std::string getFileName(const std::string &path,const bool postfix = false) + - 输入`string &path`:文件名,`bool postfix`:是否需要后缀 + - 返回文件名(是否有后缀取决于postfix的值) + + +#### static std::vector< std::string > splitString(const std::string &str,const char delimiter) + - 输入`string &str`和`const char delimiter`分隔符 + - 返回`vector`,存储了用分隔符分割的字符串 + + +#### static T min(const T &v1, const T &v2) + - 返回`(v1 < v2) ? v1 : v2`; + + +#### static std::vector< std::string > getFiles(const std::string &folder,const bool all = true) + - 输入`string &folder`:目录,`bool all`:是否搜索子目录 + - 返回`vector`:文件的集合 + + +#### static void print\_str\_lines(const char** lines) + - 打印行 + + +#### static void print\_str\_lines(const std::initializer_list &lines) + - 打印行 + + +#### static void print\_file\_lines(const std::string &file) + - 读取`file`中的内容,并打印 + + +#### static unsigned int levenshtein_distance(const T &s1, const T &s2) + - 输入两个`T`类型`&s1`和`&s2` + - 返回这两个数据的编辑距离(Levenshtein Distance),即从s1转化到s2需要的最小步数. + + +#### static bool mkdir(const std::string folder) + - 输入`string folder`:需要创建的目录 + - 返回:`bool`,表示创建成功或者失败 + + +#### static bool imwrite(const std::string &file, const cv::Mat &image) + - 输入`string &file`:路径,`Mat &image`:图片 + - 输出:是否存储成功 + + +#### std::size\_t get\_last\_slash(const std::string &path) + - 输入一个`string &path`,代表了路径 + - 返回路径`/`最后的文件名的位置 diff --git a/easyPR-doc/util/program_options.md b/easyPR-doc/util/program_options.md new file mode 100644 index 00000000..57eabf53 --- /dev/null +++ b/easyPR-doc/util/program_options.md @@ -0,0 +1,157 @@ +### Class: + - [Class Row](#Row) + - [Class Subroutine](#Subroutine) + - [Class Generator](#Generator) + + +### Class Row + +#### Properties + - `(private)`: + - [`bool require_value`](#require_value) + - [`std::string option_short`](#option_short) + - [`std::string option_long`](#option_long) + - [`std::string default_value`](#default_value) + - [`std::string description`](#description) + - `(public)`: + - [`enum Field { kShort, kLong, kDefault, kDescription }`](#Field) + - [`typedef std::initializer_list Order`](#Order) + +#### Functions + - `(public)`: + - [`Row()`](#Row) + - [`inline std::string oshort() const { return option_short; }`](#oshort_get) + - [`inline std::string olong() const { return option_long; }`](#olong_get) + - [`inline std::string value() const { return default_value; }`](#value_get) + - [`inline std::string desc() const { return description; }`](#desc_get) + - [`inline bool required() const { return require_value; }`](#required_get) + - [`inline void oshort(const std::string& oshort) { option_short = oshort; }`](#oshort_set) + - [`inline void olong(const std::string& olong) { option_long = olong; }`](#olong_set) + - [`inline void value(const std::string& value) { default_value = value; }`](#value_set) + - [`inline void desc(const std::string& desc) { description = desc; }`](#desc_set) + - [`inline void required(bool required) { require_value = required; }`](#required_set) +*** + - +*** + + +### Class Subroutine + +#### Properties + - `(public)`: + - [`typedef std::vector Usages`](#Usages) + - [`typedef std::initializer_list TemplateValue`](#TemplateValue) + - [`typedef std::vector TemplateValues`](#TemplateValues) + - `(private)`: + - [`Usages usages_`](#usages_) + - [`TemplateValues templates_`](#templates_) + - [`const char* first_line_`](#first_line_) + - [`const char* description_`](#description_) + - [`std::string name_`](#name_) + - [`std::string template_str_`](#template_str_) + - [`Row::Order order_`](#order_) + +#### Functions + - `(public)`: + - [`Subroutine()`](#Subroutine1) + - [`Subroutine(const char* name, const char* description)`](#Subroutine2) + - [`inline void add_usage_line(const Row& row) { usages_.push_back(row); }`](#add_usage_line1) + - [`inline void add_usage_line(const TemplateValue& row) {templates_.push_back(row);}`](#add_usage_line2) + - [`inline void set_first_line(const char* line) { first_line_ = line; }`](#set_first_line) + - [`inline void set_description(const char* desc) { description_ = desc; }`](#set_description) + - [`inline void set_template(const char* tstr, const Row::Order& order)`](#set_template) + - [`inline std::string to_string()`](#to_string) + - [`inline std::string get_name() const { return name_; }`](#get_name) + - [`inline const char* get_description() const { return description_; }`](#get_description) + - [`inline const char* get_first_line() const { return first_line_; }`](#get_first_line) + - [`inline Usages::iterator begin() { return usages_.begin(); }`](#begin) + - [`inline Usages::iterator end() { return usages_.end(); }`](#end) + - [`inline size_t size() { return usages_.size(); }`](#size) + - [`inline Row& at(size_t i) { return usages_.at(i); }`](#at) + - [`inline const Usages& get_usage() const { return usages_; }`](#get_usage) + - [`inline static const char* get_default_name() { return "EmptySubroutine"; }`](#get_default_name) + - `(private)`: + - [`friend std::ostream& operator<<(std::ostream& out, Subroutine& subroutine)`](#operator_subroutine) + - [`void print_with_row(std::ostream& out)`](#print_with_row) + - [`void print_with_template(std::ostream& out)`](#print_with_template) +*** + +*** + +#### Subroutine() + + +#### Subroutine(const char* name, const char* description) + - //need to confirm + + +### Class Generator + +#### Properties + - [`typedef std::map SubroutineCollection`](#SubroutineCollection) + - [`(private)const char kDelimiter`](#kDelimiter) + - [`(private)SubroutineCollection subroutines_`](#subroutines) + - [`(private)std::string current_subroutine_`](#current_subroutine_) + - [`(private)Parser* parser_`](#parser_) + +#### Functions + - (`public:`) + - [`Generator()`](#Generator) + - [`~Generator()`](#~Generator) + - [`Generator& make_usage(const char* first_line)`](#make_usage) + - [`Parser* make_parser()`](#make_parser) + - [`Generator& add_subroutine(const char* name)`](#add_subroutine1`) + - [`Generator& add_subroutine(const char* name, const char* description)`](#add_subroutine2) + - [`std::map get_subroutine_list()`](#get_subroutine_list) + - [`inline std::string to_string()`](#to_string) + - [`inline Generator& operator()(const char* option, const char* description)`](#operator1) + - [`inline Generator& operator()(const char* option, const char* default_value,const char* description)`](#operator2) + - [`inline Subroutine& operator()(const char* name)`](#operator3) + - [`inline Generator& make_template(const char* template_str,const Row::Order& order)`](#operator4) + - (`private:`) + - [`(private)inline Subroutine* get_subroutine()`](#get_subroutine) + - [`(private)friend std::ostream& operator<<(std::ostream& out, Generator& generator)`](#operator5) + - [`(private)bool add_usage_line(const char* option,const char* default_value,const char* description)`](#add_usage_line) + +*** + +#### typedef std::map SubroutineCollection + - 用于存储`[string:Subroutine]`的`map`:`SubroutineCollection` + + +#### (private)SubroutineCollection subroutines_ + - `SubroutineCollection`类型,用来存储`Subroutine`集合 + + +#### (private)std::string current_subroutine_ + - 表示当前子程序 + + +#### (private)Parser* parser_ + - 在构造方法中被初始化为`nullptr` + +*** + +#### Generator() + - 向`subroutines_`添加一条["EmptySubroutine",new Subroutine("EmptySubroutine","")] + + +#### Generator& make_usage(const char* first_line) + - 获取当前`current_subroutine_`在`subroutines_`中对应的`Subroutine` + - 将上述的`Subroutine`的属性`first_line`设置成输入的参数`first_line` + - 返回`*this` + +#### Generator& add_subroutine(const char* name) + - 执行`add_subroutine(name, "")` + - 执行`return *this`,返回当前`Generator` + + +#### Generator& add_subroutine(const char* name, const char* description) + - 查找`subroutines_`中有没有子程序`name` + - 如果有,则`return *this` + - 如果没有,则将`[name:new Subroutine(name,description)]`添加到`subroutines_`中 + + +#### inline Subroutine* get_subroutine(){return subroutines_.at(current_subroutine_);} + - 输入`current_subroutine_` + - 返回`subroutines`中`current_subroutine_`对应的`Subroutine`,即当前指令