easyPR源码解析之plate_locate.h
从今天开始,准备一点一点的啃代码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 的任何成员函数访问。
- private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问.
- protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
- 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该类表示平面上的旋转矩形,有三个属性:
- 矩形中心点(质心)
- 边长(长和宽)
- 旋转角度
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相关推荐
- easyPR源码解析之chars_segment.h
chars_segment.h用于从已经通过SVM判别得到的车牌区域中将车牌的字符分割开,用于下一步的ANN字符识别. namespace easypr { class CCharsSegment { ...
- easyPR源码解析之ann_train.h/config.h
ann_train.h源码定义一个 AnnTrain类,该类继承自ITrain类(在train.h文件中): #include "easypr/train/train.h" #in ...
- easyPR源码解析之chars_identify.h
在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符.本节主要介绍 ...
- easyPR源码解析之plate_judge.h
#ifndef EASYPR_CORE_PLATEJUDGE_H_ #define EASYPR_CORE_PLATEJUDGE_H_ #include "easypr/core/plate ...
- 【darknet源码解析-24】shortcut_layer.h 和 shortcut_layer.c 解析
本系列为darknet源码解析,本次解析src/short_layer.h 与 src/short_layer.c 两个.在yolo v3中short_layer主要完成直连操作,完成残差块中的恒等映 ...
- H.264压缩技术之视频基础(foundation of learning video)——Matlab源码解析
前言 为了后续能更好的理解,I帧编码与P帧编码,所以笔者先对数字视频中的一些基础概念进行铺垫.后续比较复杂的帧内预测,与帧间预测理解起来就会相对容易些. 关于Matlab中h.264的main函数部分 ...
- 谷歌BERT预训练源码解析(二):模型构建
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...
- Colly源码解析——结合例子分析底层实现
通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...
- libev源码解析——监视器(watcher)结构和组织形式
在<libev源码解析--总览>中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置.由于这些变量在多线程下没有同步问题,所以我们将问题简化,所提到的变量都是线程内部独有的,不 ...
最新文章
- LaTeX 修改参考文献的方法
- javascript装饰者模式
- Java学习笔记十五
- vue 前端设置允许跨域_web 前端的一些小问题
- 为什么你跟高手有差距?因为他们会在假期里读这5本书
- 分布式选举协议:Paxos
- python之路-08-集合
- t–sql pl–sql_SQL Server处理器性能指标–第1部分–最重要的CPU指标
- java程序员年龄大了怎么办,互联网行业“中年”危机
- java -虹软Caused by: java.lang.UnsatisfiedLinkError: Can‘t load library: **\WIN64\libarcsoft_face.dll
- ajax提交与上传文件同步
- python基础知识01-数据类型和序列类型
- MyBatis #{ } ${ }
- vmware workstation 14 密钥
- 孔夫子旧书网接口:实现输入ISBN获取图书信息
- Manacher算法(马拉车算法)
- 现在很火爆的外卖返利小程序源码免费分享一套源码
- sklearn实战之决策树
- 基于multisim的语音放大器电路设计
- 计算机专业的英文简历范文带翻译,[网络工程师个人英文简历模板]英文简历范文带翻译...