车牌识别系统开发记录(三) 字符识别
- - CSDN博客综合推荐文章这篇博文来谈谈车牌的字符识别. 目前,车牌字符识别算法主要是基于模板匹配、特征匹配或神经网络的方法. 在本文中我们主要来说说基于神经网络的字符识别方法,采用的是OpenCV中的CvANN_MLP. 关于神经网络的具体细节,可以参考我以前的博文:. BP神经网络解析及Matlab实现. 更加细节的东西可以查看如下参考文献:.
这篇博文来谈谈车牌的字符识别。
目前,车牌字符识别算法主要是基于模板匹配、特征匹配或神经网络的方法。
在本文中我们主要来说说基于神经网络的字符识别方法,采用的是OpenCV中的CvANN_MLP。关于神经网络的具体细节,可以参考我以前的博文:
更加细节的东西可以查看如下参考文献:
Neural Networks【OpenCV Documentation】
现在我们确定了字符识别的总体框架,那么先来说说字符的特征提取问题。这里我们主要考虑的是:
垂直方向、水平方向的数据统计特征提取法:
这种特征提取法就是自左向右对图像进行逐列的扫描,统计每列黑色像素的个数,然后,自上往下逐行扫描,统计每行的黑色像素的个数,将统计结果作为字符的特征向量,如果字符的宽度为w,高度为h,则特征向量维数是w+h。
同时,为了能够提取更多的特征用于识别,还将输入字符每个点的值引入特征向量。
Code:
Mat OCR::features(Mat in, int sizeData){
// 直方图特征,
Mat vhist=ProjectedHistogram(in,VERTICAL);
Mat hhist=ProjectedHistogram(in,HORIZONTAL);
// 将输入字符resize为15*15大小
Mat lowData;
resize(in, lowData, Size(sizeData, sizeData) );
// 特征向量维数
int numCols=vhist.cols+hhist.cols+lowData.cols*lowData.cols;
Mat out=Mat::zeros(1,numCols,CV_32F);
int j=0;
for(int i=0; i<vhist.cols; i++)
{
out.at<float>(j)=vhist.at<float>(i);
j++;
}
for(int i=0; i<hhist.cols; i++)
{
out.at<float>(j)=hhist.at<float>(i);
j++;
}
for(int x=0; x<lowData.cols; x++)
{
for(int y=0; y<lowData.rows; y++){
out.at<float>(j)=(float)lowData.at<unsigned char>(x,y);
j++;
}
}
if(DEBUG)
cout << out << "\n===========================================\n";
return out;
} Mat OCR::ProjectedHistogram(Mat img, int t)
{
int sz=(t)?img.rows:img.cols;
Mat mhist=Mat::zeros(1,sz,CV_32F);
for(int j=0; j<sz; j++){
Mat data=(t)?img.row(j):img.col(j);
mhist.at<float>(j)=countNonZero(data);
}
//Normalize histogram
double min, max;
minMaxLoc(mhist, &min, &max);
if(max>0)
mhist.convertTo(mhist,-1 , 1.0f/max, 0);
return mhist;
} 不过,在上面的特征提取之前,我们其实还需要做几步预处理:
首先,就是字符分割。在字符分割里面,我们首先将输入车牌二值化,然后利用findContours寻找出每个字符的轮廓,再利用boundingRect定位出每个轮廓的矩形区域,然后割取出每个字符的区域。
Code:
vector<CharSegment> OCR::segment(Plate plate){
Mat input=plate.plateImg;
vector<CharSegment> output;
Mat img_threshold;
threshold(input, img_threshold, 60, 255, CV_THRESH_BINARY_INV);
if(DEBUG)
imshow("Threshold plate", img_threshold);
Mat img_contours;
img_threshold.copyTo(img_contours);
// 在车牌区域中寻找可能字符的的轮廓
vector< vector< Point> > contours;
findContours(img_contours,
contours,
CV_RETR_EXTERNAL,
CV_CHAIN_APPROX_NONE);
cv::Mat result;
img_threshold.copyTo(result);
cvtColor(result, result, CV_GRAY2RGB);
cv::drawContours(result,contours,
-1,
cv::Scalar(255,0,0),
1);
vector<vector<Point> >::iterator itc= contours.begin();
while (itc!=contours.end()) {
Rect mr= boundingRect(Mat(*itc));
rectangle(result, mr, Scalar(0,255,0));
Mat auxRoi(img_threshold, mr);
if(verifySizes(auxRoi)){
auxRoi=preprocessChar(auxRoi);
output.push_back(CharSegment(auxRoi, mr));
rectangle(result, mr, Scalar(0,125,255));
}
++itc;
}
if(DEBUG)
cout << "Num chars: " << output.size() << "\n";
if(DEBUG)
imshow("SEgmented Chars", result);
return output;
}
然后,就是对字符进行预处理(统一大小)。
Code:
// 这个函数主要是对输入图片归一化到统一的大小20×20
Mat OCR::preprocessChar(Mat in){
int h=in.rows;
int w=in.cols;
Mat transformMat=Mat::eye(2,3,CV_32F);
int m=max(w,h);
transformMat.at<float>(0,2)=m/2 - w/2;
transformMat.at<float>(1,2)=m/2 - h/2;
Mat warpImage(m,m, in.type());
warpAffine(in, warpImage, transformMat, warpImage.size(), INTER_LINEAR, BORDER_CONSTANT, Scalar(0) );
Mat out;
resize(warpImage, out, Size(charSize, charSize) );
return out;
}