easyPR源码解析之plate_judge.h

#ifndef EASYPR_CORE_PLATEJUDGE_H_
#define EASYPR_CORE_PLATEJUDGE_H_

#include "easypr/core/plate.hpp"
#include "easypr/core/feature.h"//参见文末6

namespace easypr {

class PlateJudge {
 public:

//类名后面加*,表示对应类的指针类型。对应变量值为类指针类型,函数的返回值只一个Room指针,它的定义参见文末2
  static PlateJudge* instance();

//加载模型文件xml
  void LoadModel(std::string path)

  //使用NMS删除重叠的框,参见文末5
int plateJudgeUsingNMS(const std::vector<CPlate>&, std::vector<CPlate>&, int maxPlates = 5);

//,通过调用SVM模型进行判别, 设置车牌分数,0 is plate, -1 is not.参见文末4
  int plateSetScore(CPlate& plate);

 //三个构造函数,定义在plate_judge.cpp

//将输出普通Mat转化为CPlate对象,调用plateSetScore函数求出其score
int plateJudge(const Mat& plateMat);

//输入一组Mat,对其Mat逐个调用上面结构的plateJudge,求出每个Mat对应的score
int plateJudge(const std::vector<Mat> &inVec,std::vector<Mat> &resultVec);

//同上,输入一组CPlate对象,取出它的Mat成员,然后对其逐个调用上面第一个结构的plateJudge
  int plateJudge(const std::vector<CPlate> &inVec,std::vector<CPlate> &resultVec);

 private:
  // singleton,构造函数,参见文末3
  PlateJudge();

  static PlateJudge* instance_;//定义指针

svmCallback extractFeature;//定义回调函数

  cv::Ptr<ml::SVM> svm_;//SVM对象

};
}

#endif  // EASYPR_CORE_PLATEJUDGE_H_

1、我们看到头文件#include "easypr/core/plate.hpp",该头文件主要以类的形式定义了一个车牌类型,用于存放车牌区域的信息,例如,坐标,大小,score,等等。

这个hpp文件和h文件有什么区别呢?

hpp,顾名思义等于.h加上.cpp ,其实质就是将.cpp的实现代码混入.h头文件当中,定义与实现都包含在同一文件,则该类的调用者只需要include该hpp文件即可,无需再将cpp加入到project中进行编译。而实现代码将直接编译到调用者的obj文件中,不再生成单独的obj,采用hpp将大幅度减少调用project中的cpp文件数与编译次数,也不用再发布烦人的lib与dll,因此非常适合用来编写公用的开源库。(1个cpp生成1个 obj文件,1个project可以有若干个cpp文件组成,每个各自产生自己的 obj文件。然后通过链接,把 obj文件们和 库文件们链接成 exe 文件(或DLL文件) .  )

hpp的优点不少,但是编写中有以下几点要注意:https://blog.csdn.net/shinehoo/article/details/6317747

2、

//PlateJudge* 代表参数instance_的类型,它是一个PlateJudge类的类指针
PlateJudge* PlateJudge::instance_ = nullptr;//就是定义出一个PlateJudge*指针

  PlateJudge* PlateJudge::instance() {
    if (!instance_) {//当指针是空的时候,生成一个新的
      instance_ = new PlateJudge;//这个到底是怎么调用??????
    }
    return instance_;
  }

instance_是一个变量,是PlateJudge的指针类型,它指向的地方保存着一个PlateJudge类型的对象 ,

代码和这个一样
AAA x;
AAA*a;
a=&x;
可以用*a来代替对象x;

3、私有函数PlateJudge的定义,是否使用LBP特征。

PlateJudge::PlateJudge() { 
    bool useLBP = false;
    if (useLBP) {
      LOAD_SVM_MODEL(svm_, kLBPSvmPath);//地址存放SVM模型xml文件
      extractFeature = getLBPFeatures;//函数定义在feature.cpp中,计算提取特征
    }
    else {
      LOAD_SVM_MODEL(svm_, kHistSvmPath);//地址存放SVM模型xml文件
      extractFeature = getHistomPlusColoFeatures;
    }
  }

这里我们重点看一下:extractFeature = getHistomPlusColoFeatures;

getHistomPlusColoFeatures是定义的一个函数,用于提取特征:

//! color feature and histom
void getHistomPlusColoFeatures(const cv::Mat& image, cv::Mat& features);

extractFeature对象 的定义为:svmCallback  extractFeature;其中svmCallback的声明是:

//! EasyPR的getFeatures回调函数
//! 用于从车牌的image生成svm的训练特征features
typedef void (*svmCallback)(const cv::Mat& image, cv::Mat& features);

“”svmCallback  extractFeature“”的意思是:声明了一个函数指针,用于保存传入的函数地址(即getHistomPlusColoFeatures函数的地址)。    

 我的理解是,把函数getHistomPlusColoFeatures的地址赋给了extractFeature,因此当调用extractFeature时,会回调getHistomPlusColoFeatures函数,这样的方便时,我们可以自己定义不同的特征提取函数,然后把函数命名赋给extractFeature。

什么是回调函数(callback)
    模块A有一个函数foo,他向模块B传递foo的地址,然后在B里面发生某种事件(event)时,通过从A里面传递过来的foo的地址调用foo,通知A发生了什么事情,让A作出相应反应。 那么我们就把foo称为回调函数。

