在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符。本节主要介绍字符特征的提取,和如何通过训练好的神经网络模型来进行字符的识别。

字符识别主要是通过类CharsIdentify 来进行,对于中文字符和非中文字符,分别采取了不同的策略,训练得到的ANN模型也不一样,中文字符的识别主要使用 identifyChinese 来处理,非中文字符的识别主要采用 identify 来处理。另外,类CharsIdentify采用了单例模式。参见文末5

chars_identify.h头文件如下:

#include "easypr/util/kv.h"
#include "easypr/core/character.hpp"
#include "easypr/core/feature.h"

namespace easypr {

class CharsIdentify {
public:
  static CharsIdentify* instance();

 //ann_模型由LoadModel函数加载

//参见文末1。通过神经网络模型ann_对车牌字符进行识别,maxVal表示ann输出的每个字符的可能性大小
int classify(cv::Mat f, float& maxVal, bool isChinses = false, bool isAlphabet = false);

//输入车牌字符的特征向量,送入ann_
void classify(cv::Mat featureRows, std::vector<int>& out_maxIndexs,std::vector<float>& out_maxVals, std::vector<bool> isChineseVec);

//输入车牌类CCharacter的对象,调用charFeatures函数提取字符特征,送入ann_预测,文末2
void classify(std::vector<CCharacter>& charVec);

//annChinese_  annGray_分别由LoadChineseModel和LoadGrayChANN加载,见文末3

//使用模型annChinese_进行字符识别
void classifyChinese(std::vector<CCharacter>& charVec);

//使用模型annGray_进行字符识别
void classifyChineseGray(std::vector<CCharacter>& charVec);

//文末4  ,调用classify函数进行分类,得到字符在KChar数组中的索引
std::pair<std::string, std::string> identify(cv::Mat input, bool isChinese = false, bool isAlphabet = false);
  int identify(std::vector<cv::Mat> inputs, std::vector<std::pair<std::string, std::string>>& outputs,
               std::vector<bool> isChineseVec);

std::pair<std::string, std::string> identifyChinese(cv::Mat input, float& result, bool& isChinese);//调用annChinese_分类
  std::pair<std::string, std::string> identifyChineseGray(cv::Mat input, float& result, bool& isChinese);//调用annGray_分类

//使用classify函数进行分类,经过ann得到每个字符的分数,如果最大值maxVal不满如条件(maxVal >= 0.9 || (isChinese && maxVal >= chineseMaxThresh)),则说明,这个不是字符.
bool isCharacter(cv::Mat input, std::string& label, float& maxVal, bool isChinese = false);

void LoadModel(std::string path);//加载非中文字符模型ann_
  void LoadChineseModel(std::string path);//加载中文字符模型annChinese_
  void LoadGrayChANN(std::string path);//annGray_
  void LoadChineseMapping(std::string path);//加载模型kv_,不知道是啥??

private:
  CharsIdentify();
  annCallback extractFeature;
  static CharsIdentify* instance_;

// binary character classifer
  cv::Ptr<cv::ml::ANN_MLP> ann_;

// binary character classifer, only for chinese
  cv::Ptr<cv::ml::ANN_MLP> annChinese_;

// gray classifer, only for chinese
  cv::Ptr<cv::ml::ANN_MLP> annGray_;

// used for chinese mapping
  std::shared_ptr<Kv> kv_;
};
}

#endif

文末:

1、通过上述函数获取字符特征之后,可以通过神经网络模型对车牌字符进行识别,具体的识别函数:

int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses, bool isAlphabet){int result = 0;cv::Mat output(1, kCharsTotalNumber, CV_32FC1);ann_->predict(f, output);//调用其 predict() 函数,即可得到输出矩阵 output,输出矩阵中最大的值即为识别的车牌字符.maxVal = -2.f;if (!isChinses) {//如果不是中文字符if (!isAlphabet) {//不是字母result = 0;for (int j = 0; j < kCharactersNumber; j++) {//kCharactersNumber=34,10个数字,24个字母float val = output.at<float>(j);// std::cout << "j:" << j << "val:" << val << std::endl;if (val > maxVal) {maxVal = val;result = j;//招待output中最大值,和其对应的位置}}}else {//是字母字符result = 0;// begin with 11th char, which is 'A'for (int j = 10; j < kCharactersNumber; j++) {float val = output.at<float>(j);// std::cout << "j:" << j << "val:" << val << std::endl;if (val > maxVal) {maxVal = val;result = j;}}}}else {//是中文字符,从34开始,kCharsTotalNumber为65,result = kCharactersNumber;for (int j = kCharactersNumber; j < kCharsTotalNumber; j++) {float val = output.at<float>(j);//std::cout << "j:" << j << "val:" << val << std::endl;if (val > maxVal) {maxVal = val;result = j;}}}//std::cout << "maxVal:" << maxVal << std::endl;return result;
}

注意classify函数返回字符在kchars数组中的索引号

ann_为之前加载得到的神经网路模型,直接调用其 predict() 函数,即可得到输出矩阵 output,输出矩阵中最大的值即为识别的车牌字符,其中,数值分别为0-64的65个数字,对应的值如下所示:

