【深度学习模型】智云视图中文车牌识别源码解析(二)
感受
HyperLPR可以识别多种中文车牌包括白牌,新能源车牌,使馆车牌,教练车牌,武警车牌等。 代码不可谓不混乱(别忘了这是职业公司的准产品级代码,如果是什么毕业论文我就不说了)测试驱动什么的别想了,所以可能这次更了就不更了!!
代码结构
本次是Pipeline.cpp部分;
- CNNRecognizer.cpp:加载模型预测标签;
- FastDeskew.cpp:快速旋转歪斜的车牌;
- FineMapping.cpp:绘制车牌检测框;
- Pipeline.cpp:这是串联程序,串联起了plateDetection、fineMapping、plateSegmentation、generalRecognizer、segmentationFreeRecognizer;
- PlateDetection.cpp:探测车牌在图中何处位置;
- PlateSegmentation.cpp:将每一个字符从车牌图像中分离;
- Recognizer.cpp:挨个识别字符并返回识别结果;
- SegmentationFreeRecognizer.cpp:检测单个图像并把结果保存在mapping_table;
源代码
头文件:
/*------------------------------------------------------------------*/
/*--------------用于串联多个程序实现检测识别(头文件)----------------*/
/*------------------------------------------------------------------*/
#ifndef SWIFTPR_PIPLINE_H
#define SWIFTPR_PIPLINE_H
#include "PlateDetection.h"
#include "PlateSegmentation.h"
#include "CNNRecognizer.h"
#include "PlateInfo.h"
#include "FastDeskew.h"
#include "FineMapping.h"
#include "Recognizer.h"
#include "SegmentationFreeRecognizer.h"
namespace pr{
//可能出现的字符集合;
const std::vector<std::string> CH_PLATE_CODE{"京", "沪", "津", "渝", "冀", "晋", "蒙", "辽", "吉", "黑", "苏", "浙", "皖", "闽", "赣", "鲁", "豫", "鄂", "湘", "粤", "桂",
"琼", "川", "贵", "云", "藏", "陕", "甘", "青", "宁", "新", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A",
"B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z","港","学","使","警","澳","挂","军","北","南","广","沈","兰","成","济","海","民","航","空"};
//定义不同的分割方法的编号;
const int SEGMENTATION_FREE_METHOD = 0;
const int SEGMENTATION_BASED_METHOD = 1;
//声明主类;
class PipelinePR{
public:
//创建车牌位置检测模型实例指针;
GeneralRecognizer *generalRecognizer;
//创建抠出车牌图像模型实例指针;
PlateDetection *plateDetection;
//创建车牌分割模型实例指针;
PlateSegmentation *plateSegmentation;
//创建识别模型实例指针;
FineMapping *fineMapping;
//创建识别单个车牌图像的模型实例指针;
SegmentationFreeRecognizer *segmentationFreeRecognizer;
//构造函数;
PipelinePR(std::string detector_filename,
std::string finemapping_prototxt,std::string finemapping_caffemodel,
std::string segmentation_prototxt,std::string segmentation_caffemodel,
std::string charRecognization_proto,std::string charRecognization_caffemodel,
std::string segmentationfree_proto,std::string segmentationfree_caffemodel
);
//析构函数;
~PipelinePR();
//保存识别结果;
std::vector<std::string> plateRes;
//执行单个车牌的识别;
std::vector<PlateInfo> RunPiplineAsImage(cv::Mat plateImage,int method);
};
}
#endif //SWIFTPR_PIPLINE_H
主程序:
/*----------------------------------------------------------*/
/*--------------用于串联多个程序实现检测识别----------------*/
/*----------------------------------------------------------*/
#include "../include/Pipeline.h"
namespace pr {
//定义水平方向填充参数;
const int HorizontalPadding = 4;
/*
-----------串接多个功能(构造函数)------------
@detector_filename : 识别模型所在路径;
@finemapping_prototxt : 抠出车牌图像模型的prototxt定义文件路径;
@finemapping_caffemodel : 抠出车牌图像模型的模型文件路径;
@segmentation_prototxt : 分割图像字符的模型的prototxt定义文件路径;
@segmentation_caffemodel : 分割图像字符的模型的模型文件路径;
@charRecognization_proto : 字符识别的模型的prototxt定义文件路径;
@charRecognization_caffemodel : 字符识别的模型的模型文件路径;
@segmentationfree_proto : 识别单个车牌的模型的prototxt定义文件路径;
@segmentationfree_caffemodel : 识别单个车牌的模型的模型文件路径;
*/
PipelinePR::PipelinePR(std::string detector_filename,
std::string finemapping_prototxt, std::string finemapping_caffemodel,
std::string segmentation_prototxt, std::string segmentation_caffemodel,
std::string charRecognization_proto, std::string charRecognization_caffemodel,
std::string segmentationfree_proto,std::string segmentationfree_caffemodel) {
//创建车牌位置检测模型实例指针;
plateDetection = new PlateDetection(detector_filename);
//创建抠出车牌图像模型实例指针;
fineMapping = new FineMapping(fnemapping_prototxt, finemapping_caffemodel);
//创建车牌分割模型实例指针;
plateSegmentation = new PlateSegmentation(segmentation_prototxt, segmentation_caffemodel);
//创建识别模型实例指针;
generalRecognizer = new CNNRecognizer(charRecognization_proto, charRecognization_caffemodel);
//创建识别单个车牌图像的模型实例指针;
segmentationFreeRecognizer = new SegmentationFreeRecognizer(segmentationfree_proto,segmentationfree_caffemodel);
}
/*
-----------析构函数------------
*/
PipelinePR::~PipelinePR() {
//删除创建的各个实例指针;
delete plateDetection;
delete fineMapping;
delete plateSegmentation;
delete generalRecognizer;
delete segmentationFreeRecognizer;
}
/*
-----------识别车牌中各个字符------------
@plateImage : 包含一个/多个车牌图像;
@method : 分割方法的编码;
*/
std::vector<PlateInfo> PipelinePR::RunPiplineAsImage(cv::Mat plateImage,int method) {
//声明结果列表;
std::vector<PlateInfo> results;
//保存车牌中间信息;
std::vector<pr::PlateInfo> plates;
//执行车牌粗略探测位置(结果存在plates内);
plateDetection->plateDetectionRough(plateImage,plates,36,700);
//迭代图中每个车牌;
for (pr::PlateInfo plateinfo:plates) {
//获取该车牌图像(image_finemapping的finemapping是为了分割出尽量只包含单个车牌的图像);
cv::Mat image_finemapping = plateinfo.getPlateImage();
//对图像垂直处理;
image_finemapping = fineMapping->FineMappingVertical(image_finemapping);
//校正角度;
image_finemapping = pr::fastdeskew(image_finemapping, 5);
//选择分割车牌字符的方法;
//方法一:基础方法;
if(method==SEGMENTATION_BASED_METHOD)
{
//对图像水平处理;
image_finemapping = fineMapping->FineMappingHorizon(image_finemapping, 2, HorizontalPadding);
//大小调整;
cv::resize(image_finemapping, image_finemapping, cv::Size(136+HorizontalPadding, 36));
//展示;
//cv::imshow("image_finemapping",image_finemapping);
//cv::waitKey(0);
//设定为调整后的图像;
plateinfo.setPlateImage(image_finemapping);
//定义矩形框列表;
std::vector<cv::Rect> rects;
//对车牌图像的字符分割,结果存在rects;
plateSegmentation->segmentPlatePipline(plateinfo, 1, rects);
//将每个rect中的字符子图又存到plateinfo中去;
plateSegmentation->ExtractRegions(plateinfo, rects);
//复制图像并且制作边界;处理边界卷积(将image_finemapping的黑色边界填充);
cv::copyMakeBorder(image_finemapping, image_finemapping, 0, 0, 0, 20, cv::BORDER_REPLICATE);
//录入plateinfo;
plateinfo.setPlateImage(image_finemapping);
//进行识别;
generalRecognizer->SegmentBasedSequenceRecognition(plateinfo);
//解码中文字符;
plateinfo.decodePlateNormal(pr::CH_PLATE_CODE);
}
//方法二:Segmentation-free
else if(method==SEGMENTATION_FREE_METHOD)
{
//对图像水平处理;
image_finemapping = fineMapping->FineMappingHorizon(image_finemapping, 4, HorizontalPadding+3);
//大小调整;
cv::resize(image_finemapping, image_finemapping, cv::Size(136+HorizontalPadding, 36));
//存储图像;
//cv::imwrite("./test.png",image_finemapping);
//显示图像;
//cv::imshow("image_finemapping",image_finemapping);
//cv::waitKey(0);
//录入plateinfo;
plateinfo.setPlateImage(image_finemapping);
//定义矩形框列表;
//std::vector<cv::Rect> rects;
//对单个图像进行识别;
std::pair<std::string,float> res = segmentationFreeRecognizer->SegmentationFreeForSinglePlate(plateinfo.getPlateImage(),pr::CH_PLATE_CODE);
//获取置信度;
plateinfo.confidence = res.second;
//车牌识别字符结果;
plateinfo.setPlateName(res.first);
}
//结果加入列表;
results.push_back(plateinfo);
}
//遍历识别结果
//for (auto str:results) {
//输出;
//std::cout << str << std::endl;
//}
//返回结果;
return results;
}//namespace pr
}
测试程序:
/*------------------------------------------------------*/
/*--------------用于串联多个程序(测试)----------*/
/*------------------------------------------------------*/
#include "../include/Pipeline.h"
using namespace std;
/*
-----------Levenshtein距离(度量车牌识别效果)------------
编辑距离Edit Distance,又称Levenshtein距离,是指两个字串之间,由一个转成另一个所需的最少编辑操作次数;
Levenshtein距离算法可见:https://blog.youkuaiyun.com/qq844352155/article/details/38686549;
@s1 : 字符串1;
@s2 : 字符串2;
*/
template<class T>
static unsigned int levenshtein_distance(const T &s1, const T &s2){
//获取s1、s2的字符长;
const size_t len1 = s1.size(), len2 = s2.size();
//声明col和prevCol辅助计算;
//col记录的是最新的计数情况;
//prevCol记录的是上一轮的计数情况;
std::vector<unsigned int> col(len2 + 1), prevCol(len2 + 1);
//迭代给prevCol赋值;
for (unsigned int i = 0; i < prevCol.size(); i++) prevCol[i] = i;
//迭代比较;扫描两字符串(n*m级的);
for (unsigned int i = 0; i < len1; i++) {
col[0] = i + 1;
//迭代遍历s2[j] in s2;
for (unsigned int j = 0; j < len2; j++)
//比较是否不一样,交换代价最小方式优先;
col[j + 1] = min(
min(prevCol[1 + j] + 1, col[j] + 1),
prevCol[j] + (s1[i] == s2[j] ? 0 : 1));
//col与prevCol交换其内容(swap函数:prevCol的类型要与该vector一样,大小可以不同.);
col.swap(prevCol);
}
//返回结果;
return prevCol[len2];
}
/*
-----------测试函数:测试识别准确率------------
*/
void TEST_ACC(){
//声明PipelinePR实例;
pr::PipelinePR prc("model/cascade.xml",
"model/HorizonalFinemapping.prototxt","model/HorizonalFinemapping.caffemodel",
"model/Segmentation.prototxt","model/Segmentation.caffemodel",
"model/CharacterRecognization.prototxt","model/CharacterRecognization.caffemodel",
"model/SegmenationFree-Inception.prototxt","model/SegmenationFree-Inception.caffemodel"
);
//申明文件流;
ifstream file;
//图片路径;
string imagename;
//声明计数变量;
int n = 0,correct = 0,j = 0,sum = 0;
//图片名文本文件路径;
char filename[] = "/Users/yujinke/Downloads/general_test/1.txt";
//图片文件根目录;
string pathh = "/Users/yujinke/Downloads/general_test/";
//打开图片名文本文件;
file.open(filename, ios::in);
//读取图片名文本文件;
while (!file.eof())
{
//读取一个图片名到imagename;
file >> imagename;
//该图片绝对路径;
string imgpath = pathh + imagename;
//输出信息;
std::cout << "------------------------------------------------" << endl;
cout << "图片名:" << imagename << endl;
//读入图片;
cv::Mat image = cv::imread(imgpath);
//显示图片;
//cv::imshow("image", image);
//cv::waitKey(0);
//申明一个存储该图片车牌信息检测结果的列表,读取结果;
std::vector<pr::PlateInfo> res = prc.RunPiplineAsImage(image,pr::SEGMENTATION_FREE_METHOD);
//置信度变量;
float conf = 0;
//置信度列表;
vector<float> con ;
//车牌名列表;
vector<string> name;
//遍历结果res;
for (auto st : res) {
//置信度大于0.1;
if (st.confidence > 0.1) {
//输出检测信息及置信度;
//std::cout << st.getPlateName() << " " << st.confidence << std::endl;
//加入置信度列表con;
con.push_back(st.confidence);
//加入车牌名列表name;
name.push_back(st.getPlateName());
//置信度综述累计;
//conf += st.confidence;
}
//置信度小于0.1;
else
cout << "no string" << endl;
}
//输出置信度总数;
//std::cout << conf << std::endl;
//置信度列表大小;
int num = con.size();
//置信度最大值变量;
float max = 0;
//声明存储车牌字符、车牌前两个字符、车牌正确的前两个字符的变量;
string platestr, chpr, ch;
//声明度量车牌识别误差的变量;
int diff = 0,dif = 0;
//遍历置信度列表;
for (int i = 0; i < num; i++) {
//寻找最大的置信度下标;
if (con.at(i) > max)
{
//最大置信度max;
max = con.at(i);
//platestr取"最可信"(最大置信度)的结果;
platestr = name.at(i);
}
}
//输出最大置信度;
//cout << "max:"<<max << endl;
//输出车牌字符;
cout << "string:" << platestr << endl;
//车牌前两个字符;
chpr = platestr.substr(0, 2);
//车牌正确的前两个字符的变量;
ch = imagename.substr(0, 2);
//度量距离;
diff = levenshtein_distance(imagename, platestr);
//dif值;
dif = diff - 4;
//输出;
cout << "差距:" <<dif << endl;
//累计;
sum += dif;
//错误计数;
if (ch != chpr) n++;
//正确计数;
if (diff == 0) correct++;
//总数计数;
j++;
}
//计算汉字识别准确率;
float cha = 1 - float(n) / float(j);
//输出;
std::cout << "------------------------------------------------" << endl;
//输出车牌总数;
cout << "车牌总数:" << j << endl;
//输出汉字识别准确率;
cout << "汉字识别准确率:"<<cha << endl;
//计算字符识别准确率;
float chaccuracy = 1 - float(sum - n * 2) /float(j * 8);
//输出;
cout << "字符识别准确率:" << chaccuracy << endl;
}
/*
-----------测试函数:画框------------
*/
void TEST_PIPELINE(){
//声明PipelinePR实例;
pr::PipelinePR prc("model/cascade.xml",
"model/HorizonalFinemapping.prototxt","model/HorizonalFinemapping.caffemodel",
"model/Segmentation.prototxt","model/Segmentation.caffemodel",
"model/CharacterRecognization.prototxt","model/CharacterRecognization.caffemodel",
"model/SegmentationFree.prototxt","model/SegmentationFree.caffemodel"
);
//读取一张图片;
cv::Mat image = cv::imread("/Users/yujinke/ClionProjects/cpp_ocr_demo/test.png");
//申明一个存储该图片车牌信息检测结果的列表,读取结果;
std::vector<pr::PlateInfo> res = prc.RunPiplineAsImage(image,pr::SEGMENTATION_FREE_METHOD);
//遍历结果;
for(auto st:res) {
//选择置信度大于0.75的结果;
if(st.confidence>0.75) {
//输出结果;
std::cout << st.getPlateName() << " " << st.confidence << std::endl;
//矩形框位置;
cv::Rect region = st.getPlateRect();
//绘制框;
cv::rectangle(image,cv::Point(region.x,region.y),cv::Point(region.x+region.width,region.y+region.height),cv::Scalar(255,255,0),2);
}
}
//显示结果;
cv::imshow("image",image);
//等待;
cv::waitKey(0);
}
/*
-----------测试函数:视频流------------
*/
void TEST_CAM()
{
//获取视频;
cv::VideoCapture capture("test1.mp4");
//申明帧变量;
cv::Mat frame;
//声明PipelinePR实例;
pr::PipelinePR prc("model/cascade.xml",
"model/HorizonalFinemapping.prototxt","model/HorizonalFinemapping.caffemodel",
"model/Segmentation.prototxt","model/Segmentation.caffemodel",
"model/CharacterRecognization.prototxt","model/CharacterRecognization.caffemodel",
"model/SegmentationFree.prototxt","model/SegmentationFree.caffemodel"
);
//循环读取帧;
while(1) {
//读取下一帧
//异常处理;
if (!capture.read(frame)) {
std::cout << "读取视频失败" << std::endl;
exit(1);
}
//图像旋转;
//cv::transpose(frame,frame);
//图像翻转;
//cv::flip(frame,frame,2);
//大小调整;
//cv::resize(frame,frame,cv::Size(frame.cols/2,frame.rows/2));
//识别结果;
std::vector<pr::PlateInfo> res = prc.RunPiplineAsImage(frame,pr::SEGMENTATION_FREE_METHOD);
//遍历识别结果
for(auto st:res) {
if(st.confidence>0.75) {
//输出结果;
std::cout << st.getPlateName() << " " << st.confidence << std::endl;
//矩形框位置;
cv::Rect region = st.getPlateRect();
//绘制框;
cv::rectangle(image,cv::Point(region.x,region.y),cv::Point(region.x+region.width,region.y+region.height),cv::Scalar(255,255,0),2);
}
}
//显示;
cv::imshow("image",frame);
//等待;
cv::waitKey(1);
}
}
/*
-----------主函数:运行------------
*/
int main()
{
//准确率测试;
TEST_ACC();
//视频画框测试;
//TEST_CAM();
//图片画框测试;
//TEST_PIPELINE();
return 0 ;
}