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一样, ...
随机推荐
- 【JsonView工具】谷歌浏览器中安装JsonView扩展程序
接口测试过程中,有时候要查看接口返回的数据(比如Get接口),为了更方便的查看,发现这个插件挺好用的. 实际开发工作中经常用到json数据,那么就会有这样一个需求:在谷歌浏览器中访问URL地址返回的j ...
- Spring3.2.0 之后各个版本完整包下载地址
留作工作学习使用 现在Spring官网已经很难找到完整包的下载地址,都已经迁移到Maven上,这给不能用Maven或者不愿用Maven的各位带来了不小的麻烦. 经过挖掘,找到了下载3.2之后各个版本完 ...
- zuul ci
. ├── a_module ├── b_module ├── lib ├── zuul │ ├── check │ ├── ci │ ├── gate │ ├── layout.ya ...
- linux命令--xargs的使用
xargs 是给命令传递参数的一个过滤器,也是组合多个命令的一个工具. xargs 可以将管道或标准输入(stdin)数据转换成命令行参数,也能够从文件的输出中读取数据. xargs 也可以将单行或多 ...
- ios端滚动优化
加入css -webkit-overflow-scrolling: touch;
- oracle SQL多表查询
SQL多表查询 1.集合理论 1.1 什么是集合 具有某种特定性质的事物的总体. 集合的特性:无序性.互异性.确定性. 一个集合可以小到从一个表中取出一行中的一列. 1 ro ...
- Helm介绍
1.为什么要用Helm? 首先在原来项目中都是基于yaml文件来进行部署发布的,而目前项目大部分微服务化或者模块化,会分成很多个组件来部署,每个组件可能对应一个deployment.yaml,一个se ...
- React 获取服务器API接口数据:axios、fetchJsonp
使用axios.fetchJsonp获取服务器的接口数据.其中fetchJsonp是跨域访问 一.使用axios 1.安装axios模块 npm install --save axios 2.引用模块 ...
- 算法之Python实现 - 000
Python的火热已极,几乎人人在学Python,为了节约时间,也为了实现Python的代码量,计划从今天开始,将<算法与数据结构题目最优解>一书中的代码全部用Python实现.
- rm命令详解
1.简介: rm是常用的命令,该命令的功能为删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除.对于链接文件,只是删除了链接,原有文件均保持不变. 注意:rm是一个 ...