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,类型为 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 的任何成员函数访问。

  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.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该类表示平面上的旋转矩形,有三个属性:

  1. 矩形中心点(质心)
  2. 边长(长和宽)
  3. 旋转角度
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)放的是绿色车牌

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
改变通道数,代码如何修改public static String[] multiPlateRecognise(opencv_core.Mat mat) { PlateDetect plateDetect = new PlateDetect(); plateDetect.setPDLifemode(true); Vector<opencv_core.Mat> matVector = new Vector<opencv_core.Mat>(10); if (0 == plateDetect.plateDetect(mat, matVector)) { if (matVector.size() > 0) { //字符分割与识别 return new String[]{cr.charsRecognise(matVector.get(0))}; } } return null;public static void main(String[] args) { // 多张车牌图片路径 String[] imgPaths = {"res/image/test_image/plate_locate.jpg", "res/image/test_image/test.jpg", "res/image/test_image/plate_detect.jpg", "res/general_test/京A88731.jpg"}; int sum = imgPaths.length; // 总共处理的图片数量 int errNum = 0; // 识别错误的数量 int sumTime = 0; // 总耗时 long longTime = 0; // 最长处理时长 for (int i = 0; i < sum; i++) { opencv_core.Mat src = opencv_imgcodecs.imread(imgPaths[i]); String[] ret = multiPlateRecognise(src); long now = System.currentTimeMillis(); System.err.println(Arrays.toString(ret)); long s = System.currentTimeMillis() - now; if (s > longTime) { longTime = s; } sumTime += s; boolean flag =false;//是否有一个车牌号识别错误 for (String plate:ret) { if (plate == null) { continue; } String targetPlate = getTargetPlate(imgPaths[i]); if (!plate.equals(targetPlate)){ flag = true; break; } } if (flag) { errNum++; } } BigDecimal errSum = new BigDecimal(errNum); BigDecimal sumNum = new BigDecimal(sum); BigDecimal c = sumNum.subtract(errSum).divide(sumNum,2, RoundingMode.HALF_UP).multiply(new BigDecimal(100)); System.err.println("总耗时:" + sumTime + "ms,平均处理时长:" + sumTime/sum + "ms,错误数量:" + errNum + ",正确识别率:" + c + "%"); } }
06-11

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值