java车牌识别字符分割_opencv 车牌字符分割 ANN网络识别字符
最近在复习OPENCV的知识,学习caffe的深度神经网络,正好想起以前做过的车牌识别项目,可以拿出来研究下
以前的环境是VS2013和OpenCV2.4.9,感觉OpenCV2.4.9是个经典版本啊!不过要使用caffe模型的话,还是要最新的OpenCV3.3更合适!
一、车牌图片库
以前也是网上下的,如果找不到的小伙伴可以从我这儿下: 链接:http://pan.baidu.com/s/1hrQF92G 密码:43jl
里面有数字 “0-9”,字母“A-Z”的训练图片各50张。
测试车牌图片当时是从他人得到已经定位到车牌的图片,类似如下:
目标当然就是对这些车牌图片进行预处理,单字符分割,单字符识别!
二、预处理
图像的预处理做来做去就是滤波去噪,光照补偿,灰度/二值化,形态学基本操作等等。这些图片都是自然场景得到所以基本的去噪操作可以做一下,然后为了单字符分割,灰度化和形态学可以结合效果调整。
光照补偿其实一直是个问题,大多数有直方图均衡化,亮度参考白,利用公式统计补偿图片。这方面也可以结合图像增强方法来做!笔者当时觉得前两者对大多数场景已经适用。
二值化可以使用 cv::threshold函数,如:
1 Mat t1=imread("2.png",1);2 cvtColor(inimg, gimg, CV_BGR2GRAY);3 threshold(gimg, gimg, 100, 255, CV_THRESH_BINARY);4 imshow("gimg", gimg);
第一行imread(),由于flag设为1所以读的是彩图,采用cvtColor函数转化为灰度图。如果你读入就是灰度图可以省略第二行代码。第三行就是转化为二值化函数,阈值100可以修改,在灰度对比不明显是有必要!
效果:
如果预处理做的好,某些小的白色区域是可以去掉的。这个效果也可以识别。
同时可以发现车牌外围被一圈白色包围,如若能去除外围白色,对于单字符分割更有益。但其实通过寻找列像素之间的变化,白色区域只是影响了阈值不会对结果太大影响。
想要去除白色外圈可以参考:http://blog.csdn.net/u011630458/article/details/43733057
如果想要使用直方图均衡化,OPENCV有equalizeHist(inputmat, outputmat);非常方便,但是效果不好。
使用直方图均衡化后的上述车牌二值化图片:
效果更惨烈了,因为均衡化就是让直方图的像素分布更加平衡,上图黑色多,均衡之后自然白色多了,反而不好!
二、单字符分割
单字符分割主要策略就是检测列像素的总和变化,因为没有字符的区域基本是黑色,像素值低;有字符的区域白色较多,列像素和就变大了!
列像素变化的阈值是个问题,看到很多博客是固定的阈值进行检测,除非你处理后的二值化图像非常完美,不然有的图片混入了白色区域就会分割错误!而且对于得到分割宽度如果太小也应该使用策略进行剔除,没有一定的宽度限制分割后的图片可能是很多个窄窄的小区域。。。
1 int getColSum(Mat& bimg, intcol)2 {3 int height =bimg.rows;4 int sum = 0;5 for (int i = 1; i < height; i++)6 {7 sum += bimg.at(i, col);8 }9 cout << sum <
13 int cutLeft(Mat& src, int Tsum, int right)//左右切割
14 {15 intleft;16 left = 0;17
18 inti;19 for (i=0; i < src.cols; i++)20 {21 int colValue =getColSum(src, i);22 if (colValue>Tsum)23 {24 left =i;25 break;26 }27 }28 int roiWidth=src.cols/7;29 for (; i < src.cols; i++)30 {31 int colValue =getColSum(src, i);32 if (colValue
38 {39 roiWidth = right -left;40 break;41 }42
43 }44 }45 returnroiWidth;46 }47
48 int getOne(Mat&inimg)49 {50 Mat gimg,histimg;51 cvtColor(inimg, gimg, CV_BGR2GRAY);52 equalizeHist(gimg,histimg);53 //imshow("histimg", histimg);
54 threshold(gimg, gimg, 100, 255, CV_THRESH_BINARY);55 imshow("gimg", gimg);56 waitKey(0);57
58 int psum=0;59 for (int i = 0; i < gimg.cols; i++)60 {61 psum+=getColSum(gimg, i);62 }63 cout <
67 returnroiWid;68 }
View Code
笔者思路也很简单:
首先统计所有列像素的总和,取其列像素的均值作为参考标准之一(也可以选用其他数学指标参考),列像素的阈值Tsum设置为列像素均值的百分比(如60%,是情景定)。
利用cutLeft()函数对图片进行列扫描,将列像素超过阈值的列标记为左边,再继续寻找右边,将满足阈值的右边进行标记。左右相减即可得到宽度分割字符。
考虑到车牌中只有7个字符,所以先判断得到宽度大小,如果小于总宽的七分之一视为干扰放弃;其实也可以加大到总宽的8分之一(因为车牌中间可能有连接符)。
getColSum()函数是求一列的像素和,这里用到了.at<> 方式,其实还有别的方法也可以,只要获得当前的像素值,并累加整列即可!
上图车牌的分割效果:
因为第三张有车牌的连接符,所以导致第三张和第四张稍有瑕疵,但总体分割还是满意的!
三、单字符识别
只论字符识别其实有不少选择方案,一开始笔者尝试了ORB特征,想利用特征匹配计算相似度来判断最优的字符结果。ORB特征相比SURF/SIFT更加快速,而且特征不变性也不错。但是在匹配时发现单字符的图片像素点太少,提取的特征点数极少,无法得到较好的匹配结果,只能放弃!
其实也有模板匹配来做字符识别的,但是OPENCV提供的模板匹配对于从同一副图片提取的模板图去匹配样本图效果很好,不是同一副图片时效果很一般。因为笔者用OPENCV的模板匹配一般用来找重复区域。
OCR识别是可以完全用在此处的,OCR识别甚至可以识别汉字,安装OCR的库之后就可以尝试一番!
笔者最后选择了神经网络ANN来做字符分类识别,利用SVM也可以都是分类器之一。使用神经网络可以和caffe的mnist模型有所对比的感觉!
1 void ann10(Mat&testroi)2 {3 const string fileform = "*.png";4 const string perfileReadPath = "E:\\vswork\\charSamples";5
6 const int sample_mun_perclass = 50;//训练字符每类数量
7 const int class_mun = 34;//训练字符类数 0-9 A-Z 除了I、O
8
9 const int image_cols = 8;10 const int image_rows = 16;11 stringfileReadName,fileReadPath;12 char temp[256];13
14 float trainingData[class_mun*sample_mun_perclass][image_rows*image_cols] = { { 0 } };//每一行一个训练样本
15 float labels[class_mun*sample_mun_perclass][class_mun] = { { 0 } };//训练样本标签
16
17 for (int i = 0; i <= class_mun - 1; i++)//不同类
18 {19 //读取每个类文件夹下所有图像
20 int j = 0;//每一类读取图像个数计数
21
22 if (i <= 9)//0-9
23 {24 sprintf(temp, "%d", i);25 //printf("%d\n", i);
26 }27 else//A-Z
28 {29 sprintf(temp, "%c", i + 55);30 //printf("%c\n", i+55);
31 }32
33 fileReadPath = perfileReadPath + "/" + temp + "/" +fileform;34 cout << "文件夹" << fileReadPath <
36 HANDLE hFile;37 LPCTSTR lpFileName = StringToWchar(fileReadPath);//指定搜索目录和文件类型,如搜索d盘的音频文件可以是"D:\\*.mp3"
38 WIN32_FIND_DATA pNextInfo; //搜索得到的文件信息将储存在pNextInfo中;
39 hFile = FindFirstFile(lpFileName, &pNextInfo);//请注意是 &pNextInfo , 不是 pNextInfo;
40 if (hFile ==INVALID_HANDLE_VALUE)41 {42 continue;//搜索失败
43 }44 //do-while循环读取
45 do
46 {47 if (pNextInfo.cFileName[0] == '.')//过滤.和..
48 continue;49 j++;//读取一张图50 //wcout<
53 Mat srcImage = imread(perfileReadPath + "/" + temp + "/" +WcharToChar(pNextInfo.cFileName), CV_LOAD_IMAGE_GRAYSCALE);54 Mat resizeImage;55 Mat trainImage;56 Mat result;57
58 resize(srcImage, resizeImage, Size(image_cols, image_rows), (0, 0), (0, 0), CV_INTER_AREA);//使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现
59 threshold(resizeImage, trainImage, 0, 255, CV_THRESH_BINARY |CV_THRESH_OTSU);60
61 for (int k = 0; k((int)k/8,(int)k%8);//(float)train_image.data[k];65 //cout<(k/8,k%8)<
66 }67
68 } while (FindNextFile(hFile, &pNextInfo) && j
69
70 }71
72 //Set up training data Mat
73 Mat trainingDataMat(class_mun*sample_mun_perclass, image_rows*image_cols, CV_32FC1, trainingData);74 cout << "trainingDataMat——OK!" <
76 //Set up label data
77 for (int i = 0; i <= class_mun - 1; ++i)78 {79 for (int j = 0; j <= sample_mun_perclass - 1; ++j)80 {81 for (int k = 0; k < class_mun; ++k)82 {83 if (k ==i)84 if (k == 18)85 {86 labels[i*sample_mun_perclass + j][1] = 1;87 }88 else if (k == 24)89 {90 labels[i*sample_mun_perclass + j][0] = 1;91 }92 else
93 {94 labels[i*sample_mun_perclass + j][k] = 1;95 }96 else
97 labels[i*sample_mun_perclass + j][k] = 0;98 }99 }100 }101 Mat labelsMat(class_mun*sample_mun_perclass, class_mun, CV_32FC1, labels);102 cout << "labelsMat:" <
106 cout << "labelsMat——OK!" <
108 //训练代码
109
110 cout << "training start...." <
113 CvANN_MLP_TrainParams params;114 params.train_method =CvANN_MLP_TrainParams::BACKPROP;115 params.bp_dw_scale = 0.001;116 params.bp_moment_scale = 0.1;117 params.term_crit = cvTermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 10000, 0.0001); //设置结束条件118 //params.train_method=CvANN_MLP_TrainParams::RPROP;119 //params.rp_dw0 = 0.1;120 //params.rp_dw_plus = 1.2;121 //params.rp_dw_minus = 0.5;122 //params.rp_dw_min = FLT_EPSILON;123 //params.rp_dw_max = 50.;124
125 //Setup the BPNetwork
126 Mat layerSizes = (Mat_(1, 5) << image_rows*image_cols, 128, 128, 128, class_mun);127 bp.create(layerSizes, CvANN_MLP::SIGMOID_SYM, 1.0, 1.0);//CvANN_MLP::SIGMOID_SYM128 //CvANN_MLP::GAUSSIAN129 //CvANN_MLP::IDENTITY
130 cout << "training...." <
133 bp.save("../bpcharModel.xml"); //save classifier
134 cout << "training finish...bpModel.xml saved" <
ann10
ann10函数主要完成读取图片训练ANN网络的功能。
注意点:
修改图片文件类型 fileform;
修改训练图片路径 perfileReadPath等;
修改训练图片数量 sample_mun_perclass;
修改训练类别数 class_mun;(34类是因为IO与10很像,所以少了两类);
image_cols和image_rows根据自己图片情况修改;
观察代码发现训练文件在工程目录的 bpcharModel.xml;之后调用该网络模型即可,网上有很多网络调用和网络训练没有分开,这样你每预测分类一个字符都要重新训练网络会相当浪费时间的,笔者的渣电脑训练一次就要几分钟,每次分类都训练时间有点伤不起。。。真正的实际应用也是用训练好的网络参数直接调用,速度很快。就像caffe中的深度神经网络,使用网络分类时也只是调用生成好的caffemodel和标签、solver文件就行了,如果还要重新训练一小时根本没有实用性。
1 voidpredictann(Mat testroi)2 {3 //测试神经网络
4 CvANN_MLP bp;5 bp.load("E:\\vswork\\CarNumRecog\\bpcharModel.xml");6 const int image_cols = 8;7 const int image_rows = 16;8
9 cout << "测试:" <
11 Mat test_temp;12 resize(testroi, test_temp, Size(image_cols, image_rows), (0, 0), (0, 0), CV_INTER_AREA);//使用象素关系重采样。当图像缩小时候,该方法可以避免波纹出现
13 threshold(test_temp, test_temp, 0, 255, CV_THRESH_BINARY |CV_THRESH_OTSU);14 Mat_sampleMat(1, image_rows*image_cols);15 for (int i = 0; i(0, i) = (float)test_temp.at(i / 8, i % 8);18 }19
20 Mat responseMat;21 bp.predict(sampleMat, responseMat);22 Point maxLoc;23 double maxVal = 0;24 minMaxLoc(responseMat, NULL, &maxVal, NULL, &maxLoc);25 char temp[256];26
27 if (maxLoc.x <= 9)//0-9
28 {29 sprintf(temp, "%d", maxLoc.x);30 //printf("%d\n", i);
31 }32 else//A-Z
33 {34 sprintf(temp, "%c", maxLoc.x + 55);35 //printf("%c\n", i+55);
36 }37
38 cout << "识别结果:" << temp << "相似度:" << maxVal * 100 << "%" <
42 return;43 }
predictann
predictann函数就是调用ann10函数生成的网络模型文件,进行预测分类的功能。
上述车牌的单字符识别效果如下:
可以看到有的相似度很高,有的却很低,也有一些识别错误的,我不再显示。。。
相比之前使用的caffe mnist识别率真的是差距有点大,以后有机会将mnist的模型来识别车牌字符试试~~
度盘失效了,附上我的github地址,里面会传数据集和完整代码!欢迎大家star 和 fork 我~~
https://github.com/chenzhefan/CarNumRecognize.git
java车牌识别字符分割_opencv 车牌字符分割 ANN网络识别字符相关推荐
- opencv 车牌字符分割 ANN网络识别字符
opencv 车牌字符分割 ANN网络识别字符 原文参考:https://www.cnblogs.com/chenzhefan/p/7629441.html 最近在复习OPENCV的知识,学习ca ...
- python车牌字符分割_OpenCV+Python识别车牌和字符分割的实现
本篇文章主要基于python语言和OpenCV库(cv2)进行车牌区域识别和字符分割,开篇之前针对在python中安装opencv的环境这里不做介绍,可以自行安装配置! 车牌号检测需要大致分为四个部分 ...
- java车牌识别字符分割_车牌识别LPR(六)-- 字符分割
第六篇:字符分割 在知道了车牌字符的规律之后,可以根据车牌的特点对字符进行分割.一般最容易想到的方法就是根据车牌投影.像素统计特征对车牌图像进行字符分割的方法.是一种最常用的.最基本的.最简单的车牌字 ...
- 【车牌识别】基于matlab GUI阈值分割车牌识别(带面板)【含Matlab源码 721期】
⛄一.车牌识别简介 1 车牌图像处理 车牌图像处理主要有五个组成部分:图像灰度化.图像二值化.图像边缘检测.图像形态学运算和图像滤波处理.它是车牌识别系统中最根本且最基础的操作,车牌图像处理的好坏情况 ...
- 【MATLAB图像处理实用案例详解(3)】—— 基于阈值分割的车牌定位识别
目录 一.背景意义 二.理论基础 2.1 车牌区域分割: 2.2 车牌定位及裁剪 三.算法流程 3.1 车牌图像处理 3.1.1 图像灰度化 3.1.2 图像二值化 3.1.3 图像边缘检测 3.1. ...
- Dataset之图片数据增强:设计自动生成汽车车牌图片算法(cv2+PIL)根据随机指定七个字符生成逼真车牌图片数据集(自然场景下+各种噪声效果)可视化
Dataset之图片数据增强:设计自动生成汽车车牌图片算法(cv2+PIL)根据随机指定七个字符生成逼真车牌图片数据集(自然场景下+各种噪声效果)可视化 导读 设计自动生成汽车车牌图片算法,基于cv2 ...
- Dataset之图片数据增强:设计自动生成(高级封装之命令行解析实现)汽车车牌图片算法(cv2+PIL+argparse)根据随机指定七个字符自动生成逼真车牌图片数据集(带各种噪声效果)
Dataset之图片数据增强:设计自动生成(高级封装之命令行解析实现)汽车车牌图片算法(cv2+PIL+argparse)根据随机指定七个字符自动生成逼真车牌图片数据集(带各种噪声效果) 导读 设计自 ...
- java车牌识别系统_基于jsp的车牌识别系统-JavaEE实现车牌识别系统 - java项目源码...
基于jsp+servlet+pojo+mysql实现一个javaee/javaweb的车牌识别系统, 该项目可用各类java课程设计大作业中, 车牌识别系统的系统架构分为前后台两部分, 最终实现在线上 ...
- 车牌识别算法_PC端车牌识别SDK融入好算法
PC端车牌识别SDK介绍 易泊PC端车牌识别SDK融合了车牌定位.车牌字符切分.车牌字符识别等算法,使该系统具有识别效率高.速度快.适应性强.使用方便等优势,技术处于国际先进水平.大力发展机器人.人工 ...
- matlab车牌识别课程设计,matlab车牌识别课程设计报告模板(附源代码).doc
您所在位置:网站首页 > 海量文档  > 计算机 > matlab matlab车牌识别课程设计报告模板(附源代码). ...
最新文章
- 统计学习方法笔记(七)-线性支持向量机原理及python实现
- DirectFB编译环境
- c++-内存管理-G4.9
- 算法(7)-leetcode-explore-learn-数据结构-数组-小结
- python教育学_跟着老男孩教育学Python开发【第三篇】:Python函数
- 顺丰不行了吗?对快递行业的深度理解
- Netty工作笔记0005---NIO介绍说明
- C语言实现查找一组数中的众数
- 使用Qt学习C语言编程1
- 基线管理之Windows安全配置
- 【优化布局】基于粒子群算法求解带出入点的车间布局优化问题附matlab代码
- checkbox全选、清除、反选
- ZJNU 2314-Sleepy Cow Herding
- 运放的原理、应用、参数和命名规则
- table固定列html5,css+js简单实现table固定首行首列
- vue 3.0学习1
- emui系统就是鸿蒙吗,华为EMUI是不是鸿蒙系统
- 交换机开发(一)—— 交换机的工作原理
- 华为离职副总徐家骏给任正非的辞职信,振聋发聩
- 【SQL】LeetCode-Trips and Users