EasyPR源码剖析(9):字符识别
在上一篇文章的介绍中,我们已经通过相应的字符分割方法,将车牌区域进行分割,得到7个分割字符图块,接下来要做的就是将字符图块放入训练好的神经网络模型,通过模型来预测每个图块所表示的具体字符。神经网络的介绍和训练过程我们将在下一节中具体介绍,本节主要介绍字符特征的提取,和如何通过训练好的神经网络模型来进行字符的识别。
字符识别主要是通过 类CharsIdentify 来进行,对于中文字符和非中文字符,分别采取了不同的策略,训练得到的ANN模型也不一样,中文字符的识别主要使用 identifyChinese 来处理,非中文字符的识别主要采用 identify 来处理。另外,类CharsIdentify采用了单例模式,具体的初始化代码和构造函数如下:
CharsIdentify* CharsIdentify::instance_ = nullptr;
CharsIdentify* CharsIdentify::instance() {
if (!instance_) {
instance_ = new CharsIdentify;
}
return instance_;
}
CharsIdentify::CharsIdentify() {
ann_ = ml::ANN_MLP::load<ml::ANN_MLP>(kDefaultAnnPath);
annChinese_ = ml::ANN_MLP::load<ml::ANN_MLP>(kChineseAnnPath);
kv_ = std::shared_ptr<Kv>(new Kv);
kv_->load("etc/province_mapping");
}
void CharsIdentify::LoadModel(std::string path) {
if (path != std::string(kDefaultAnnPath)) {
if (!ann_->empty())
ann_->clear();
ann_ = ml::ANN_MLP::load<ml::ANN_MLP>(path);
}
}
void CharsIdentify::LoadChineseModel(std::string path) {
if (path != std::string(kChineseAnnPath)) {
if (!annChinese_->empty())
annChinese_->clear();
annChinese_ = ml::ANN_MLP::load<ml::ANN_MLP>(path);
}
}
这边单例模式只考虑了单线程情况,对于多线程的话,需要加入双重锁定。此处处理中文字符和非中文字符,分别加载了不同的ANN模型文件,ANN模型通过opencv 中机器学习中自带的神经网络模型 ml::ANN_MLP 来实现。
字符特征获取
通过神经网络对字符图块进行判别,首先需要获取字符图块的特征,字符特征的获取,主要通过 charFeatures 函数来实现。具体的函数代码如下所示:
Mat charFeatures(Mat in, int sizeData) {
const int VERTICAL = ;
const int HORIZONTAL = ;
// cut the cetner, will afect 5% perices.
Rect _rect = GetCenterRect(in);
Mat tmpIn = CutTheRect(in, _rect);
//Mat tmpIn = in.clone();
// Low data feature
Mat lowData;
resize(tmpIn, lowData, Size(sizeData, sizeData));
// Histogram features
Mat vhist = ProjectedHistogram(lowData, VERTICAL);
Mat hhist = ProjectedHistogram(lowData, HORIZONTAL);
// Last 10 is the number of moments components
int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols;
Mat out = Mat::zeros(, numCols, CV_32F);
// Asign values to
int j = ;
for (int i = ; i < vhist.cols; i++) {
out.at<float>(j) = vhist.at<float>(i);
j++;
}
for (int i = ; i < hhist.cols; i++) {
out.at<float>(j) = hhist.at<float>(i);
j++;
}
for (int x = ; x < lowData.cols; x++) {
for (int y = ; y < lowData.rows; y++) {
out.at<float>(j) += (float)lowData.at <unsigned char>(x, y);
j++;
}
}
//std::cout << out << std::endl;
return out;
}
对于中文字符和英文字符,默认的图块大小是不一样的,中文字符默认是 20*20,非中文默认是10*10。
- GetCenterRect 函数主要用于获取字符的边框,分别查找从四个角落查找字符的位置;
- CutTheRect 函数裁剪原图,即将字符移动到图像的中间位置,通过这一步的操作,可将字符识别的准确率提高5%左右;
- ProjectedHistogram 函数用于获取归一化序列,归一化到0-1区间范围内;
GetCenterRect 函数具体代码如下:
Rect GetCenterRect(Mat &in) {
Rect _rect;
int top = ;
int bottom = in.rows - ;
// find the center rect
for (int i = ; i < in.rows; ++i) {
bool bFind = false;
for (int j = ; j < in.cols; ++j) {
if (in.data[i * in.step[] + j] > ) {
top = i;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
for (int i = in.rows - ;
i >= ;
--i) {
bool bFind = false;
for (int j = ; j < in.cols; ++j) {
if (in.data[i * in.step[] + j] > ) {
bottom = i;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
int left = ;
int right = in.cols - ;
for (int j = ; j < in.cols; ++j) {
bool bFind = false;
for (int i = ; i < in.rows; ++i) {
if (in.data[i * in.step[] + j] > ) {
left = j;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
for (int j = in.cols - ;
j >= ;
--j) {
bool bFind = false;
for (int i = ; i < in.rows; ++i) {
if (in.data[i * in.step[] + j] > ) {
right = j;
bFind = true;
break;
}
}
if (bFind) {
break;
}
}
_rect.x = left;
_rect.y = top;
_rect.width = right - left + ;
_rect.height = bottom - top + ;
return _rect;
}
CutTheRect 函数具体代码如下:
Mat CutTheRect(Mat &in, Rect &rect) {
int size = in.cols; // (rect.width>rect.height)?rect.width:rect.height;
Mat dstMat(size, size, CV_8UC1);
dstMat.setTo(Scalar(, , ));
int x = (int) floor((float) (size - rect.width) / 2.0f);
int y = (int) floor((float) (size - rect.height) / 2.0f);
for (int i = ; i < rect.height; ++i) {
for (int j = ; j < rect.width; ++j) {
dstMat.data[dstMat.step[] * (i + y) + j + x] =
in.data[in.step[] * (i + rect.y) + j + rect.x];
}
}
//
return dstMat;
}
ProjectedHistogram 函数代码如下:
float countOfBigValue(Mat &mat, int iValue) {
float iCount = 0.0;
if (mat.rows > ) {
for (int i = ; i < mat.rows; ++i) {
if (mat.data[i * mat.step[]] > iValue) {
iCount += 1.0;
}
}
return iCount;
} else {
for (int i = ; i < mat.cols; ++i) {
if (mat.data[i] > iValue) {
iCount += 1.0;
}
}
return iCount;
}
}
Mat ProjectedHistogram(Mat img, int t) {
int sz = (t) ? img.rows : img.cols;
Mat mhist = Mat::zeros(, sz, CV_32F);
for (int j = ; j < sz; j++) {
Mat data = (t) ? img.row(j) : img.col(j);
mhist.at<float>(j) = countOfBigValue(data, );
}
// Normalize histogram
double min, max;
minMaxLoc(mhist, &min, &max);
if (max > )
mhist.convertTo(mhist, -, 1.0f / max, ); //归一化 0-1
return mhist;
}
通过上述代码可知,非中文字符和中文字符获得的字符特征个数是不同的,非中文字符features个数为 10+10+10*10=120,中文字符features个数为 20+20+20*20=440。
字符识别
通过上述函数获取字符特征之后,可以通过神经网络模型对车牌字符进行识别,具体的识别函数如下所示:
int CharsIdentify::classify(cv::Mat f, float& maxVal, bool isChinses){
int result = -;
cv::Mat output(, kCharsTotalNumber, CV_32FC1);
ann_->predict(f, output);
maxVal = -.f;
if (!isChinses) {
result = ;
for (int j = ; 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 {
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;
}
ann_为之前加载得到的神经网路模型,直接调用其 predict() 函数,即可得到输出矩阵 output,输出矩阵中最大的值即为识别的车牌字符,其中,数值分别为0-64的65个数字,对应的值如下所示:
static const char *kChars[] = {
"", "", "",
"", "", "",
"", "", "",
"",
/* 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 */
};
其中26个英文字母中,因为I 和 O容易和数字的 1和0 混淆,因此被去除了,后面31个中文字符分别对应中国的31个行政区域(港澳台暂不考虑)。将识别的各个字符整体输出,就得到了最终的结果。
EasyPR源码剖析(9):字符识别的更多相关文章
- EasyPR源码剖析(1):概述
EasyPR(Easy to do Plate Recognition)是本人在opencv学习过程中接触的一个开源的中文车牌识别系统,项目Git地址为https://github.com/liuru ...
- EasyPR源码剖析(8):字符分割
通过前面的学习,我们已经可以从图像中定位出车牌区域,并且通过SVM模型删除“虚假”车牌,下面我们需要对车牌检测步骤中获取到的车牌图像,进行光学字符识别(OCR),在进行光学字符识别之前,需要对车牌图块 ...
- EasyPR源码剖析(2):车牌定位
上一篇主要介绍了车牌识别的整体框架和流程,车牌识别主要划分为了两个过程:即车牌检测和字符识别,而车牌识别的核心环节就是这一节主要介绍的车牌定位,即 Plate Locate.车牌定位主要是将图片中有可 ...
- EasyPR源码剖析(7):车牌判断之SVM
前面的文章中我们主要介绍了车牌定位的相关技术,但是定位出来的相关区域可能并非是真实的车牌区域,EasyPR通过SVM支持向量机,一种机器学习算法来判定截取的图块是否是真的“车牌”,本节主要对相关的技术 ...
- EasyPR源码剖析(5):车牌定位之偏斜扭转
一.简介 通过颜色定位和Sobel算子定位可以计算出一个个的矩形区域,这些区域都是潜在车牌区域,但是在进行SVM判别是否是车牌之前,还需要进行一定的处理.主要是考虑到以下几个问题: 1.定位区域存在一 ...
- EasyPR源码剖析(4):车牌定位之Sobel算子定位
一.简介 sobel算子主要是用于获得数字图像的一阶梯度,常见的应用是边缘检测. Ⅰ.水平变化: 将 I 与一个奇数大小的内核进行卷积.比如,当内核大小为3时, 的计算结果为: Ⅱ.垂直变化: 将: ...
- EasyPR源码剖析(3):车牌定位之颜色定位
一.简介 对车牌颜色进行识别,可能大部分人首先想到的是RGB模型, 但是此处RGB模型有一定的局限性,譬如蓝色,其值是255,还需要另外两个分量都为0,不然很有可能你得到的值是白色.黄色更麻烦,它是由 ...
- EasyPR源码剖析(6):车牌判断之LBP特征
一.LBP特征 LBP指局部二值模式,英文全称:Local Binary Pattern,是一种用来描述图像局部特征的算子,LBP特征具有灰度不变性和旋转不变性等显著优点. 原始的LBP算子定义在像素 ...
- jQuery之Deferred源码剖析
一.前言 大约在夏季,我们谈过ES6的Promise(详见here),其实在ES6前jQuery早就有了Promise,也就是我们所知道的Deferred对象,宗旨当然也和ES6的Promise一样, ...
随机推荐
- docker swarm 部署服务时,限制服务启动后所在的机器
借助容器技术,可以方便的在不同环境下部署服务,保证服务环境的一致性.docker swarm这个东西,可以方便的对容器进行编排管理. docker swarm集群中,有manager节点与worker ...
- mybatis学习 -每天一记(驼峰命名匹配)
在mybatis 中,数据库表有一个与之对应的实体类.类属性的命名是驼峰命名的,所以在mybatis中要开启驼峰匹配, 在spring boot 的项目中,至需要在yml文件中配置 即可. 当然也有其 ...
- 嵌入式linux——S3C2440介绍(二)
一.关于S3C2440要知道的事情 (唉,刚才按错了键,弹出了网页的代码,关闭之后,白屏,什么都没有,网页返回,就没保存上,在敲一边...) 1. S3C244三星公司的芯片 2. S3C2440的构 ...
- 阅读rocketmq技术内幕、实战与原理杂记 - 设计
最近正在研究rocketmq,简单记录下设计的不同 互联网系统中Rpc.服务治理.消息中间件基本都是标配,消息中间件能解耦,削峰,高可用并能间接提供达到最终一致性 消息中间件中,消息消费分为最多一次, ...
- 学生月上网时间分布-TestData
Python机器学习应用 | [第一周]无监督学习 - weixin_42906066的博客 - CSDN博客https://blog.csdn.net/weixin_42906066/article ...
- 微软Microsoft SQL server 之 MDS connection问题
微软的MDS和DQS使用面大概还不太广的两个新产品,之前有说道DQS的system.web的register的问题,MDS的问题就经常会碰到链接的问题,当你的用户在Excel中经常都会发生以下问题的时 ...
- (译)MySQL 8.0实验室---MySQL中的倒序索引(Descending Indexes)
译者注:MySQL 8.0之前,不管是否指定索引建的排序方式,都会忽略创建索引时候指定的排序方式(语法上不会报错),最终都会创建为ASC方式的索引,在执行查询的时候,只存在forwarded(正向)方 ...
- 移动端目标识别(1)——使用TensorFlow Lite将tensorflow模型部署到移动端(ssd)之TensorFlow Lite简介
平时工作就是做深度学习,但是深度学习没有落地就是比较虚,目前在移动端或嵌入式端应用的比较实际,也了解到目前主要有 caffe2,腾讯ncnn,tensorflow,因为工作用tensorflow比较多 ...
- mysql Table 'user' is marked as crashed and should be repaired
myisamchk -f x:\xxxxxxxxx\MySQL\data\mysql\*.MYI
- C#使用Spire.Doc Word for .Net读写Word
以前对Excel或Word文档操作都使用微软的COM组件Microsoft Word 15.0 object library. 但是这种方式必须要求服务器上安装Office,而且会出现读写操作完成后未 ...