声明回调函数类型:
  typedef int (WINAPI *PFCALLBACK)(int Param1,int Param2) ;实际上是声明了一个返回值为int,传入参数为两个int的指向函数的指针。

举个回调函数的例子:

#include <stdio.h>
//回调函数
int ADD(int (*callback)(int,int), int a, int b){
	return (*callback)(a,b);//此处回调add函数...
}
//普通函数
int add(int a, int b){
	return a + b;
}
 
int main(void){
	printf("%d\n",add(1,2));
	printf("%d\n",ADD(add,1,2));
	return 0;
}

具体可参考:https://blog.csdn.net/hellozex/article/details/81742348

4、调用SVM求每个车牌的score,这里score表示样本与决策边界的距离,score存放到plate类中的成员中,用于NMS,同时,根据score的大小,该函数返回0,-1,代表是否为车牌。

int PlateJudge::plateSetScore(CPlate& plate) {
    Mat features;
    extractFeature(plate.getPlateMat(), features);//回调特征提取函数,计算特征
    float score = svm_->predict(features, noArray(), cv::ml::StatModel::Flags::RAW_OUTPUT);
    //std::cout << "score:" << score << std::endl;
    if (0) {
      imshow("plate", plate.getPlateMat());
      waitKey(0);
      destroyWindow("plate");
    }
    // 分数score是margin的距离,零下是车牌,大于0则不是车牌,当得分低于零时,值越小,成为车牌的可能性越大。
//如果距离分类超平面(hyperlane)最近的数据点的距离(margin)越大,该分类器的噪声容忍度就越大,
    plate.setPlateScore(score);//注意这里的setPlateScore函数是类CPlate里定义的函数
    if (score < 0.5) return 0;
    else return -1;      
  }

5、NMS,computeIOU函数定义在core_func.cpp

void NMS(std::vector<CPlate> &inVec, std::vector<CPlate> &resultVec, double overlap) {
    std::sort(inVec.begin(), inVec.end());
    std::vector<CPlate>::iterator it = inVec.begin();
    for (; it != inVec.end(); ++it) {
      CPlate plateSrc = *it;
      //std::cout << "plateScore:" << plateSrc.getPlateScore() << std::endl;
      Rect rectSrc = plateSrc.getPlatePos().boundingRect();
      std::vector<CPlate>::iterator itc = it + 1;
      for (; itc != inVec.end();) {
        CPlate plateComp = *itc;
   //plateComp.getPlatePos()得到一个RotatedRect,再用boundingRect()求这个旋转矩形的最小外接矩形
        Rect rectComp = plateComp.getPlatePos().boundingRect();
        float iou = computeIOU(rectSrc, rectComp);//计算重叠区域
        if (iou > overlap) {
          itc = inVec.erase(itc);//IOU面积大于阈值,删除这个框
        }
        else {
          ++itc;
        }
      }
    }
    resultVec = inVec;
  }

  float computeIOU(const Rect &rect1, const Rect &rect2) {
    Rect inter = interRect(rect1, rect2);
    Rect urect = mergeRect(rect1, rect2);
    float iou = (float) inter.area() / (float) urect.area();
    return iou;
  }

Rect interRect(const Rect &a, const Rect &b) {
    Rect c;
    int x1 = a.x > b.x ? a.x : b.x;
    int y1 = a.y > b.y ? a.y : b.y;
    c.width = (a.x + a.width < b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
    c.height = (a.y + a.height < b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
    c.x = x1;
    c.y = y1;
    if (c.width <= 0 || c.height <= 0)
      c = Rect();
    return c;
  }

  Rect mergeRect(const Rect &a, const Rect &b) {
    Rect c;
    int x1 = a.x < b.x ? a.x : b.x;
    int y1 = a.y < b.y ? a.y : b.y;
    c.width = (a.x + a.width > b.x + b.width ? a.x + a.width : b.x + b.width) - x1;
    c.height = (a.y + a.height > b.y + b.height ? a.y + a.height : b.y + b.height) - y1;
    c.x = x1;
    c.y = y1;
    return c;
  }

6、#include "easypr/core/feature.h"包含求取车牌图像特征的头文件,函数定义在feature.cpp中

//! 获得车牌的特征数
cv::Mat getHistogram(cv::Mat in);

//! EasyPR的getFeatures回调函数, 用于从车牌的image生成svm的训练特征features
typedef void (*svmCallback)(const cv::Mat& image, cv::Mat& features);

//! EasyPR的getFeatures回调函数, 从车牌image生成gray ann的训练特征features
typedef void (*annCallback)(const cv::Mat& image, cv::Mat& features);

//! gray and project feature
void getGrayPlusProject(const cv::Mat& grayChar, cv::Mat& features);

//!本函数是获取垂直和水平的直方图图值
void getHistogramFeatures(const cv::Mat& image, cv::Mat& features);

//! 本函数是获取SIFT特征子
void getSIFTFeatures(const cv::Mat& image, cv::Mat& features);

//! 本函数是获取HOG特征子
void getHOGFeatures(const cv::Mat& image, cv::Mat& features);

//! 本函数是获取HSV空间量化的直方图特征子
void getHSVHistFeatures(const cv::Mat& image, cv::Mat& features);

//! LBP feature。。。。。等等

猜你喜欢

转载自blog.csdn.net/qq_30815237/article/details/89174080