之前说到,把车牌区域提前出来后,就可以着手识别程序了。先使用SVM判断是不是车牌。这里为了提高运行速度,板子资源有限,程序里我把svm训练部分注释掉了,假设每次都能找到车牌,实际使用时,还是要加上svm的。
然后对图像进行分割,我们的分类器只能对数字一个一个地识别,所以把每个数字分割出来,每个字符归一化为20*20的字符。
基本思想是先用findContours()函数把基本轮廓找出来,然后通过简单验证以确认是否为数字的轮廓。对于那些通过验证的轮廓,接下去会用boundingRect()找出它们的包围盒。
分割完后就可以进行识别了,字符识别使用ANN算法采用三层神经网络,识别需要用到一些xml文件,这些文件需要用分类器和大量样本做训练,提取他们的特征、,让机器去“学习”(利用训练好的XML文件去预测图像中车牌 ),我找的这三个xml数据集,说实话,不太好用,准确率一般般,有兴趣的可以自己训练。
完整程序如下,里面有详细注释了:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <string>
#include <cvaux.h>
#include <stdio.h>
#include <opencv2/gpu/gpu.hpp>
#include <opencv2/ml/ml.hpp>
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<inputImage.rows ;++ i)
{
uchar *ptrGray = outpuImage.ptr<uchar>(i);
const Vec3b * ptrRgb = inputImage.ptr<Vec3b>(i);
for (int j = 0 ;j<inputImage.cols ;++ j)
{
ptrGray[j] = 0.3*ptrRgb[j][2]+0.59*ptrRgb[j][1]+0.11*ptrRgb[j][0];
}
}
}
void normal_area(Mat &intputImg, vector<RotatedRect> &rects_optimal, vector <Mat>& 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(c