#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。。。。。等等