这个车牌识别系统(opencv定位,切割+BP神经网络识别字符)我已经在本科毕业的时候顺利完成了,相关的文档可以给大家一个链接~祝顺。http://wenku.baidu.com/view/0d69765dbed5b9f3f90f1cd5.html
1.图像预处理
车牌识别系统的第一个步骤为图像预处理。简单的说,为了方便计算,减少计算量,我们通常将获取的图片灰度化。所谓灰度化就是让图片每个像素点在0-255之间。灰度化后图片可以方便我们操作而且不影响我们对车牌的操作。这里先说下,我们获取的照片因为光照的不同,或者车牌本身有污染等原因,会让我们的照片存在干扰信号。将图片傅里叶变换之后,这些干扰信后又通常为高频信号,所以我们需要做一个滤波器,将高频滤掉。对,就是一个低通。推荐使用均值滤波哈,方便我们后面的几个操作!
注意一下,这里的0代表的是强行将图片转化为灰度图,再读入内存。当然,你完全可以用另一个函数:
cvCvtColor( SRC_PICTURE , DES_PICTURE , CV_BGR2GRAY );
img0 = cvLoadImage( names2[i], 0 );
if( !img0 )
{
printf("Couldn't load %s/n", names[i] );
continue;
}
img=cvCreateImage(cvSize(400,300),8,1);
imgsource=cvCreateImage(cvSize(400,300),8,1);
/*目标图片img*/
cvResize(img0,img);
/*显示目标文件img*/
cvNamedWindow("input",1);
cvShowImage("input",img);
/*均值滤波*/
cvSmooth(img,img,CV_MEDIAN);
到此为止,我们就做好了图片预处理50%啦!接下来的一个操作是将图片进行sobel变换。简单的说,sobel变化是一种边缘检测,它可以帮我们检测出一张图片的边缘。而它到底又是怎么样的原理呢?我帮大家推荐一篇文章,应该可以解决大家大部分的疑惑!点击打开链接而sobel在opencv里面对应的函数为:void cvSobel( const CvArr* src, CvArr* dst, int xorder, int yorder, int aperture_size=3 );根据大量的实验,观察,我们发现车牌是纵向纹理的,所以我们可以通过sobel函数将车牌的纹理提取出来!下面附一张效果图
大家可以清楚地看到,此时,车牌的纵向纹理就被很好的提取出来。现在到了最关键的一步,就是二值化。所谓二值化就是在图片中设定一个阀值T,当某点的像素小于T时,就将此点像素置为0,否则置为255;而关键却在于如何寻找合适阀值?
阀值的需找有很多种,我选择了大律法。因为据说这种算法十分经典。下面给出C源码:
#define GrayScale 256
int otsu(const IplImage *frame) //大津法求阈值
{
//frame灰度级
int width=frame->width;
int height=frame->height;
int pixelCount[GrayScale]={0};
float pixelPro[GrayScale]={0};
int i, j, pixelSum = width * height, threshold = 0;
uchar* data = (uchar*)frame->imageData;
float w0, w1, u0tmp, u1tmp, u0, u1, deltaTmp, deltaMax = 0;
//统计每个灰度级中像素的个数
for(i = 0; i < height; i++)
{
for(j = 0;j < width;j++)
{
pixelCount[(int)data[i * width + j]]++;
}
}
//计算每个灰度级的像素数目占整幅图像的比例
for(i = 0; i < GrayScale; i++)
{
pixelPro[i] = (float)pixelCount[i] / pixelSum;
}
//遍历灰度级[0,255],寻找合适的threshold
for(i = 0; i < GrayScale; i++)
{
w0 = w1 = u0tmp = u1tmp = u0 = u1 = deltaTmp = 0;
for(j = 0; j < GrayScale; j++)
{
if(j <= i) //背景部分
{
w0 += pixelPro[j];
u0tmp += j * pixelPro[j];
}
else //前景部分
{
w1 += pixelPro[j];
u1tmp += j * pixelPro[j];
}
}
u0 = u0tmp / w0;
u1 = u1tmp / w1;
deltaTmp = (float)(w0 *w1* pow((u0 - u1), 2)) ;
if(deltaTmp > deltaMax)
{
deltaMax = deltaTmp;
threshold = i;
}
}
return threshold;
}
阀值找到后,我们可以用opencv的库函数将图片阀值话了!原型为:void cvThreshold( const CvArr* src, CvArr* dst, double threshold, double max_value, int threshold_type );让我们看一看阀值后的效果:
到此为止,图片预处理已经完成了,我贴下这部分的源码:
IplImage* imgS=cvCreateImage(cvGetSize(img),IPL_DEPTH_16S,1);
IplImage* imgTh=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
IplImage* temp=cvCreateImage(cvGetSize(img),IPL_DEPTH_8U,1);
cvSobel(img,imgS,2,0,3);
cvNormalize(imgS,imgTh,255,0,CV_MINMAX);
cvNamedWindow("Sobel",1);
cvShowImage("Sobel",imgTh);
cvReleaseImage(&imgS);
cvReleaseImage(&temp);
/*阀值化*/
printf("threshold=%d",otsu(imgTh));
cvThreshold( imgTh, imgTh, otsu(imgTh), 255, CV_THRESH_BINARY );
车牌识别的第二部分为车牌定位,我所用的方法是基于数学形态变化。将上面得到的图片不断地进行开操作,闭操作,从而可以让车牌部分连成一个区域,大家可以看下效
果图:
大家可以很清楚得看到我们已经得到了车牌明显的区域。在进行开闭操作的时候有两点需要注意:
1.Opencv的开闭操作函数不是很好,因为它对计算过后的核没有进行进步一步操作。(大致意思)所以我查看了几位牛人的博客,得到了他们无私奉献的开源函数。(我是小菜啊,刚大3,接触cv不到1个月,以前一直玩单片机的。)现在公布如下哈:
void lhMorpROpen(const IplImage* src, IplImage* dst, IplConvKernel* se, int iterations)
{
assert(src != NULL && dst != NULL && src != dst );
IplImage* temp = cvCreateImage(cvGetSize(src), 8, 1);
cvErode(src, temp, se, iterations);
lhMorpRDilate(temp, src, dst, se, -1);
cvReleaseImage(&temp);
}
void lhMorpRClose(const IplImage* src, IplImage* dst, IplConvKernel* se , int iterations)
{
assert(src != NULL && dst != NULL && src != dst );
IplImage* temp = cvCreateImage(cvGetSize(src), 8, 1);
cvDilate(src, temp, se, iterations);
lhMorpRErode(temp, src, dst, se, -1);
cvReleaseImage(&temp);
}
介于篇幅,lhMorpRErode等函数大家可以百度之,或者联系我哈~
2.关于开闭操作的核的问题。膨胀,腐蚀都是岩石形态学里面的东西,我国庆节利用时间看了一下这方面的只是,也问我数学系的哥们,都感觉比较难。它卷积的计算方法应该是自有一套体系吧,懂的大神帮助下我~我再去告诉其他人嘛!我看了几个论文,最终试出了比较好的核。1*3,中心0,1.在下面的代码中大家会看到哈!
接下来就是找到车牌区域了。在opencv里面有专门的寻找轮廓的函数,简直是所向披靡。 int cvFindContours( CvArr* image, CvMemStorage* storage, CvSeq** first_contour,
int header_size=sizeof(CvContour), int mode=CV_RETR_LIST,int method=CV_CHAIN_APPROX_SIMPLE, CvPoint offset=cvPoint(0,0) );它的返回值是找到的轮廓条数。找到的轮廓被这个函数放在了队列里面存储,(opencv里面采用队列存储)并用一根指针指向了这个队列。CvSeq** first_contour,就是这根指针哈。具体的函数flag的含义大家google,百度很方便的,我也就不多嘴了。下面给出效果图和源码
cvFindContours( src, storage, &contours, sizeof(CvContour), CV_RETR_CCOMP, CV_CHAIN_APPROX_SIMPLE );
for( ; contours != 0; contours = contours->h_next)
{
CvRect aRect = cvBoundingRect( contours, 1 ); //使用边界框的方式
int tmparea=aRect.width*aRect.height;
float fact=float(float(aRect.width)/float(aRect.height));
printf("Are0=%d,Width=%d,Hei=%d,fact=%f\n",tmparea,aRect.width,aRect.height,fact);
if (fact>=3.2 && fact<=8.0&& tmparea>=1000&&tmparea<=9500)
{
cvRectangle(dst,cvPoint(aRect.x,aRect.y),cvPoint(aRect.x+aRect.width ,aRect.y+aRect.height),color,2);
printf("Are1=%d,Width=%d,Hei=%d,fact=%f\n",tmparea,aRect.width,aRect.height,fact);
CarID_Picture_Rec(aRect);
cvNamedWindow("carID",1);
cvShowImage("carID",pImg8uROI);
}
}