3)车牌字符切割
a. 阈值滤波,使用CV_THRESH_BINARY参数通过把白色值变为黑色,黑色值变为白色来实现阈值输出的反转,因为需要获取字符的轮廓,而轮廓的算法寻找的是白色像素;
b. 查找轮廓;
c. 验证轮廓是否为字符,去除那些规格太小的或者宽高比不正确的区域。字符是45/77的宽高比,允许0.35的误差。如果一个区域面积高于80%(就是像素大于0的超过80%),则认为这个区域是一个黑色块,不是字符。
/* charSlicer.cpp */ #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; //包围字符的矩形筛选 bool charDetection(Mat rect) { float error = 0.35; const float width_height = 45.0 / 77.0; float char_width_height = (float)rect.cols / (float)rect.rows; float min_value = 0.2; float max_value = width_height*(1 + error); float min_height = 20; float max_height = 30; int pixels = countNonZero(rect); float area = rect.cols*rect.rows; float ratio = pixels / area;//获得矩形区域黑色所占的比例 return ratio<0.8&&char_width_height>min_value&&char_width_height < max_value &&rect.rows >= min_height && rect.rows <= max_height; } void plateChar(Mat srcImg) { //1. 二值化图像,像素值大于60设为0,反之设为255 Mat plateImg; threshold(srcImg, plateImg, 60, 255, CV_THRESH_BINARY_INV); //imshow("反转图像", plateImg); //2. 寻找轮廓,findContours函数会改变输入图像,可对其clone Mat contoursImg = plateImg.clone(); vector<vector<Point>> contours;//定义轮廓,每个轮廓是一个点集 /*void findContours(InputOutputArray Img, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())*/ findContours(contoursImg, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); cout << "轮廓数量:" << contours.size() << endl; //3. 获得各个字符图像的外部矩形边界,在原图中找出字符 Mat oriImg = imread("plateOri.jpg"); for (int i = 0; i < contours.size(); i++) { drawContours(oriImg, contours, i, Scalar(0, 255, 0), 1);//绿色填充所有轮廓 Rect rect = boundingRect(contours[i]);//获得字符轮廓点集的外部矩形边界 rect.height += 1;//切割较大的图像 rect.width += 1; /*void rectangle(Mat& img, Rect rec, const Scalar& color, int thickness=1, int lineType=8, int shift=0 )*/ rectangle(oriImg, rect, Scalar(0, 0, 255), 1);//红色绘制所有轮廓的矩形 Mat charImg(plateImg, rect);//在图像中分割rect区域 if (charDetection(charImg))//判断rect区域是否是字符 { cout << "字符的width--height: " << rect.width << "--" << rect.height << endl; rectangle(oriImg, rect, Scalar(255, 0, 0), 1);//蓝色绘制包含字符的矩形 //imshow("字符", charImg); //imwrite(to_string(i) + ".jpg", charImg); } } imshow("矩形包围字符", oriImg); }
4)车牌字符分类
a. 提取预测字符的特征,创建字符特征矩阵(水平和垂直累积直方图、低分辨率图像5*5)创建一个M列的矩阵,矩阵的每一行的每一列都是特征值水平累加直方图构造的特征,垂直方向构造的特征,低分辨图像构造的特征);
b.人工神经网络ANN分类。
/* charClassification.cpp */ #include <iostream> #include <opencv2/opencv.hpp> using namespace std; using namespace cv; using namespace ml; const int HORIZONTAL = 1; const int VERTICAL = 0; const int numChar = 30; const char strCharacters[numChar] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', }; //获得水平或者垂直方向的累加直方图 Mat projectedHistogram(Mat img, int flag) { int num = (flag) ? img.rows : img.cols;//获得图像的最大边长,若取行数 Mat phist = Mat::zeros(1, num, CV_32F);//创建矩阵,存储每行的非0像素数量 for (int i = 0; i < num; i++) { Mat data = (flag) ? img.row(i) : img.col(i); phist.at<float>(i) = countNonZero(data); } double min, max; /*void minMaxLoc(InputArray src, double* minVal, double* maxVal=0, Point* minLoc=0, Point* maxLoc=0, InputArray mask=noArray())*/ minMaxLoc(phist, &min, &max);//获得各行的非0像素数量最大值 if (max > 0) { /*src.converTo(dst, type, scale, shift)*/ phist.convertTo(phist, -1, 1.0 / max, 0);//缩放为小数 return phist; } } //创建特征矩阵 Mat featureMat(Mat img, int size) { Mat hhist = projectedHistogram(img, HORIZONTAL); Mat vhist = projectedHistogram(img, VERTICAL); Mat lowImg; resize(img, lowImg, Size(size, size));//保存低分辨率图像的每个特征 //特征矩阵=水平累加直方图+竖直累加直方图+低分辨率图像 int numCols = hhist.cols + vhist.cols + lowImg.cols*lowImg.cols; Mat featMat = Mat::zeros(1, numCols, CV_32F); //赋值给特征矩阵 先存取水平方向累加直方图,再存取垂直方向累加直方图,最后存取低分辨率图像 int idx = 0; for (int i = 0; i < vhist.cols; i++) { featMat.at<float>(idx) = vhist.at<float>(i); idx++; } for (int i = 0; i < hhist.cols; i++) { featMat.at<float>(idx) = hhist.at<float>(i); idx++; } for (int i = 0; i < lowImg.cols; i++) { for (int j = 0; j < lowImg.rows; j++) { featMat.at<float>(idx) = (float)lowImg.at<unsigned char>(i, j); idx++; } } return featMat; } int trainANN(Mat trainMat, Mat classesMat, int hnn, Mat featMat) { //1. 生成训练数据,如果第i行的样本属于第j类,则(i,j)=1 Mat classesData; classesData.create(trainMat.rows, numChar, CV_32FC1); for (int i = 0; i < classesData.rows; i++) { for (int j = 0; j < classesData.cols; j++) { if (j == classesMat.at<int>(i)) classesData.at<float>(i, j) = 1; else classesData.at<float>(i, j) = 0; } } Ptr<TrainData> trainData = TrainData::create(trainMat, ROW_SAMPLE, classesData); //2.创建ANN模型 Ptr<ANN_MLP> ann = ANN_MLP::create(); //3.设置神经网络的层数和神经元数量 /* setLayerSizes(InputArray _layer_sizes); */ Mat layerSizes(1, 3, CV_32SC1); layerSizes.at<int>(0) = trainMat.cols;//输入层神经元数量 layerSizes.at<int>(1) = hnn;//隐层神经元数量 layerSizes.at<int>(2) = numChar;//输出层神经元数量=10个数字+20个字符 ann->setLayerSizes(layerSizes); //4.设置激活函数 /* setActivationFunction(int type, double param1 = 0, double param2 = 0); */ ann->setActivationFunction(ANN_MLP::SIGMOID_SYM, 1, 1); //5.训练模型 /*samples - 训练样本; layout - 训练样本为 “行样本” ROW_SAMPLE 或 “列样本” COL_SAMPLE; result - 对应样本数据的分类结果*/ /* train(InputArray samples,int layout,InputArray results)*/ ann->train(trainData); //6.预测,结果为行向量 Mat result(1, numChar, CV_32FC1); ann->predict(featMat, result); //7.获得输出矩阵的最大值 Point maxLoc; double maxVal; minMaxLoc(result, 0, &maxVal, 0, &maxLoc); return maxLoc.x; } void annClassifier(Mat charImg) { //1.仿射变换图像进行平移 int height = charImg.rows; int width = charImg.cols; cout << "原始图像的width--height: " << width << "--" << height << endl; Mat warpMat = Mat::eye(2, 3, CV_32F); int max = (height>width) ? height : width; warpMat.at<float>(0, 2) = max / 2 - width / 2;//高度大于宽度,向水平两边平移 warpMat.at<float>(1, 2) = max / 2 - height / 2;//宽度大于高度,向垂直两边平移 Mat warpImg(max, max, charImg.type()); warpAffine(charImg, warpImg, warpMat, warpImg.size()); imshow("仿射图像", warpImg); cout << "平移后图像的width--height: " << warpImg.cols << "--" << warpImg.rows << endl; //3.调整大小 Mat resizeImg; resize(warpImg, resizeImg, Size(20, 20)); imshow("调整尺寸", resizeImg); cout << "调整图像大小width--height: " << resizeImg.cols << "--" << resizeImg.rows << endl; //4.读取训练数据 FileStorage fs; fs.open("OCR.xml", FileStorage::READ); Mat trainMat, classesMat; fs["TrainingDataF5"] >> trainMat; fs["classes"] >> classesMat; cout << "训练数据的样本和维度: " << trainMat.rows << "--" << trainMat.cols << endl; //5.创建特征矩阵 20+20+25 Mat featMat = featureMat(resizeImg, 5); cout << "特征矩阵的维度: " << trainMat.rows << "--" << trainMat.cols << endl; //6.训练预测 int index = trainANN(trainMat, classesMat, 10, featMat); cout << "分类结果索引: "<< index << endl; cout << "分类结果: "<< strCharacters[index] << endl; }
/* main.cpp */ #include <iostream> #include <opencv2/opencv.hpp> #include "plateDetection.h" using namespace std; using namespace cv; int main(int argc, int ** argv) { /*1.车牌图像切割*/ Mat carImg = imread("car.jpg"); imgProcess(carImg); /*2.车牌图像识别*/ Mat plateImg = imread("plate.jpg", 0); SVMClassifier(plateImg); /*3.车牌字符切割*/ Mat plateImg = imread("plate.jpg", 0); plateChar(plateImg); /*4.车牌字符分类*/ Mat charImg = imread("5.jpg", 0); imshow("字符", charImg); annClassifier(charImg); waitKey(0);//显示一帧图像 return 0; }
所有知识:
1. Ostu方法又名最大类间差方法(大津法),通过统计整个图像的直方图特性来实现全局阈值T的自动选取,其算法步骤为:
1) 先计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量;
2) 归一化直方图,也即将每个bin中像素点数量除以总的像素点;
3) i表示分类的阈值,也即一个灰度级,从0开始迭代;
4) 通过归一化的直方图,统计0~i 灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例w0,并统计前景像素的平均灰度u0;统计i~255灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例w1,并统计背景像素的平均灰度u1;
5) 计算前景像素和背景像素的方差 g = w0*w1*(u0-u1) (u0-u1);
6) i++;转到4),直到i为256时结束迭代;7)将最大g相应的i值作为图像的全局阈值T;
2. OpenCV图像的深度和通道:CV_<bit_depth>(S|U|F)C<number_of_channels>
S = 符号整型 U = 无符号整型 F= 浮点型
CV_8UC1 是指一个8位无符号整型单通道矩阵
CV_32FC2是指一个32位浮点型双通道矩阵
其中,通道表示每个点能存放多少个数,类似于RGB彩色图中的每个像素点有三个值,即三通道。图片中的深度表示每个值由多少位来存储,是一个精度问题,一般图片是8bit(位)的,则深度是8。
参考资料:
1. 《深入理解OpenCV实用计算机视觉项目解析》第五章 基于SVM和神经网络的车牌识别
2. 毛星云等编著《OpenCV3编程入门》
3. 基于SVM和神经网络的的车牌识别 CSDN系列博客:
https://blog.csdn.net/u010429424/article/details/75322182
https://blog.csdn.net/zhazhiqiang/article/details/21190521
4. OpenCV中的神经网络:https://www.cnblogs.com/xinxue/archive/2017/06/27/5789421.html
5. 图像处理中的仿射变换:https://blog.csdn.net/bytekiller/article/details/47803753
6. 累加直方图:https://blog.csdn.net/tkp2014/article/details/40151515
7. Ostu大津法:https://blog.csdn.net/ap1005834/article/details/51452516