static const char *kChars[] = {"0", "1", "2","3", "4", "5","6", "7", "8","9",/*  10  */"A", "B", "C","D", "E", "F","G", "H", /* {"I", "I"} */"J", "K", "L","M", "N", /* {"O", "O"} */"P", "Q", "R","S", "T", "U","V", "W", "X","Y", "Z",/*  24  */"zh_cuan" , "zh_e"    , "zh_gan"  ,"zh_gan1" , "zh_gui"  , "zh_gui1" ,"zh_hei"  , "zh_hu"   , "zh_ji"   ,"zh_jin"  , "zh_jing" , "zh_jl"   ,"zh_liao" , "zh_lu"   , "zh_meng" ,"zh_min"  , "zh_ning" , "zh_qing" ,"zh_qiong", "zh_shan" , "zh_su"   ,"zh_sx"   , "zh_wan"  , "zh_xiang","zh_xin"  , "zh_yu"   , "zh_yu1"  ,"zh_yue"  , "zh_yun"  , "zh_zang" ,"zh_zhe"/*  31  */
};

2、字符特征的获取,主要通过 charFeatures 函数来实现,返回字符特征向量。

非中文字符features个数为 10+10+10*10=120,10个水平投影,10个垂直投影,100个像素行累加和特征。

Mat charFeatures(Mat in, int sizeData) {const int VERTICAL = 0;const int HORIZONTAL = 1;// cut the cetner, will afect 5% perices.Rect _rect = GetCenterRect(in);Mat tmpIn = CutTheRect(in, _rect);// Low data featureMat lowData;
//非中文字符和中文字符获得的字符特征个数是不同的,非中文字符features个数为 10+10+10*10=120,
//中文字符features个数为  20+20+20*20=440resize(tmpIn, lowData, Size(sizeData, sizeData));//英文字符尺寸size:10*10// Histogram featuresMat vhist = ProjectedHistogram(lowData, VERTICAL);//垂直投影Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);/水平投影int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;Mat out = Mat::zeros(1, numCols, CV_32F);int j = 0;
//将这些特征填入out,用于ANNfor (int i = 0; i < vhist.cols; i++) {out.at<float>(j) = vhist.at<float>(i);j++;}for (int i = 0; i < hhist.cols; i++) {out.at<float>(j) = hhist.at<float>(i);j++;}for (int x = 0; x < lowData.cols; x++) {for (int y = 0; y < lowData.rows; y++) {out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);j++;}}return out;//返回字符特征向量
}

对于中文字符和英文字符,默认的图块大小是不一样的,中文字符默认是 20*20,非中文默认是10*10。

  • GetCenterRect 函数主要用于获取字符的边框,分别查找从四个角落查找字符的位置;
  • CutTheRect 函数裁剪原图,即将字符移动到图像的中间位置,通过这一步的操作,可将字符识别的准确率提高5%左右;
  • ProjectedHistogram 函数用于获取归一化序列,归一化到0-1区间范围内;

3、

void CharsIdentify::LoadModel(std::string path) {if (path != std::string(kDefaultAnnPath)) {if (!ann_->empty())ann_->clear();LOAD_ANN_MODEL(ann_, path);}
}void CharsIdentify::LoadChineseModel(std::string path) {if (path != std::string(kChineseAnnPath)) {if (!annChinese_->empty())annChinese_->clear();LOAD_ANN_MODEL(annChinese_, path);}
}void CharsIdentify::LoadGrayChANN(std::string path) {if (path != std::string(kGrayAnnPath)) {if (!annGray_->empty())annGray_->clear();LOAD_ANN_MODEL(annGray_, path);}
}

4、

std::pair<std::string, std::string> CharsIdentify::identify(cv::Mat input, bool isChinese, bool isAlphabet) {cv::Mat feature = charFeatures(input, kPredictSize);float maxVal = -2;auto index = static_cast<int>(classify(feature, maxVal, isChinese, isAlphabet));if (index < kCharactersNumber) {return std::make_pair(kChars[index], kChars[index]);}else {const char* key = kChars[index];std::string s = key;std::string province = kv_->get(s);return std::make_pair(s, province);}
}

5、单例模式

单例模式(Singleton Pattern)涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

5.1、单例模式的懒汉模式

所谓懒汉模式,就是在需要用到创建实例了程序再去创建实例,不需要创建实例程序就“懒得”去创建实例,这是一种时间换空间的做法,这体现了“懒汉的本性”。如果有多个线程,同时调用了获取实例,那么可能就会产生多个实例,那么这就不是我们的单例模式了。所以我们需要做一些事情才能保证我们是一个单例类。

对于饿汉模式来说,它在单例类内部定义了一个类对象的指针,在初始化这个指针的时候,直接调用new在堆上申请了空间。于是达到了这种效果,饿汉就是类一旦加载,就把单例初始化完成,在进入main函数之前,这个单例类的实例已经创建好了参考:单例模式——饿汉模式。

