从今天开始,准备一点一点的啃代码easyPR项目。

我们先从libesypr文件下的源文件/core/plate_locate.cpp开始:

plate_locate.cpp文件中包含的头文件如下:

#include "easypr/core/plate_locate.h"
#include "easypr/core/core_func.h"
#include "easypr/util/util.h"
#include "easypr/core/params.h"

我们一个一个看,首先是plate_locate.h头文件,头文件中实际是定义一个CPlateLocate类,在类中声明类的函数,但没有定义,定义放在了plate_locate.cpp中。一般类或者结构体的声明放在头文件中,把定义实现放在源文件中,这样的文件结构便于你维护代码。如果你的程序出错了或者有什么功能需要添加修改之类的,发现main函数好长好长,要找修改的地方确实不容易。实例参见文末1

plate_locate.h代码如下:

#ifndef EASYPR_CORE_PLATELOCATE_H_
#define EASYPR_CORE_PLATELOCATE_H_

#include "easypr/core/plate.hpp"

/*! \namespace easypr
    Namespace where all the C++ EasyPR functionality resides
*/

using namespace std;

namespace easypr {

class CPlateLocate {
 public:
//声明构造函数,因为没有加"{}",就只是声明详细的构造函数定义在plate_locate.cpp中;如果加"{}",就是定义空构造,例如CPlateLocate(){};
  CPlateLocate();

//以下皆为成员函数声明,类的成员函数的原型要写在类体中,原型说明了函数的参数表和返回值类型。而函数的定义一般在类外面(在这里,成员函数的详细定义,在plate_locate.cpp中),也可以直接在类内部定义。前者与普通函数不同的是,实现成员函数时要指明类的名称,具体形式为:返回值类型 类名 ::函数成员名(参数表){函数体};而后者一般为一些短小的函数(5行以内),也就是内联函数。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。(注意区分声明和定义的差别)参见文末2!

//sobelFrtSearch函数求外接矩形框,加上尺寸判断,安全旋转判断,调用sobelOper,verifySizes,calcSafeRect
int sobelFrtSearch(const Mat& src, std::vector<Rect_<float>>& outRects);

//sobelSecSearch调用sobelOper函数,得到二值图,然后求轮廓,得到外接矩形框
int sobelSecSearch(Mat& bound, Point2f refpoint,std::vector<RotatedRect>& outRects);

//也是输出矩形框,与上面的类似
int sobelSecSearchPart(Mat& bound, Point2f refpoint,std::vector<RotatedRect>& outRects);

//deskew偏斜扭正过程,调用calcSafeRect函数,其声明在core_func.h头文件中,用于实现一个安全的计算最小外接矩形Rect;调用rotation 函数进行车牌旋转,调用isdeflection完成偏斜判断与斜率计算,调用deleteNotArea函数,采用颜色特征删除不是车牌的区域,,参见车牌识别EasyPR(2)——车牌颜色定位与偏斜扭转
int deskew(const Mat& src, const Mat& src_b,std::vector<RotatedRect>& inRects, std::vector<CPlate>& outPlates,bool useDeteleArea = true, Color color = UNKNOWN);

//完成偏斜判断与斜率计算
bool isdeflection(const Mat& in, const double angle, double& slope);

//sobelOper对输入图像进行高斯平滑,Sobel边缘检测,提取垂直边缘,进行自适应阈值,并进行闭操作 
int sobelOper(const Mat& in, Mat& out, int blurSize, int morphW, int morphH);

//旋转车牌
bool rotation(Mat& in, Mat& out, const Size rect_size, const Point2f center,const double angle);

void affine(const Mat& in, Mat& out, const double slope);//仿射变换

//plateColorLocate颜色定位,分为蓝,黄两种,调用colorSearch函数,调用deskew偏转矫正
 int plateColorLocate(Mat src, std::vector<CPlate>& candPlates, int index = 0);

//调用sobelFrtSearch函数,又调用sobelSecSearchPart函数???????
  int plateSobelLocate(Mat src, std::vector<CPlate>& candPlates, int index = 0);

//sobelOperT函数好像和上面的sobelOper函数是一样的功能
  int sobelOperT(const Mat& in, Mat& out, int blurSize, int morphW, int morphH);

//文本定位车牌  ,调用mserSearch函数 
 int plateMserLocate(Mat src, std::vector<CPlate>& candPlates, int index = 0);

//颜色搜索方法,colorSearch函数中调用了colorMatch函数,该函数声明在core_func.h头文件中,用于匹配蓝色黄色车牌,原理参见:车牌识别EasyPR(2)——车牌颜色定位与偏斜扭转
  int colorSearch(const Mat& src, const Color r, Mat& out,std::vector<RotatedRect>& outRects);

//文字搜索方法,mserSearch函数声明在core_func.h ,定义在core_func.cpp,调用mserCharMatch函数,mserCharMatch函数中又使用新的类Ptr<MSER2> mser,MSER2是从OpenCV中的MSER改进得来的,参见文末8,算法原理参见:车牌识别EasyPR(5)——文字定位

int mserSearch(const Mat &src, vector<Mat>& out,  vector<vector<CPlate>>& out_plateVec, bool usePlateMser,     vector<vector<RotatedRect>>& out_plateRRect, int img_index = 0, bool showDebug = false);

//车牌定位综合,调用plateColorLocate/ plateSobelLocate/ plateMserLocate ,将三种方法找到的车牌区域进行合并在一起 ,不用担心有很多重合区域,我们采用非极大值抑制,根据置信度去除重叠框   
int plateLocate(Mat, std::vector<Mat>&, int = 0);
int plateLocate(Mat, std::vector<CPlate>&, int = 0);

//通过判断矩形框的尺寸判断其是否为车牌候补,参见文末7
bool verifySizes(RotatedRect mr);

//用来修改构造函数的值,param为False,使用默认构造值,为True使用自定义的值,参见6
void setLifemode(bool param);

//以下函数直接在类中定义,属于内联函数,之所以定义内联函数,是因为内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存,如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间占比很小。若代码执行时间很短,则内联函数就可以节省函数调用的时间,可以看出下列的内联函数的函数体只有1行,运行较快,定义为内联函数,可以缩短函数调用时间,同时付出的内存代价很小。from:https://www.cnblogs.com/shijingjing07/p/5523224.html

inline void setGaussianBlurSize(int param) { m_GaussianBlurSize = param; }
  inline int getGaussianBlurSize() const { return m_GaussianBlurSize; }

inline void setMorphSizeWidth(int param) { m_MorphSizeWidth = param; }
  inline int getMorphSizeWidth() const { return m_MorphSizeWidth; }

inline void setMorphSizeHeight(int param) { m_MorphSizeHeight = param; }
  inline int getMorphSizeHeight() const { return m_MorphSizeHeight; }

inline void setVerifyError(float param) { m_error = param; }
  inline float getVerifyError() const { return m_error; }
  inline void setVerifyAspect(float param) { m_aspect = param; }
  inline float getVerifyAspect() const { return m_aspect; }

inline void setVerifyMin(int param) { m_verifyMin = param; }
  inline void setVerifyMax(int param) { m_verifyMax = param; }

inline void setJudgeAngle(int param) { m_angle = param; }

inline void setDebug(bool param) { m_debug = param; }
  inline bool getDebug() { return m_debug; }

//const只是声明一个常量,static表示该常量是静态变量,只要程序不结束,该变量从声明的地方开始,一直在内存中存在不释放,对于class 中static const int 的变量,可以在类中进行初始化,并省去外部的定义(const static 与 static const 意义相同,)参见文末3!
  static const int DEFAULT_GAUSSIANBLUR_SIZE = 5;
  static const int SOBEL_SCALE = 1;
  static const int SOBEL_DELTA = 0;
  static const int SOBEL_DDEPTH = CV_16S;
  static const int SOBEL_X_WEIGHT = 1;  //默认对水平方向求导的标志位,可得到垂直边缘,值传入addWeighted函数中
  static const int SOBEL_Y_WEIGHT = 0; //默认不对垂直方向求导,因为车头太多水平边缘。
  static const int DEFAULT_MORPH_SIZE_WIDTH = 17;  // 17
  static const int DEFAULT_MORPH_SIZE_HEIGHT = 3;  // 3
  static const int WIDTH = 136;
  static const int HEIGHT = 36;
  static const int TYPE = CV_8UC3;
  static const int DEFAULT_VERIFY_MIN = 1;   // 3
  static const int DEFAULT_VERIFY_MAX = 24;  // 20

static const int DEFAULT_ANGLE = 60;  // 30
  static const int DEFAULT_DEBUG = 1;

//protected:只允许子类及本类的成员函数访问,不能被该类的对象访问,参见文末4,查看plate_locate.cpp中的函数实现会发现,下面的变量是用于定义构造函数。参见文末5
 protected:

int m_GaussianBlurSize;
  int m_MorphSizeWidth;
  int m_MorphSizeHeight;
  float m_error;
  float m_aspect;
  int m_verifyMin;
  int m_verifyMax;
  int m_angle;
  bool m_debug;
};

} /*! \namespace easypr*/

