博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
在ARM-Linux下实现车牌识别(二)------车牌识别
阅读量:4298 次
发布时间:2019-05-27

本文共 12616 字,大约阅读时间需要 42 分钟。

之前说到,把车牌区域提前出来后,就可以着手识别程序了。先使用SVM判断是不是车牌。这里为了提高运行速度,板子资源有限,程序里我把svm训练部分注释掉了,假设每次都能找到车牌,实际使用时,还是要加上svm的。

      然后对图像进行分割,我们的分类器只能对数字一个一个地识别,所以把每个数字分割出来,每个字符归一化为20*20的字符。
      基本思想是先用findContours()函数把基本轮廓找出来,然后通过简单验证以确认是否为数字的轮廓。对于那些通过验证的轮廓,接下去会用boundingRect()找出它们的包围盒。
      分割完后就可以进行识别了,字符识别使用ANN算法采用三层神经网络,识别需要用到一些xml文件,这些文件需要用分类器和大量样本做训练,提取他们的特征、,让机器去“学习”(利用训练好的XML文件去预测图像中车牌 ),我找的这三个xml数据集,说实话,不太好用,准确率一般般,有兴趣的可以自己训练。

      完整程序如下,里面有详细注释了:

#include 
#include
#include
#include
#include
#include
#include
#include
#include
using namespace cv; using namespace std;//车牌宽高比为520/110=4.727272左右,误差不超过40%//车牌高度范围在15~125之间,视摄像头距离而定(图像大小)bool verifySizes_closeImg(const RotatedRect & candidate){ float error = 0.4;//误差40% const float aspect = 4.7272;//44/14; //长宽比 int min = 15*aspect*15;//20*aspect*20; //面积下限,最小区域 int max = 125*aspect*125;//180*aspect*180; //面积上限,最大区域 float rmin = aspect - aspect*error; //考虑误差后的最小长宽比 float rmax = aspect + aspect*error; //考虑误差后的最大长宽比 int area = candidate.size.height * candidate.size.width;//计算面积 float r = (float)candidate.size.width/(float)candidate.size.height;//计算宽高比 if(r <1) r = 1/r; if( (area < min || area > max) || (r< rmin || r > rmax) )//满足条件才认为是车牌候选区域 return false; else return true;}void RgbConvToGray(const Mat& inputImage,Mat & outpuImage) //g = 0.3R+0.59G+0.11B{ outpuImage = Mat(inputImage.rows ,inputImage.cols ,CV_8UC1); for (int i = 0 ;i
(i); const Vec3b * ptrRgb = inputImage.ptr
(i); for (int j = 0 ;j
&rects_optimal, vector
& output_area ){ float r,angle; for (int i = 0 ;i< rects_optimal.size() ; ++i) { //旋转区域 angle = rects_optimal[i].angle; r = (float)rects_optimal[i].size.width / (float) (float)rects_optimal[i].size.height; if(r<1) angle = 90 + angle;//旋转图像使其得到长大于高度图像。 Mat rotmat = getRotationMatrix2D(rects_optimal[i].center , angle,1);//获得变形矩阵对象 Mat img_rotated; warpAffine(intputImg ,img_rotated,rotmat, intputImg.size(),CV_INTER_CUBIC); imwrite("car_rotated.jpg",img_rotated);//得到旋转图像 //裁剪图像 Size rect_size = rects_optimal[i].size; if(r<1) swap(rect_size.width, rect_size.height); //交换高和宽 Mat img_crop; getRectSubPix(img_rotated ,rect_size,rects_optimal[i].center , img_crop );//图像切割 //用光照直方图调整所有裁剪得到的图像,使具有相同宽度和高度,适用于训练和分类 Mat resultResized; //别人写的: /*resultResized.create(33,144,CV32FC1); resize(img_crop , resultResized,resultResized.size() , 0,0,INTER_CUBIC); resultResized.convertTo(resultResized, CV32FC1); resultResized = resultResized.reshape(1,1);*/ resultResized.create(33,144,CV_8UC3);//CV32FC1???? resize(img_crop , resultResized,resultResized.size() , 0,0,INTER_CUBIC); Mat grayResult; RgbConvToGray(resultResized ,grayResult); //blur(grayResult ,grayResult,Size(3,3)); equalizeHist(grayResult,grayResult); output_area.push_back(grayResult); }}bool char_verifySizes(const RotatedRect & candidate){ float aspect = 45.0f/77.0f;//45.0f/90.0f; float width,height; if (candidate.size.width >=candidate.size.height) { width = (float) candidate.size.height; height = (float) candidate.size.width; } else { width = (float) candidate.size.width; height = (float)candidate.size.height; } //这样确定是了高比宽要高 float charAspect = (float) width/ (float)height;//宽高比 float error = 0.35;//0.5; float minHeight = 15; //最小高度11 float maxHeight = 28;//33; //最大高度33 float minAspect = 0.15;//0.05; //考虑到数字1,最小长宽比为0.15 float maxAspect = 1.0; if( charAspect > minAspect && charAspect <= 1.0 && height>= minHeight && height< maxHeight) //非0像素maxAspect长宽比、高度需满足条件 return true; else return false;}void char_sort(vector
& in_char ) //对字符区域进行排序{ vector
out_char; const int length = 7; //7个字符 int index[length] = {0,1,2,3,4,5,6}; float centerX[length]; for (int i=0;i < length ; ++ i) { centerX[i] = in_char[i].center.x; } for (int j=0;j
= j;i--) if (centerX[i] > centerX[i+1]) { float t=centerX[i]; centerX[i]=centerX[i+1]; centerX[i+1]=t; int tt = index[i]; index[i] = index[i+1]; index[i+1] = tt; } } for(int i=0;i
& dst_mat)//得到20*20的标准字符分割图像{ Mat img_threshold; threshold(inputImg ,img_threshold , 180,255 ,CV_THRESH_BINARY );//二值化 //Mat element = getStructuringElement(MORPH_RECT ,Size(3 ,3)); //闭形态学的结构元素 //morphologyEx(img_threshold ,img_threshold,CV_MOP_CLOSE,element); //形态学处理 //imshow ("img_thresho00ld",img_threshold); //waitKey(); Mat img_contours; img_threshold.copyTo(img_contours); if (!clearLiuDing(img_contours)) { std::cout << "不是车牌" << endl; } else { //imshow("img_cda",img_contours); //waitKey(); Mat result2; inputImg.copyTo(result2); vector < vector
> contours; findContours(img_contours ,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE); vector< vector
> ::iterator itc = contours.begin(); vector
char_rects; drawContours(result2,contours,-1, Scalar(0,255,255), 1); while( itc != contours.end()) { RotatedRect minArea = minAreaRect(Mat( *itc )); //返回每个轮廓的最小有界矩形区域 Point2f vertices[4]; minArea.points(vertices); if(!char_verifySizes(minArea)) //判断矩形轮廓是否符合要求 { itc = contours.erase(itc); } else { ++itc; char_rects.push_back(minArea); } } /*imshow("char1",char_rects[1]); imshow("char2",char_rects[2]); imshow("char3",char_rects[3]); imshow("char4",char_rects[4]); imshow("char5",char_rects[5]); imshow("char6",char_rects[6]); imshow("char7",char_rects[0]); waitKey();*/ char_sort(char_rects); //对字符排序 vector
char_mat; for (int i = 0; i
char_mat[i].cols?char_mat[i].rows:char_mat[i].cols; dstTri[0] = Point2f( 0.0, 0.0 ); dstTri[1] = Point2f( length, 0.0 ); dstTri[2] = Point2f( 0.0, length ); train_mat = getAffineTransform( srcTri, dstTri ); dst_mat[i]=Mat::zeros(length,length,char_mat[i].type()); warpAffine(char_mat[i],dst_mat[i],train_mat,dst_mat[i].size(),INTER_LINEAR,BORDER_CONSTANT,Scalar(0)); //resize(dst_mat[i],dst_mat[i],Size(20,20),0,0,CV_INTER_CUBIC); //尺寸调整为20*20 resize(dst_mat[i],dst_mat[i],Size(20,20));//每个字符归一化为20*20的字符 } for (int i = 1; i< char_mat.size();++i) { srcTri[0] = Point2f( 0,0 ); srcTri[1] = Point2f( char_mat[i].cols - 1, 0 ); srcTri[2] = Point2f( 0, char_mat[i].rows - 1 ); length = char_mat[i].rows > char_mat[i].cols?char_mat[i].rows:char_mat[i].cols; dstTri[0] = Point2f( 0.0, 0.0 ); dstTri[1] = Point2f( length, 0.0 ); dstTri[2] = Point2f( 0.0, length ); train_mat = getAffineTransform( srcTri, dstTri ); dst_mat[i]=Mat::zeros(length,length,char_mat[i].type()); warpAffine(char_mat[i],dst_mat[i],train_mat,dst_mat[i].size(),INTER_LINEAR,BORDER_CONSTANT,Scalar(0)); //resize(dst_mat[i],dst_mat[i],Size(20,20),0,0,CV_INTER_CUBIC); //尺寸调整为20*20 resize(dst_mat[i],dst_mat[i],Size(20,20));//每个字符归一化为20*20的字符 } }}void features(const Mat & in , Mat & out ,int sizeData){ // 分别在水平方向和垂直方向上 创建累积直方图 Mat vhist = projectHistogram(in , 1); //水平直方图 Mat hhist = projectHistogram(in , 0); //垂直直方图 // 低分辨率图像 // 低分辨率图像中的每一个像素都将被保存在特征矩阵中 Mat lowData; resize(in , lowData ,Size(sizeData ,sizeData )); //特征矩阵的列数 int numCols = vhist.cols + hhist.cols + lowData.cols * lowData.cols; out = Mat::zeros(1, numCols , CV_32F); // 向特征矩阵赋值 int j = 0; for (int i =0 ;i
(j) = vhist.at
(i); j++; } for (int i=0 ; i < hhist.cols ;++i)// 然后把竖直方向累积直方图的值,存到特征矩阵中 { out.at
(j) = hhist.at
(i); } for(int x =0 ;x
(j) = (float)lowData.at
(x,y); j++; } }}void ann_train(CvANN_MLP &ann ,int numCharacters, int nlayers, string str)//http://blog.csdn.net/yiqiudream/article/details/51712497{ Mat trainData ,classes; FileStorage fs; fs.open(str, FileStorage::READ);//str是文件名字 fs["TrainingData"] >>trainData; fs["classes"] >>classes; //CvANN_MLP bp; //Set up BPNetwork's parameters //CvANN_MLP_TrainParams params; //params.train_method=CvANN_MLP_TrainParams::BACKPROP; //params.bp_dw_scale=0.1; //params.bp_moment_scale=0.1; Mat layerSizes(1,3,CV_32SC1); layerSizes.at
( 0 ) = trainData.cols; layerSizes.at
( 1 ) = nlayers; //隐藏神经元数,可设为3 layerSizes.at
( 2 ) = numCharacters; //样本类数为34 //layerSizes.at
( 3 ) = numCharacters ; ann.create(layerSizes , CvANN_MLP::SIGMOID_SYM ); //初始化ann Mat trainClasses; trainClasses.create(trainData.rows , numCharacters ,CV_32FC1); for (int i =0;i< trainData.rows; i++) { for (int k=0 ; k< trainClasses.cols ; k++ ) { if ( k == (int)classes.at
(i)) { trainClasses.at
(i,k) = 1 ; } else trainClasses.at
(i,k) = 0; } } Mat weights(1 , trainData.rows , CV_32FC1 ,Scalar::all(1) ); ann.train( trainData ,trainClasses , weights);}void svm_train(CvSVM & svmClassifier){ FileStorage fs; fs.open("SVM.xml" , FileStorage::READ); Mat SVM_TrainningData; Mat SVM_Classes; fs["TrainingData"] >>SVM_TrainningData; fs["classes"] >>SVM_Classes; CvSVMParams SVM_params; SVM_params.kernel_type = CvSVM::LINEAR; svmClassifier.train(SVM_TrainningData,SVM_Classes ,Mat(),Mat(),SVM_params); //SVM训练模型 fs.release();}int main(int argc, char* argv[]){ Mat img_input= imread("./car.jpg");//加载图片 if(img_input.empty())//如果读入图像失败 { cout << "Can not load image" << endl; return -1; } Mat hsvImg ; cvtColor(img_input,hsvImg,CV_BGR2HSV);//RGB模型转换成HSV模型 imwrite("car_hsv.jpg",hsvImg);//看下hsv效果 vector
hsvSplit; split(hsvImg,hsvSplit);//将图像的各个通道分离 equalizeHist(hsvSplit[2],hsvSplit[2]);//直方图均衡化,提高图像的质量 merge(hsvSplit,hsvImg);//将分离的多个单通道合成一幅多通道图像 imwrite("car_hsv1.jpg",hsvImg);//看下处理效果 const int min_blue =100;//最小蓝车区域 const int max_blue =240;//最大蓝车区域 int avg_h = (min_blue+max_blue)/2; int channels = hsvImg.channels(); int nRows = hsvImg.rows; //图像数据列需要考虑通道数的影响; int nCols = hsvImg.cols * channels; if (hsvImg.isContinuous())//连续存储的数据,按一行处理 { nCols *= nRows; nRows = 1; } int i, j; unsigned char* p; const float minref_sv = 64; //参考的S V的值 const float max_sv = 255; // S V 的最大值 for (i = 0; i < nRows; ++i)//根据蓝色在HSV在的区域每个通道的取值范围将此作为阈值,提取出图片中蓝色部分作为备选区域 { p = hsvImg.ptr
(i);//有效提高了车牌和车色颜色在不相差较大的情况下的识别率 for (j = 0; j < nCols; j += 3)//致命问题:蓝色的车和蓝色的牌照? { int H = int(p[j]); //0-180 int S = int(p[j + 1]); //0-255 int V = int(p[j + 2]); //0-255 bool colorMatched = false; if (H > min_blue && H < max_blue) { int Hdiff = 0; float Hdiff_p = float(Hdiff) / 40; float min_sv = 0; if (H > avg_h) { Hdiff = H - avg_h; } else { Hdiff = avg_h - H; } min_sv = minref_sv - minref_sv / 2 * (1 - Hdiff_p); if ((S > 70&& S < 255) &&(V > 70 && V < 255)) colorMatched = true; } if (colorMatched == true) { p[j] = 0; p[j + 1] = 0; p[j + 2] = 255; } else { p[j] = 0; p[j + 1] = 0; p[j + 2] = 0; } } } Mat src_grey; Mat img_threshold; vector
hsvSplit_done; split(hsvImg, hsvSplit_done); src_grey = hsvSplit_done[2];//提取黑色分量 imwrite("car_hsvSplit.jpg",src_grey);//查看分离通道出来的车牌 vector
rects; Mat element = getStructuringElement(MORPH_RECT ,Size(17 ,3)); //闭形态学的结构元素 morphologyEx(src_grey ,img_threshold,CV_MOP_CLOSE,element); //闭运算,先膨胀后腐蚀,连通近邻区域(填补白色区域的间隙) morphologyEx(img_threshold,img_threshold,MORPH_OPEN,element);//形态学处理 imwrite("car_morphology.jpg",img_threshold);//查看threshold vector< vector
> contours;//寻找车牌区域的轮廓 findContours(img_threshold ,contours,CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);//只检测外轮廓。存储所以轮廓点 //绘制轮廓 /*for(int find=0; find < contours.size(); find++) drawContours(img_threshold, contours, find, Scalar(255), 2); imwrite("car_contours.jpg",img_threshold);//查看轮廓*/ //对候选的轮廓进行进一步筛选 vector< vector
> ::iterator itc = contours.begin(); while( itc != contours.end()) { RotatedRect mr = minAreaRect(Mat( *itc )); //返回每个轮廓的最小有界矩形区域 if(!verifySizes_closeImg(mr)) //判断矩形轮廓是否符合要求 { itc = contours.erase(itc); } else { rects.push_back(mr); ++itc; } } vector
output_area; normal_area(img_input ,rects,output_area); //获得144*33的候选车牌区域output_area imwrite("car_area.jpg",output_area[0]);//得到候选区域,这里可能会获得多个候选区域,最好使用svm训练一下 //(二)添加如下: //CvSVM svmClassifier;//为了运行速度,我就把这里注释掉了,这样会降低准确度 //svm_train(svmClassifier); //使用SVM对正负样本进行训练,为了运行速度,我就把这里注释掉了,这样会降低准确度 vector
plates_svm; //需要把候选车牌区域output_area图像中每个像素点作为一行特征向量,后进行预测 for(int i=0;i< output_area.size(); ++i)//实际情况下应该加上SVM训练,我这里是学习测试 { cout << "output " << i << endl; Mat img = output_area[i]; Mat p = img.reshape(1,1); p.convertTo(p,CV_32FC1); //int response = (int)svmClassifier.predict( p );//为了运行速度,我就把这里注释掉了,这样会降低准确度 //if (response == 1)//为了运行速度,我就把这里注释掉了,这样会降低准确度 plates_svm.push_back(output_area[i]); //保存预测结果 } //从SVM预测获取车牌区域分割得到字符区域 vector
char_seg; char_segment(plates_svm[0],char_seg);//对车牌区域中字符进行分割 imwrite("char0.jpg",char_seg[0]);//显示七个字符 imwrite("char1.jpg",char_seg[1]); imwrite("char2.jpg",char_seg[2]); imwrite("char3.jpg",char_seg[3]); imwrite("char4.jpg",char_seg[4]); imwrite("char5.jpg",char_seg[5]); imwrite("char6.jpg",char_seg[6]); //获得7个字符矩阵的相应特征矩阵 vector
char_feature; char_feature.resize(7); for (int i =0;i
char_result; //classify(ann_classify,char_feature,char_result); char_result.resize(char_feature.size()); for (int i=0;i

源代码和xml文件下载:https://download.csdn.net/download/guet_kite/10930196

代码大部分都是抄网上一篇文章的,文章地址我找不到了…
不过给出几个参考链接,可以看看里面的。
参考:
[1]:使用opencv的SVM和神经网络实现车牌识别
[2]:OpenCV自学笔记17. 基于SVM和神经网络的车牌识别
[3]:OpenCV实现车牌识别,OCR分割,ANN神经网络
 

你可能感兴趣的文章
Java:按值传递还是按引用传递详细解说
查看>>
全面理解Java内存模型
查看>>
Java中Synchronized的用法
查看>>
阻塞队列
查看>>
linux的基础知识
查看>>
接口技术原理
查看>>
五大串口的基本原理
查看>>
PCB设计技巧与注意事项
查看>>
linux进程之间通讯常用信号
查看>>
main函数带参数
查看>>
PCB布线技巧
查看>>
关于PCB设计中过孔能否打在焊盘上的两种观点
查看>>
PCB反推理念
查看>>
京东技术架构(一)构建亿级前端读服务
查看>>
git 提示:error: unable to rewind rpc post data - try increasing http.postBuffer
查看>>
php 解决json_encode中文UNICODE转码问题
查看>>
LNMP 安装 thinkcmf提示404not found
查看>>
PHP empty、isset、innull的区别
查看>>
apache+nginx 实现动静分离
查看>>
通过Navicat远程连接MySQL配置
查看>>