而对于懒汉模式而言,我们可以不让这个类对象指针在初始化的时候就new,而是给它赋一个NULL,那这样,在进入main函数之前,这个类对象指针只是一个空指针,并没有产生实际的对象。而当我们在程序中调用instance()函数时,就需要进行一个判断,如果该类对象指针为空,那么我们就需要调用new创建一个对象,而如果该类对象指针不为空,那么我们就不用创建对象直接返回该指针就好了。根据这个思路,我们实现了下面的代码(因为我们的静态成员变量采用的是类对象的指针而不是类对象,因此我们需要写一个垃圾回收机制,这里我们采用的是上一篇文章的方法二,即实现一个内部的垃圾回收类)

头文件声明:
class CharsIdentify {public:static CharsIdentify* instance();private:CharsIdentify();  //通过创建私有构造函数这样是可以保证单例。
}源文件定义:
CharsIdentify* CharsIdentify::instance_ = nullptr;//定义一个空的类对象指针
CharsIdentify* CharsIdentify::instance() {if (!instance_) {instance_ = new CharsIdentify;}return instance_;
}

类加载时类的初始化和创建实例时的初始化顺序

1、虚拟机在首次加载类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化
2、只有在调用new方法时才会创建类的实例

easyPR源码解析之chars_identify.h相关推荐

  1. easyPR源码解析之chars_segment.h

    chars_segment.h用于从已经通过SVM判别得到的车牌区域中将车牌的字符分割开,用于下一步的ANN字符识别. namespace easypr { class CCharsSegment { ...

  2. easyPR源码解析之ann_train.h/config.h

    ann_train.h源码定义一个 AnnTrain类,该类继承自ITrain类(在train.h文件中): #include "easypr/train/train.h" #in ...

  3. easyPR源码解析之plate_judge.h

    #ifndef EASYPR_CORE_PLATEJUDGE_H_ #define EASYPR_CORE_PLATEJUDGE_H_ #include "easypr/core/plate ...

  4. easyPR源码解析之plate_locate.h

    从今天开始,准备一点一点的啃代码easyPR项目. 我们先从libesypr文件下的源文件/core/plate_locate.cpp开始: plate_locate.cpp文件中包含的头文件如下: ...

  5. 【darknet源码解析-24】shortcut_layer.h 和 shortcut_layer.c 解析

    本系列为darknet源码解析,本次解析src/short_layer.h 与 src/short_layer.c 两个.在yolo v3中short_layer主要完成直连操作,完成残差块中的恒等映 ...

  6. H.264压缩技术之视频基础(foundation of learning video)——Matlab源码解析

    前言 为了后续能更好的理解,I帧编码与P帧编码,所以笔者先对数字视频中的一些基础概念进行铺垫.后续比较复杂的帧内预测,与帧间预测理解起来就会相对容易些. 关于Matlab中h.264的main函数部分 ...

  7. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  8. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  9. libev源码解析——监视器(watcher)结构和组织形式

    在<libev源码解析--总览>中,我们介绍了libev的一些重要变量在不同编译参数下的定义位置.由于这些变量在多线程下没有同步问题,所以我们将问题简化,所提到的变量都是线程内部独有的,不 ...

最新文章

  1. 流程图的制作技巧分享,手把手教你如何画流程图?
  2. C++11中的Lambda表达式
  3. 网站制作时应该如何更合理定位与策划
  4. 14 调整数组顺序使奇数位于偶数前面
  5. 读zepto核心源码学习JS笔记(3)--zepto.init()
  6. 企业级市场,正成为IT老兵创业的最佳选择
  7. oracle 改变受限模式,oracle之受限模式修改
  8. 在本地新建分支,以进行功能开发
  9. ac3168无线网卡驱动下载_70块的笔记本网卡,值不值得换
  10. git-commit-amend踩坑
  11. 链游玩家寻找新一代武侠小说掌门人,签约青年武侠作家常博涵
  12. MTK Android Led框架分析
  13. 8天掌握EF的Code First开发系列之动手写第一个Code First应用
  14. P1252 马拉松接力赛
  15. SQLServer修改表数据
  16. 进阶实验4-3.5 哈夫曼编码 (30 分)
  17. proteus7isis 如何设置电源电压范围
  18. 用VMware导入.ova文件
  19. 软件测试面试题:优惠券的测试点包含哪些方面?测试点包含哪些方面?
  20. 解决下载速度快但打开网页速度慢的问题

热门文章

  1. asterisk通话无声音_对讲机的语音通话间距到底有多远?对讲机的常见问题?
  2. 证书是用来改善与增强,而不是代替? --如何让钱主动来找你?
  3. 软考信息安全工程师备考笔记1:第一章信息安全基础备考要点
  4. python的特性是_python的特性
  5. 广州测试沙龙的问题。
  6. web自动化测试第12步:selenium中下拉框的解决方法(Select)
  7. Spark机器学习(3):保序回归算法
  8. 朴素贝叶斯与贝叶斯网络
  9. flutter ScaleTransition实现缩放动画
  10. Laravel 日志权限问题