#endif

文末:

1、头文件和源文件

// mesh.h
class mesh{
public:void set_a(int aa);void print_mesh();
private:int a;
};// mesh.cpp
#include "mesh.h"
#include <iostream>void mesh::set_a(int aa){a = aa;
}void mesh::print_mesh(){std::cout << a << std::endl;
}// main.cpp
#include "mesh.h"int main()
{mesh a_mesh;a_mesh.set_a( 5);a_mesh.print_mesh();return 0;
}

from:https://bbs.csdn.net/topics/390880788

2、类中成员函数声明和定义

#include <iostream>using namespace std;class Box
{public:double length;         // 长度double breadth;        // 宽度double height;         // 高度// 成员函数声明double getVolume(void);void setLength( double len );void setBreadth( double bre );void setHeight( double hei );
};// 成员函数定义
double Box::getVolume(void)
{return length * breadth * height;
}void Box::setLength( double len )
{length = len;
}void Box::setBreadth( double bre )
{breadth = bre;
}void Box::setHeight( double hei )
{height = hei;
}// 程序的主函数
int main( )
{Box Box1;                // 声明 Box1,类型为 BoxBox Box2;                // 声明 Box2,类型为 Boxdouble volume = 0.0;     // 用于存储体积// box 1 详述Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0);// box 2 详述Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0);// box 1 的体积volume = Box1.getVolume();cout << "Box1 的体积:" << volume <<endl;// box 2 的体积volume = Box2.getVolume();cout << "Box2 的体积:" << volume <<endl;return 0;
}

