【深度学习模型】智云视图中文车牌识别源码解析(一)
【深度学习模型】智云视图中文车牌识别源码解析HyperLPR是 智云视图(http://www.zeusee.com)开源的一个使用深度学习针对对中文车牌识别的实现,HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等。Github地址:https://github.com/zeusees/HyperLPR代码结构CNNRecogn...
·
【深度学习模型】智云视图中文车牌识别源码解析
HyperLPR是 智云视图(http://www.zeusee.com)开源的一个使用深度学习针对对中文车牌识别的实现,HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等。
Github地址:https://github.com/zeusees/HyperLPR
代码结构
- CNNRecognizer.cpp:加载模型预测标签;
- FastDeskew.cpp:快速旋转歪斜的车牌;
- FineMapping.cpp:绘制车牌检测框;
- Pipeline.cpp:这是串联程序,串联起了plateDetection、fineMapping、plateSegmentation、generalRecognizer、segmentationFreeRecognizer;
- PlateDetection.cpp:探测车牌在图中何处位置;
- PlateSegmentation.cpp:将每一个字符从车牌图像中分离;
- Recognizer.cpp:挨个识别字符并返回识别结果;
- SegmentationFreeRecognizer.cpp:检测单个图像并把结果保存在mapping_table;
代码量还是不小,所以打算两周写完8篇解析,说实话代码质量… …但是功能还是实现了的吧,如果看到后面代码质量太无语,就停更(话说在github上人气还蛮高);
可识别和待支持的车牌的类型
- [x] 单行蓝牌
- [x] 单行黄牌
- [x] 新能源车牌
- [x] 白色警用车牌
- [x] 使馆/港澳车牌
- [x] 教练车牌
- [x] 武警车牌
- [ ] 民航车牌
- [ ] 双层黄牌
- [ ] 双层武警
- [ ] 双层军牌
- [ ] 双层农用车牌
- [ ] 双层个性化车牌
特性
- 速度快 720p ,单核 Intel 2.2G CPU (macbook Pro 2015)平均识别时间低于100ms
- 基于端到端的车牌识别无需进行字符分割
- 识别率高,仅仅针对车牌ROI在EasyPR数据集上,0-error达到 95.2%, 1-error识别率达到 97.4% (指在定位成功后的车牌识别率)
- 轻量 总代码量不超1k行
解析
今天先解析FastDeskew.cpp:
/*----------------------------------------------*/
/*--------------用于快速旋转图像----------------*/
/*----------------------------------------------*/
#include <../include/FastDeskew.h>
namespace pr{
//定义最小旋转角度;
const int ANGLE_MIN = 30 ;
//定义最大旋转角度;
const int ANGLE_MAX = 150 ;
//定义车牌高;
const int PLATE_H = 36;
//定义车牌宽;
const int PLATE_W = 136;
/*
-----------计算角度------------
@x : 边长;
@y : 边长;
*/
int angle(float x,float y)
{
//计算角度;
return atan2(x,y)*180/3.1415;
}
/*
-----------计算一组角度的平均角度(类似平滑卷积)------------
@angle_list : 一组角度值;
@windowsSize : 平滑计算时窗口大小;
*/
std::vector<float> avgfilter(std::vector<float> angle_list,int windowsSize) {
//定义vector angle_list_filtered;
std::vector<float> angle_list_filtered(angle_list.size() - windowsSize + 1);
//循环计算每个
for (int i = 0; i < angle_list.size() - windowsSize + 1; i++) {
//清空当前angle的均值;
float avg = 0.00f;
//累计计算总的angle;
for (int j = 0; j < windowsSize; j++) {
avg += angle_list[i + j];
}
//求平均;
avg = avg / windowsSize;
//对应平均值写入angle_list_filtered;
angle_list_filtered[i] = avg;
}
//返回结果;
return angle_list_filtered;
}
/*
------------绘制直方图---------------
@seq 一组数据,需要画到直方图上;
*/
void drawHist(std::vector<float> seq){
//直方图声明;
cv::Mat image(300,seq.size(),CV_8U);
//直方图清零;
image.setTo(0);
//循环,将seq中每个数值画上去;
for(int i = 0;i<seq.size();i++)
{
//先画seq中最大的值;
float l = *std::max_element(seq.begin(),seq.end());
//高度挤压到在0~300内;
int p = int(float(seq[i])/l*300);
//画上去;
cv::line(image,cv::Point(i,300),cv::Point(i,300-p),cv::Scalar(255,255,255));
}
//展示这个结果;
cv::imshow("vis",image);
}
/*
------------校正车牌图像---------------
@skewPlate : 需要校正的图像;
@angle : 需要旋转的角度;
@maxAngle : 最大旋转的角度;
*/
cv::Mat correctPlateImage(cv::Mat skewPlate,float angle,float maxAngle)
{
//声明旋转后的图像;
cv::Mat dst;
//获取待处理图像的尺寸size_o;
cv::Size size_o(skewPlate.cols,skewPlate.rows);
//延长填充变量声明;
int extend_padding = 0;
//计算延长填充大小;
extend_padding = static_cast<int>(skewPlate.rows*tan(cv::abs(angle)/180* 3.14) );
//计算延展后的图像尺寸size;
cv::Size size(skewPlate.cols + extend_padding ,skewPlate.rows);
//计算旋转后的宽;
float interval = abs(sin((angle /180) * 3.14)* skewPlate.rows);
//原图像构成的矩形的四个顶点坐标;
cv::Point2f pts1[4] = {cv::Point2f(0,0),cv::Point2f(0,size_o.height),cv::Point2f(size_o.width,0),cv::Point2f(size_o.width,size_o.height)};
//逆时针旋转;
if(angle>0) {
//新图像构成的矩形的四个顶点坐标;
cv::Point2f pts2[4] = {cv::Point2f(interval, 0), cv::Point2f(0, size_o.height),
cv::Point2f(size_o.width, 0), cv::Point2f(size_o.width - interval, size_o.height)};
//计算变换矩阵;
cv::Mat M = cv::getPerspectiveTransform(pts1,pts2);
//将skewPlate经M变换为大小为size的图像dst;
cv::warpPerspective(skewPlate,dst,M,size);
}
//顺时针旋转;
else {
//新图像构成的矩形的四个顶点坐标;
cv::Point2f pts2[4] = {cv::Point2f(0, 0), cv::Point2f(interval, size_o.height), cv::Point2f(size_o.width-interval, 0),
cv::Point2f(size_o.width, size_o.height)};
//计算变换矩阵;
cv::Mat M = cv::getPerspectiveTransform(pts1,pts2);
//将skewPlate经M变换为大小为size的图像dst;
cv::warpPerspective(skewPlate,dst,M,size,cv::INTER_CUBIC);
}
//返回结果;
return dst;
}
/*
------------快速旋转车牌图像---------------
@skewPlate : 需要校正的图像;
@blockSize : 角点检测步长;
*/
cv::Mat fastdeskew(cv::Mat skewImage,int blockSize){
//过滤的窗口大小;
const int FILTER_WINDOWS_SIZE = 5;
//声明一个angle_list存储角度;
std::vector<float> angle_list(180);
//为其分配内存;
memset(angle_list.data(),0,angle_list.size()*sizeof(int));
//用于备份原图像的图像bak;
cv::Mat bak;
//将原图赋值给bak;
skewImage.copyTo(bak);
//如果是rgb图像;
if(skewImage.channels() == 3)
//先把它转化为黑白图;
cv::cvtColor(skewImage,skewImage,cv::COLOR_RGB2GRAY);
//如果是黑白图;
if(skewImage.channels() == 1)
{
//声明特征矩阵eigen;
cv::Mat eigen;
//计算图像块的特征值和特征向量,用于角点检测,结果保存在eigen;
cv::cornerEigenValsAndVecs(skewImage,eigen,blockSize,5);
//遍历skewImage的每个像素;
for( int j = 0; j < skewImage.rows; j+=blockSize )
{ for( int i = 0; i < skewImage.cols; i+=blockSize )
{
//(x2,y2)存储的是skewImage存在eigen的角点信息;
float x2 = eigen.at<cv::Vec6f>(j, i)[4];
float y2 = eigen.at<cv::Vec6f>(j, i)[5];
//计算角度;
int angle_cell = angle(x2,y2);
//在对应角度上作累计计数;
angle_list[(angle_cell + 180)%180]+=1.0;
}
}
}
//计算平滑窗口大小为FILTER_WINDOWS_SIZE的平均角度过滤;
std::vector<float> filtered = avgfilter(angle_list,FILTER_WINDOWS_SIZE);
//计算均角过滤的最大位置;
int maxPos = std::max_element(filtered.begin(),filtered.end()) - filtered.begin() + FILTER_WINDOWS_SIZE/2;
//超过了ANGLE_MAX;
if(maxPos>ANGLE_MAX)
//作角度变换;
maxPos = (-maxPos+90+180)%180;
//未超过ANGLE_MAX;
if(maxPos<ANGLE_MIN)
//控制在90内;
maxPos-=90;
//再变换;
maxPos=90-maxPos;
//按maxPos校正图像;
cv::Mat deskewed = correctPlateImage(bak, static_cast<float>(maxPos),60.0f);
//返回结果;
return deskewed;
}
}//namespace pr
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
已为社区贡献1条内容
所有评论(0)