本文和下文用于开源项目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
#include
#include
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_areamin_value && rect_value> 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 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::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 pointInterest;
Mat_::iterator itMask = mask.begin();//Mat的轻量级数据类型Mat_,需指定类型
for (; itMask != mask.end(); ++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
#include
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::create(trainMat,ROW_SAMPLE,classesMat);
//2.创建分类器,设置参数
SVM::ParamTypes param;//设置SVM参数
SVM::KernelTypes kernel = SVM::LINEAR;
Ptr svm = SVM::create();
svm->setKernel(kernel);
//3.训练分类器
svm->trainAuto(trainData);
//4.预测
int result = svm->predict(plateImg);
cout << "预测结果: " << result << endl;
}