from:http://www.runoob.com/cplusplus/cpp-class-member-functions.html

3、static, const 和 static const 变量的初始化问题

const 常量的在超出其作用域的时候会被释放,但是 static 静态变量在其作用域之外并没有释放,只是不能访问。

static 修饰的是静态变量,静态函数。对于类来说,静态成员和静态函数是属于整个类的,而不是属于对象。可以通过类名来访问,但是其作用域限制于包含它的文件中。

const 常量的不变形只是针对与一个对象来说的,同一个类的不同对象的 const 常量的值可以不一样。 如果想让 const 常量在类的所有实例对象的值都一样,可以用 static const (const static),使用方式如下:

1 class A {
2     const static int num1; // 声明
3     const static int num2 = 13; // 声明和初始化
4 };
5 const int A::num1 = 12; // 定义并初始化
6 const int num2;  // 定义

上面两种方式都可以对 const static 常量进行初始化。注意,第 3 行的代码并没有对 num2 进行定义,它只是进行声明。其实这里给了值 13 也没用进行初始化,因为变量必须在定义了以后才进行初始化。

from:https://www.cnblogs.com/xiezhw3/p/4354601.html

4、 public、private、protected

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。成员和类的默认访问修饰符是 private。

4.1、公有(public)成员

公有成员在程序中类的外部是可访问的。可以不使用任何成员函数来设置和获取公有变量的值

