从今天开始,准备一点一点的啃代码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,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double 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.75
CPlateLocate::CPlateLocate() {
m_GaussianBlurSize = DEFAULT_GAUSSIANBLUR_SIZE;
m_MorphSizeWidth = DEFAULT_MORPH_SIZE_WIDTH;
m_MorphSizeHeight = DEFAULT_MORPH_SIZE_HEIGHT;
m_error = DEFAULT_ERROR;
m_aspect = DEFAULT_ASPECT;
m_verifyMin = DEFAULT_VERIFY_MIN;
m_verifyMax = DEFAULT_VERIFY_MAX;
m_angle = DEFAULT_ANGLE;
m_debug = DEFAULT_DEBUG;
}
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.142857
float aspect = m_aspect; //3.75
// 设置最大最小面积,删除不满足条件的矩形区域,真实车牌尺寸: 136 * 32,
int min = 34 * 8 * m_verifyMin; //m_verifyMin=1
int 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))//如果矩形区域不满足两个条件中的任意一个,它都不是车牌区域,返回False
return false;
else
return 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)放的是绿色车牌