本文和下文用于开源项目OpenCV的车牌识别的学习!!!
车牌识别的过程可分为四步:1)车牌图像切割;2)车牌图像分类;3)车牌字符切割;4)车牌字符分类;
1)车牌图像切割:
a.将原图转化为灰度图,可去除多通道产生的外界噪声;
b. sobel滤波 ,车牌分割的一个重要特征是车牌中的垂直边缘比较多,为了提取垂直边缘,采用sobel一阶垂直方向导数;
c. 阈值化处理,应用一个OSTU法自动获取阈值滤波器来获得一个二值图像;
d. 闭运算形态学 ,连接含有边缘数量很多的所有区域,删除边缘之间的空白区域,将车牌区域连接起来;
e. 漫水填充, 所有的车牌都有统一的背景颜色。使用漫水填充算法来获取旋转矩阵的精确修剪。漫水填充函数用颜色把连通区域填充到掩码图像,填充从种子开始。填充的像素点都是与种子点进行比较,如果像素值为x,seed-low<=x<=seed+up,则该位置将被填充。一旦得到了用来剪切的掩码图像,进而可得到掩码图像点的最小外接矩形,再次检查矩形大小。对于每一个掩码,白色像素获得位置用minAreaRect函数重新得到最相近的修剪区域;
f. 仿射变换,用来去掉检测矩形区域的旋转;
g. 提取矩形;
h. 调整为统一大小,直方图均衡化,提取的矩形图像不能很好的在训练和分类中使用,因为他们没有相同的大小。并且,每个图像包含不同的光照条件,增加了他们之间的差别。
/* imageSlicer.cpp */
#include <iostream> #include <opencv2/opencv.hpp> #include <time.h> using namespace std; using namespace cv; //筛选旋转矩形的面积和宽高比 bool areaDetection(RotatedRect rectArea) { float error = 0.4;//允许错误率 const float width_height = 4.7272;//获得西班牙车牌的宽高比 57200 int min_area = 25 * 25 * width_height;//获得允许矩形的最大最小面积 int max_area = 100 * 100 * width_height; float min_value = width_height*(1 - error);//获得允许矩形的最大最小宽高比 1.890-6.6180 float max_value = width_height*(1 + error); int rect_area = rectArea.size.width*rectArea.size.height;//计算可旋转矩形的包围面积和宽高比 float rect_value = rectArea.size.width / rectArea.size.height; rect_value = rect_value<1 ? 1 / rect_value : rect_value; return rect_area>min_area && rect_area<max_area && rect_value>min_value && rect_value<max_value; } void imgProcess(Mat carImg) { //1.获得灰度图 Mat grayImg, blurImg, sobelImg, threshImg, mopImg; cvtColor(carImg, grayImg, COLOR_BGR2GRAY); //imshow("灰度图", grayImg); //2.均值滤波 /*void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )*/ blur(grayImg, blurImg, Size(5, 5));//均值滤波 //imshow("均值滤波", blurImg); //3.sobel边缘检测 /*void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )*/ Sobel(blurImg, sobelImg, CV_8U, 1, 0);//sobel算子边缘检测 只对x方向求一阶导 //imshow("sobel边缘检测", sobelImg); //4.二值化,OTSU算法自动获取阈值获得二值化图像 /*double threshold(InputArray src, OutputArray dst, double thresh, double maxval, int type)*/ threshold(sobelImg, threshImg, 0, 255, CV_THRESH_OTSU+CV_THRESH_BINARY); //imshow("图像二值化", threshImg); //5.闭运算,删除边缘之间的空白区域,将车牌区域连接起来 /*Mat getStructuringElement(int shape, Size ksize, Point anchor=Point(-1,-1))*/ Mat element = getStructuringElement(MORPH_RECT, Size(17, 3));//定义结构元素 /*void morphologyEx(InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor=Point(-1,-1), int iterations=1, int borderType=BORDER_CONSTANT, const Scalar& borderValue=morphologyDefaultBorderValue() )*/ morphologyEx(threshImg, mopImg, CV_MOP_CLOSE, element); //imshow("闭运算", mopImg); //6.寻找所有轮廓,然后用面积和宽高比筛选 vector<vector<Point>> contours;//定义轮廓,每个轮廓是一个点集 /*void findContours(InputOutputArray Img, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())*/ findContours(mopImg, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);//检测最外层轮廓 map<int, RotatedRect> mapCounter;//定义map容器,存储筛选后的矩形轮廓 for (int i = 0; i < contours.size(); ++i) { /*void drawContours(InputOutputArray Img, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )*/ drawContours(mopImg, contours, i, Scalar(255), 1);//绘制图像轮廓 /*RotatedRect minAreaRect(InputArray points)*/ RotatedRect contourRect = minAreaRect(contours[i]);//给定2D点集,寻找最小面积点集的包围矩形 Point2f vertices[4];//定义矩形的四个顶点 contourRect.points(vertices); for (int j = 0; j < 4; ++j) { line(mopImg, vertices[j], vertices[(j + 1) % 4], Scalar(255), 2);//绘制包围轮廓的矩形 } if (areaDetection(contourRect)) mapCounter[i] = contourRect; } //imshow("矩形包围所有轮廓", mopImg); cout << "轮廓总数: " << contours.size() << endl; cout << "筛选后的轮廓数量:" << mapCounter.size() << endl; //获得map存储筛选后矩形轮廓的起始地址用于遍历,继续筛选 map<int, RotatedRect>::iterator it = mapCounter.begin(); while (it != mapCounter.end()) { //7.绘制通过面积和宽高比筛选的矩形 //RotatedRect contourRect = it->second;//获得键对应的值 //Point2f vertices[4];//定义四个顶点 //contourRect.points(vertices); //for (int j = 0; j < 4; ++j) //{ // line(mopImg, vertices[j], vertices[(j + 1) % 4], Scalar(255), 5); //} //++it; //8.漫水填充矩形轮廓 RotatedRect contourRect = it->second;//获得键对应的值 /*void circle(Mat& img, Point center, int radius, const Scalar& color, int thickness=1, int lineType=8, int shift=0)*/ circle(mopImg, contourRect.center, 3, Scalar(255), -1);//绘制矩形中心 float minSize = (contourRect.size.width < contourRect.size.height) ? contourRect.size.width : contourRect.size.height; minSize = minSize*0.5;//设置随机种子的范围 srand(time(NULL)); int seedNum = 10;//设置每个掩码图像的随机种子数 Mat mask;//绘制新的掩码图像 mask.create(carImg.rows + 2, carImg.cols + 2, CV_8UC1); mask = Scalar::all(0); Scalar newVal = Scalar::all(255);//填充颜色 Rect ccomp;//重绘最小矩形区域 Scalar loDiff(30, 30, 30);//亮度或颜色负差的最大值 Scalar upDiff(30, 30, 30);//亮度或颜色正差的最大值 //只考虑当前像素水平和垂直方向的像素+白色填充值为255(忽略newVal)+考虑当前像素与种子像素的差+填充掩码图像 int flags = 4 + (255 << 8) + FLOODFILL_FIXED_RANGE + FLOODFILL_MASK_ONLY; for (int j = 0; j < seedNum; ++j) { /*int floodFill(InputOutputArray Img, InputOutputArray mask, Point seedPoint, Scalar newVal, Rect* rect=0, Scalar loDiff=Scalar(), Scalar upDiff=Scalar(), int flags=4 )*/ Point point; point.x = contourRect.center.x + rand() % (int)minSize - (int)(minSize / 2);//获得矩形区域中心附近的点作为种子(黄色) point.y = contourRect.center.y + rand() % (int)minSize - (int)(minSize / 2); circle(mopImg, point, 1, Scalar(255), -1);//绘制种子 int area = floodFill(carImg, mask, point, newVal, &ccomp, loDiff, upDiff, flags); } //imshow("漫水填充图像", mopImg); //imshow("掩码图像", mask); //9.通过每个图像掩码获得最小面积的包围矩形 vector<Point> pointInterest; Mat_<uchar>::iterator itMask = mask.begin<uchar>();//Mat的轻量级数据类型Mat_,需指定类型 for (; itMask != mask.end<uchar>(); ++itMask) { if (*itMask == 255) pointInterest.push_back(itMask.pos());//保存白点的坐标 } RotatedRect minRect = minAreaRect(pointInterest);//给定2D点集,寻找最小面积的包围矩形 if (areaDetection(minRect)) { //10.绘制通过继续筛选的矩形 Point2f minRectPoints[4]; minRect.points(minRectPoints); for (int k = 0; k < 4; k++) line(mopImg, minRectPoints[k], minRectPoints[(k + 1) % 4], Scalar(255)); //11.对原图仿射变换 float width_height = (float)minRect.size.width / (float)minRect.size.height; float angle = minRect.angle; if (width_height < 1)//处理图像中旋转角度大于90度的车牌 angle = angle + 90; Mat rotMat = getRotationMatrix2D(minRect.center, angle, 1);//获得矩形的旋转矩阵 Mat warpImg; /*void warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())*/ warpAffine(carImg, warpImg, rotMat, carImg.size(), INTER_CUBIC); //imshow("原图仿射变换", warpImg); //12.图像切割 Size minRectSize = minRect.size; if (width_height < 1) swap(minRectSize.width, minRectSize.height); Mat plateImg; /*void getRectSubPix(InputArray Img, Size patchSize, Point2f center, OutputArray patch, int patchType=-1 )*/ getRectSubPix(warpImg, minRectSize, minRect.center, plateImg); //imshow("原图车牌", plateImg); //13.调整车牌图像大小为标准33*144 Mat resizeImg; resizeImg.create(33, 144, CV_8UC3); /*void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )*/ resize(plateImg, resizeImg, resizeImg.size(), 0, 0, INTER_CUBIC); imshow("33*144车牌", resizeImg); imwrite("plateOri.jpg", resizeImg); //14.直方图均衡化 Mat histImg; cvtColor(resizeImg, histImg, COLOR_BGR2GRAY); blur(histImg, histImg, Size(3, 3)); equalizeHist(histImg, histImg); imshow("直方图均衡化车牌", histImg); imwrite("plate.jpg", histImg); } ++it; } }
2)车牌图像分类
a. SVM
/* imageClassification.cpp */
#include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; using namespace ml; void SVMClassifier(Mat srcImg) { Mat plateImg; srcImg.convertTo(plateImg, CV_32FC1); plateImg = plateImg.reshape(1, 1);//将图像转换为1行m列特征,自动计算列数 FileStorage fs; fs.open("SVM.xml", FileStorage::READ);//打开xml文件 //1.获得训练数据 Mat trainMat, classesMat; fs["TrainingData"] >> trainMat;//读取训练数据集和类标签 fs["classes"] >> classesMat; Ptr<TrainData> trainData = TrainData::create(trainMat,ROW_SAMPLE,classesMat); //2.创建分类器,设置参数 SVM::ParamTypes param;//设置SVM参数 SVM::KernelTypes kernel = SVM::LINEAR; Ptr<SVM> svm = SVM::create(); svm->setKernel(kernel); //3.训练分类器 svm->trainAuto(trainData); //4.预测 int result = svm->predict(plateImg); cout << "预测结果: " << result << endl; }