4.2、私有成员

     私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。例如在下面的类中,width 是一个私有成员,这意味着,如果您没有使用任何访问修饰符,类的成员将被假定为私有成员,实际操作中,我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。

#include <iostream>
using namespace std;
class Box
{public:double length;void setWidth( double wid );double getWidth( void ); private:double width;
};
// 成员函数定义
double Box::getWidth(void){return width ;}void Box::setWidth( double wid ){ width = wid;}
// 程序的主函数
int main( )
{Box box; // 不使用成员函数设置长度box.length = 10.0; // OK: 因为 length 是公有的cout << "Length of box : " << box.length <<endl;// 不使用成员函数设置宽度// box.width = 10.0; // Error: 因为 width 是私有的box.setWidth(10.0);  // 使用成员函数设置宽度cout << "Width of box : " << box.getWidth() <<endl;return 0;
}

4.3、保护(protected)成员

      保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可访问的。如果我们从父类 Box 派生了一个子类 smallBox,在这里 Box类中的保护成员 width 成员可被派生类 smallBox 的任何成员函数访问。

  1. private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
  2. protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
  3. public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问

from:http://www.runoob.com/cplusplus/cpp-class-access-modifiers.html

5、

const float DEFAULT_ERROR = 0.9f;    // 0.6
const float DEFAULT_ASPECT = 3.75f;  // 3.75CPlateLocate::CPlateLocate() {m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE;m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH;m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT;m_error = DEFAULT_ERROR;m_aspect = DEFAULT_ASPECT;m_verifyMin = DEFAULT_VERIFY_MIN;m_verifyMax = DEFAULT_VERIFY_MAX;m_angle = DEFAULT_ANGLE;m_debug = DEFAULT_DEBUG;
}

6、//用来修改构造函数的值

void CPlateLocate::setLifemode(bool param) {if (param) {setGaussianBlurSize(5);setMorphSizeWidth(10);setMorphSizeHeight(3);setVerifyError(0.75);setVerifyAspect(4.0);setVerifyMin(1);setVerifyMax(200);} else {setGaussianBlurSize(DEFAULT_GAUSSIANBLUR_SIZE);setMorphSizeWidth(DEFAULT_MORPH_SIZE_WIDTH);setMorphSizeHeight(DEFAULT_MORPH_SIZE_HEIGHT);setVerifyError(DEFAULT_ERROR);setVerifyAspect(DEFAULT_ASPECT);setVerifyMin(DEFAULT_VERIFY_MIN);setVerifyMax(DEFAULT_VERIFY_MAX);}
}

7、通过判断矩形框的尺寸判断其是否为车牌候补,返回False,或True

RotatedRect该类表示平面上的旋转矩形,有三个属性:

  1. 矩形中心点(质心)
  2. 边长(长和宽)
  3. 旋转角度
bool CPlateLocate::verifySizes(RotatedRect mr) {                   //校验车牌的尺寸float error = m_error;    //0.9// 中国车牌的一般大小是440mm*140mm宽高比aspect 为3.142857float aspect = m_aspect;   //3.75// 设置最大最小面积,删除不满足条件的矩形区域,真实车牌尺寸: 136 * 32,int min = 34 * 8 * m_verifyMin;  //m_verifyMin=1int max = 34 * 8 * m_verifyMax;  // m_verifyMax=20,自己设置的值,可修改// 设置车牌宽高比率范围,不满足这个条件的,判断为非车牌区域float rmin = aspect - aspect * error;float rmax = aspect + aspect * error;float area = mr.size.height * mr.size.width;float r = (float) mr.size.width / (float) mr.size.height;if (r < 1) r = (float) mr.size.height / (float) mr.size.width;if ((area < min || area > max) || (r < rmin || r > rmax))//如果矩形区域不满足两个条件中的任意一个,它都不是车牌区域,返回Falsereturn false;elsereturn true;
}

8、Ptr<MSER2>在core_func.cpp中使用如下,可以看出改进的MSER2,相比OpenCV的MSER,优点是可以同时输出蓝色车牌和黄色车牌的检测区域。

Ptr<MSER2> mser;mser = MSER2::create(delta, minArea, int(maxAreaRatio * imageArea));mser->detectRegions(image, all_contours.at(0), all_boxes.at(0), all_contours.at(1), all_boxes.at(1));//all_contours.at(0)放的是蓝色车牌,all_contours.at(0)放的是绿色车牌

easyPR源码解析之plate_locate.h相关推荐

  1. easyPR源码解析之chars_segment.h

    chars_segment.h用于从已经通过SVM判别得到的车牌区域中将车牌的字符分割开,用于下一步的ANN字符识别. namespace easypr { class CCharsSegment { ...

  2. easyPR源码解析之ann_train.h/config.h

    ann_train.h源码定义一个 AnnTrain类,该类继承自ITrain类(在train.h文件中): #include "easypr/train/train.h" #in ...

  3. easyPR源码解析之chars_identify.h

    在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符.本节主要介绍 ...

  4. easyPR源码解析之plate_judge.h

    #ifndef EASYPR_CORE_PLATEJUDGE_H_ #define EASYPR_CORE_PLATEJUDGE_H_ #include "easypr/core/plate ...

  5. 【darknet源码解析-24】shortcut_layer.h 和 shortcut_layer.c 解析

    本系列为darknet源码解析,本次解析src/short_layer.h 与 src/short_layer.c 两个.在yolo v3中short_layer主要完成直连操作,完成残差块中的恒等映 ...

  6. H.264压缩技术之视频基础(foundation of learning video)——Matlab源码解析

    前言 为了后续能更好的理解,I帧编码与P帧编码,所以笔者先对数字视频中的一些基础概念进行铺垫.后续比较复杂的帧内预测,与帧间预测理解起来就会相对容易些. 关于Matlab中h.264的main函数部分 ...

  7. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  8. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  9. libev源码解析——监视器(watcher)结构和组织形式

    在<libev源码解析--总览>中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置.由于这些变量在多线程下没有同步问题,所以我们将问题简化,所提到的变量都是线程内部独有的,不 ...

最新文章

  1. LaTeX 修改参考文献的方法
  2. javascript装饰者模式
  3. Java学习笔记十五
  4. vue 前端设置允许跨域_web 前端的一些小问题
  5. 为什么你跟高手有差距?因为他们会在假期里读这5本书
  6. 分布式选举协议:Paxos
  7. python之路-08-集合
  8. t–sql pl–sql_SQL Server处理器性能指标–第1部分–最重要的CPU指标
  9. java程序员年龄大了怎么办,互联网行业“中年”危机
  10. java -虹软Caused by: java.lang.UnsatisfiedLinkError: Can‘t load library: **\WIN64\libarcsoft_face.dll
  11. ajax提交与上传文件同步
  12. python基础知识01-数据类型和序列类型
  13. MyBatis #{ } ${ }
  14. vmware workstation 14 密钥
  15. 孔夫子旧书网接口:实现输入ISBN获取图书信息
  16. Manacher算法(马拉车算法)
  17. 现在很火爆的外卖返利小程序源码免费分享一套源码
  18. sklearn实战之决策树
  19. 基于multisim的语音放大器电路设计
  20. 计算机专业的英文简历范文带翻译,[网络工程师个人英文简历模板]英文简历范文带翻译...

热门文章

  1. 大唐波斯将军 机器人_跑到大唐的萨珊波斯遗民
  2. 2022年软考信息安全工程师考试备考指南
  3. 小汤学编程之JDBC(一)——JDBC概述和快速入门
  4. position:搜索框显示历史浏览记录
  5. android-studio add jar
  6. [INS-20802] Oracle Net Configuration Assistant failed
  7. 基于应用层自身反远程线程注入的研究
  8. 微信小程序学习目录推荐
  9. java基础将字符串进行反转
  10. 【09】Nginx:静态压缩 / 日志切割 / 防盗链 /恶意解析/ 跨域