基于Opencv的汽车车牌识别

在我国汽车工业迅猛发展的今天,汽车保有量的逐年大幅增加,对传统的车 辆管理和交通运行方式面临着巨大的压力和挑战。为了解决上述问题,车辆牌照自动识别技术(AVI)应运而生,并发展成为现代智能交通系统中的重要组成部分之一。它以计算机视觉处理、数字图像处理、模式识别等技术为基础,对摄像机所 拍摄的车辆图像或者视频图像进行处理分析,得到每辆车的车牌号码,从而完成识别过程。通过一些后续处理技术其可以实现停车场出入口收费管理、盗抢车辆管理、高速公路超速自动化管理、闯红灯电子警察、公路收费管理等等功能。对于维护交通安全和城市治安,防止交通堵塞,实现交通全自动化管理有着现实的意义。

1框架流程

基于Opencv的汽车车牌识别_第1张图片

2、平滑处理

基于Opencv的汽车车牌识别_第2张图片

车牌定位的第一步为图像预处理。为了方便计算,系统通常将获取的图片灰度化。将彩色图像转化成为灰度图像的过程就称为图像的灰度化处理。彩色图像中R、G、B三个分量的值决定了具体的像素点。一个像素点可以有上千万种颜色。而灰度图像是一种彩色图像,但是它的特点在于R、G、B三个分量具体的值是一致的。灰度图中每个像素点的变化区间是0到255,由于方便计算,所以在实际工程处理中会先将各种格式的图像转变成灰度图像。在保留图像轮廓和特征的基础上,灰度图仍然能够反映整幅图像轮廓和纹理。在Opencv里面有实现图像灰度化的接口。调用OpenCV中的cvSmooth函数进行中值滤波处理,以去除细小毛刺。


3、二值化

局部自适应二值化是针对灰度图像中的每一个像素逐点进行阈值计算,它的阈值是由像素周围点局部灰度特性和像素灰度值来确定的。局部阈值法是逐个计算图像的每个像素灰度级,保存了图像的细节信息,非均匀光照条件等情况虽然影响整个图像的灰度分布缺不影响局部的图像性质,但也存在缺点和问题,相比全局阈值法来说,它的计算时间较长,但适用于多变的环境。

设图像在像素点(x,y)处的灰度值为f(x,y),考虑以像素点(x,y)为中心的(2w+1)*(2w+1)窗口(w为窗口宽度),则局部自适应二值化算法可以描述如下:

a.计算图像中各点(x,y)的阈值w(x,y)

W(x,y)=0.5*(max f(x+m,y+n)+min f(x+m,y+n))

b.如果f(x,y)>w(x,y),则二值化结果为1,代表字符区域的目标点;否则二值化结果为0,代表背景区域的目标点。


4、车牌定位

基于Opencv的汽车车牌识别_第3张图片



Canny边缘检测算子的方向性质保证了很好的边缘强度估计,而且能同时产生边缘梯度方向和强度两个信息,即能在一定程度上抗噪声又能保持弱边缘,因此采用以canny算子做边缘检测。

Canny算法步骤:

(1)去噪

任何边缘检测算法都不可能在未经处理的原始数据上很好地處理,所以第一步是对原始数据与高斯 mask 作卷积,得到的图像与原始图像相比有些轻微的模糊(blurred)。这样,单独的一个像素雜訊在经过高斯平滑的图像上变得几乎没有影响。

(2)用一阶偏导的有限差分来计算梯度的幅值和方向。

(3)对梯度幅值进行非极大值抑制。

仅仅得到全局的梯度并不足以确定边缘,因此为确定边缘,必须保留局部梯度最大的点,而抑制非极大值。

非极大值抑制产生的二值灰度矩阵的潜在点中按照高阈值寻找边缘,并以所找到的点为中心寻找邻域内满足低阈值的点,从而形成一个闭合的轮廓。然后对于不满足条件的点直接删除掉。

行扫描定位 

车牌细定位的目的是为下一步字符的分割做,就是要进一步去掉车牌冗余的部分。在一幅经过适当二值化处理 含有车牌的图像中,车牌区域具有以下三个基本特征:

1.在一个不大的区域内密集包含有多个字符; 

2.车牌字符与车牌底色形成强烈对比;

3.车牌区域大小相对固定,区域长度和宽度成固定比例。

根据以上特征,车牌区域所在行相邻像素之间0 到1和1到0 的的变化会很频繁,变化总数会大于一个临界值,这可以作为寻找车牌区域的一个依据。 因此根据跳变次数与设定的阈值比较,就可以确定出车牌的水平区域。

由于车牌一般悬挂在车辆下部,所以采用从上到下,从左到右的方式对图像进行扫描。车牌的字符部分由7个字符数与两个竖直边框组成,则车牌区域内任一行的跳变次数至少为(7+2)*2=18次。从图像的底部开始向顶部进行扫描,则第一组连续数行且每行的跳变次数都大于跳变阈值,同时满足连续行数大于某个阈值。

在车牌的水平区域中,最高行与最低行的差值即为车牌在图像中的高度。我国车牌区域为矩形,宽高比约为3.14,取3.14*H作为车牌的宽度。在水平区域内选择任意一行,用L长的窗口由左至右移动,统计窗口中相邻像素0,1的跳变次数并存入数组中。若窗口移动到车牌的垂直区域时,窗口内的跳变次数应该最大。因此在数组中找到最大值,其对应的区域即为车牌的垂直区域。


5、模板匹配




基于BP神经网络的字符识别

车牌字符识别是字符识别的重要组成部分。车牌字符识别的最终目的就是将图像中的车牌字符转化成文本字符,车牌字符的识别属于印刷体识别范畴。

字符识别的基本思想是匹配判别。抽取待识别字符特征按照字符识别的原理和预先存储在计算机中的标准字符模式表达形式的集合逐一进行匹配,找出最接近输入字符模式的表达形式,该表达形式对应的字就是识别结果。字符识别的原理如下:

基于Opencv的汽车车牌识别_第4张图片

根据我国的车牌牌照标准,车牌第一位字符一般为汉字,车牌第二位英文大写字母,第三位至第七位为英文大写字母或数字。考虑到神经网络对小类别字符集有较高的识别率,因此在车牌字符识别系统中,分别设计三个神经网络:汉字网络、字母网络、字母数字网络 实现对字符的分类识别。

基于Opencv的汽车车牌识别_第5张图片

以字母0为例,提取字符特征的步骤为:将分割好的图片再分割成8*4的特征向量,形成一个32维的向量。最终字符特征提取的结果如下:

基于Opencv的汽车车牌识别_第6张图片

最终效果:

基于Opencv的汽车车牌识别_第7张图片


主要函数代码:

图像的预处理,加载图像、并灰度化、高斯滤波

void CMyDialog::OnLoadimage() 
{
	// TODO: Add your control notification handler code here
	src = NULL ;
	CString filePath;
	CFileDialog dlg(TRUE, _T("*.bmp"),"",OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST|OFN_HIDEREADONLY,"image files (*.bmp; *.jpg) |*.bmp;*.jpg|All Files (*.*)|*.*||",NULL);
	char title[]= {"Open Image"};
	dlg.m_ofn.lpstrTitle= title;
	if (dlg.DoModal() == IDOK) {
		filePath= dlg.GetPathName();
		src=cvLoadImage(filePath);
		DrawPicToHDC(src,IDC_IMAGESRC);
	}	
    pImgCanny=cvCreateImage(cvSize(src->width,src->height),IPL_DEPTH_8U,1);
    
    cvCvtColor(src,pImgCanny,CV_RGB2GRAY);
    cvSmooth(pImgCanny,pImgCanny,CV_GAUSSIAN,3,0,0);   //平滑高斯滤波  滤波后的图片保存在 pImgCanny            
}

自适应阈值法定义阈值

int CMyDialog::AdaptiveThreshold(int t, IplImage *Image)
{
	int t1=0,t2=0,tnew=0,i=0,j=0;
	int Allt1=0,Allt2=0,accountt1=0,accountt2=0;
	for(j=0;jheight;j++) //根据现有t,将图像分为两部分,分别求两部分像素的平均值t1、t2
	{	
		for(i=0;iwidth;i++)
		{
			if(CV_IMAGE_ELEM(Image,uchar,j,i)

二值化图像

void CMyDialog::Threshold(IplImage *Image, IplImage *Image_O)
{
	int thresMax=0,thresMin=255,i=0,j=0,t=0;
	//循环得到图片的最大灰度值和最小灰度值
    for(j=0;jheight;j++)
		for(i=0;iwidth;i++)
		{
			if(CV_IMAGE_ELEM(Image,uchar,j,i)>thresMax) 
				thresMax=CV_IMAGE_ELEM(Image,uchar,j,i);
			else if(CV_IMAGE_ELEM(Image,uchar,j,i)

车牌定位

int CMyDialog::PlateAreaSearch(IplImage *pImg_Image)
{
	// 检测是否有值
	if (pImg_Image==NULL)	
	{ 
		return 0;	
	}
    
    IplImage* imgTest =0; 
    int i=0, j=0,k=0,m=0;
	bool flag=0;
    int plate_n=0;  //上边界
	int plate_s=0;  //下边界
	int plate_e=0;  //右边界
	int plate_w=0;  //左边界
      	
	int *num_h=new int[MAX(pImg_Image->width,pImg_Image->height)];	 
	if ( num_h==NULL )  
    {
		MessageBox("memory exhausted!");
		return 0;	
	} 

	//初始化分配的空间
    for(i=0;iwidth;i++)
	{
		num_h[i]=0;
	}  

    imgTest = cvCreateImage(cvSize(pImg_Image->width,pImg_Image->height),IPL_DEPTH_8U,1);
	cvCopy(pImg_Image, imgTest); 

	//--水平轮廓细化
    for(j=0; jheight; j++)
    {
        for(i=0;iwidth-1;i++)
	   {
		    CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j,i+1)-CV_IMAGE_ELEM(imgTest,uchar,j,i);
			//记录每一行的像素值
		    num_h[j]+=CV_IMAGE_ELEM(imgTest,uchar,j,i)/250;      
	   }		
	} 
 
    int temp_1 = 0;  //统计20行中最大的每行数据量
    int temp_max = 0;  //20行最大的数据量 
    int temp_i = 0;  //最大数据量的行

    for(j=0; jheight-20; j++)  
    {
      temp_1=0;
      for(i=0;i<20;i++)  
        temp_1 +=  num_h[i+j];
      if(temp_1>=temp_max)
      {
          temp_max=temp_1;
          temp_i = j;
      }
    }

	//找出上行边界行
    k=temp_i;
    while ( ((num_h[k +1]>POINT_X )||(num_h[k +2]>POINT_X )||(num_h[k]>POINT_X )) && k ) k--;
    plate_n=k+1; 

	//找出下边界行
    k=temp_i+10;
    while (((num_h[k -1]>POINT_X )||(num_h[k-2]>POINT_X )||(num_h[k]>POINT_X ))&&(kheight)) k++; 
    plate_s=k;

	//没找到水平分割线,设置为默认值
    if ( !(plate_n && plate_s
	    && (plate_nwidth*(1-WITH_X))))
	{
		MessageBox("水平分割失败!");
		return 0;
	}
	//找到水平线 
	else 
	{
        int  max_count = 0;
        int  plate_length = (imgTest->width-(plate_s-plate_n)*HIGH_WITH_CAR);
        plate_w=imgTest->width*WITH_X-1;//车牌宽度 默认
    
        //--垂直方向 轮廓细化
        for(i=0;iwidth;i++)
            for(j=0;jheight-1;j++) 
           {
             	CV_IMAGE_ELEM(imgTest,uchar,j,i)=CV_IMAGE_ELEM(imgTest,uchar,j+1,i)-CV_IMAGE_ELEM(imgTest,uchar,j,i);
           }

        for(k=0;kmax_count)
			{
        		max_count = num_h[k];
        		plate_w = k;
			}
        
        }
          

		CvRect ROI_rect;                 //获得图片感兴趣区域
        ROI_rect.x=plate_w;
        ROI_rect.y=plate_n;
        ROI_rect.width=(plate_s-plate_n)*HIGH_WITH_CAR;
        ROI_rect.height=plate_s-plate_n;
              
        if ((ROI_rect.width+ROI_rect.x)> pImg_Image->width)
        {
        	ROI_rect.width=pImg_Image->width-ROI_rect.x;   
			MessageBox("垂直方向分割失败!");
        	return 0;
        }

		else
        {
			IplImage *pImg8uROI=NULL;         //感兴趣的图片  
            pImg8uROI=cvCreateImage(cvSize(ROI_rect.width,ROI_rect.height), src->depth,src->nChannels);
			 
			IplImage *pImg8u11=NULL;        //车牌区域灰度图
		 	pImg8u11=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),pImg8uROI->depth,pImg8uROI->nChannels);

			cvSetImageROI(src,ROI_rect);
			cvCopy(src,pImg8uROI,NULL);
			cvResetImageROI(src);
   
			pImgResize=cvCreateImage(cvSize(40*HIGH_WITH_CAR,40),IPL_DEPTH_8U,1);	
			cvResize(pImg8uROI,pImg8u11,CV_INTER_LINEAR); //线性插值

			cvCvtColor(pImg8u11,pImgResize,CV_RGB2GRAY);
			Threshold(pImgResize,pImgResize);
			
			cvReleaseImage(&pImg8uROI);
		    cvReleaseImage(&pImg8u11);
			cvReleaseImage(&imgTest);
		} 
	} 
		 
	// 释放内存
	delete []num_h;  
	num_h=NULL;	 
	return 1;
}

字符分割

int CMyDialog::SegmentPlate()
{
	// 没有切割成功,直接弹出
	if (pImgResize==NULL)  
	{ 
		return 0; 
	} 

	int *num_h=new int[MAX(pImgResize->width,pImgResize->height)];
	if ( num_h==NULL ) 
    { 	
		MessageBox("字符分割memory exhausted");
		return 0;	
	}

	int i=0,j=0,k=0;//循环变量 12
    int  letter[14]={0,20,23,43,55,75,78,98,101,121,124,127,147,167}; // 默认分割
    bool flag1=0;  
	
	// 垂直投影
    for(i=0;i<40*HIGH_WITH_CAR;i++)
    {	
         num_h[i]=0; // 初始化指针
         for(j=0;j<17;j++)  // 0-16
         {
             num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45;
         }	
         for(j=24;j<40;j++)  // 24-39
         {
             num_h[i]+=CV_IMAGE_ELEM(pImgResize,uchar,j,i)/45;
         }
	}
	// 初定位,定位点 第二个字符末端,
	int	max_count=0;
	int flag=0; 
	for(i=30;i<40*HIGH_WITH_CAR;i++)
	{
		if(num_h[i]0;j--)//找第一个和第二个字符起始位置
	{
		if((num_h[j]=23)?j-3:letter[1];   //第一个字符的结束位置
            letter[0]=(j>=23)?j-23:letter[0];  //第一个字符的起始位置
			break;
		}
	}

	j=2;  flag=0;flag1=0;//两个标记
	for(i=letter[4];i<40*HIGH_WITH_CAR;i++)  //从第三个字符的开始位置算起
	{
		if((num_h[i]>POINT_Y)&&(num_h[i-1]>POINT_Y) && !flag )
		{
			flag=1;
            flag1=0;
            letter[2*j]=i-1; //这里 只记录字符的开始位置
			if(j==6)  //判断 最后一个字符的结束位置 是否越界 超出界限,如果没有,则letter[13]=letter[12]+20
			{
				letter[2*j+1]=((letter[2*j]+20)>40*HIGH_WITH_CAR-1)?40*HIGH_WITH_CAR-1:letter[2*j]+20;
				break;
			}
		}
		else if((num_h[i]

字符识别 

int CMyDialog::CodeRecognize(IplImage *imgTest, int num, int char_num)
{
	if (imgTest==NULL)
	{ 
		return 0;
	}
	
	int i=0,j=0,k=0,t=0;
	int  char_start=0,char_end=0;
    int num_t[CHARACTER ]={0};
	
	
	 switch(num)//这里这样分 可以提高效率,并且提高了识别率
	 {
	    case 0:  char_start =0;         // 数字
	    	             char_end  = 9;
	    	              break;
	    case 1:  char_start =10;        // 英文
	    	             char_end  = 35;
	    	              break;
	    case 2:  char_start =0;       // 英文和数字
	    	             char_end  = 35;
	    	              break;  
	    case 3:  char_start =36;       // 中文
	    	             char_end  = TEMPLETENUM-1;
	    	              break;
	    	default: break;              	
	 }

	// 提取前8个特征
	for(k=0; k<8; k++)
	{ 
   		for(j=int(k/2)*10; j=5)  //特征值 大于5 
		   {
			    if(abs(num_t[i]-Num_Templete[k][i])<=1)
			    matchnum+=2;
		   }
   		   else if( num_t[i]==Num_Templete[k][i])
		   { 
    	  		matchnum+=2;
		   }
		}
		if(matchnum>matchnum_max)
        {
        	  matchnum_max=matchnum;  //保留最大的 匹配 
        	  matchcode= k;  //记录 识别的字符的 索引 
        }	
	}
	//识别输出  存放输出结果
	G_PlateChar[char_num]=PlateCode[matchcode]; //保存下该字符
}

网上分享的的车牌字符模板特征值,并不是很全但对于初步学习已经足够了

const int Num_Templete[TEMPLETENUM][CHARACTER]=
{
  {16,19,10,12,10,10,15,18,110,3,2,2,3,3,3},     //0
  {9,11,10,10,10,10,9,10,79,2,2,2,0,2,12},       //1
  {18,19,3,18,10,10,23,22,123,4,2,2,7,6,8},      //2
  {19,21,11,14,4,20,18,22,129,2,2,4,6,6,7},      //3
  {2,18,11,22,20,21,11,18,123,2,4,2,6,7,5},      //4
  {23,19,20,12,9,20,18,22,143,2,4,4,6,6,6},      //5
  {6,13,17,8,15,20,18,20,117,2,2,4,5,7,6},       //6
  {21,21,0,20,8,12,9,11,102,2,2,2,2,8,15},       //7
  {17,18,18,19,14,20,17,20,143,4,2,4,6,6,6},     //8
  {16,18,15,21,7,19,13,7,116,3,2,2,6,6,5},       //9
  {10,10,16,16,20,20,18,19,129,2,4,2,8,3,6},     //A
  {24,20,20,19,22,22,24,20,171,4,8,4,6,6,6},     //B
  {18,19,20,4,20,8,17,21,127,3,2,4,4,4,4},       //C
  {23,19,11,20,12,20,22,21,148,3,3,3,4,4,4},     //D
  {23,19,21,9,22,8,23,23,148,2,2,2,6,6,6},       //E
  {25,17,20,9,22,8,19,0,120,2,2,2,4,4,4},        //F
  {17,18,22,14,12,24,18,21,146,4,7,4,4,6,6},     //G
  {14,20,18,22,17,22,16,20,149,4,1,4,2,2,2},     //H
  {0,17,0,20,3,20,18,22,100,2,2,4,2,2,2},        //J
  {19,20,26,10,20,20,20,22,157,4,4,4,3,5,11},    //K
  {20,0,20,0,20,0,25,20,105,2,2,2,2,2,2},        //L
  {20,10,27,17,20,10,22,14,140,1,3,3,4,1,5},     //M
  {21,12,25,17,26,12,18,18,149,3,5,3,5,5,6},     //N 
  {23,19,18,20,21,8,22,0,131,3,3,2,4,4,4},       //P
  {18,19,20,10,26,15,18,21,147,3,3,4,5,7,5},     //Q
  {26,19,21,18,21,17,20,21,163,4,3,4,4,6,5},     //R
  {18,18,18,10,8,17,17,22,128,4,3,4,6,6,6},      //S
  {22,18,10,10,10,10,10,10,100,2,2,2,33,2,2},    //T
  {18,12,20,10,20,10,19,21,130,3,3,3,2,2,2},     //U
  {20,19,20,20,15,14,9,10,127,4,4,2,9,1,8},      //V
  {21,25,26,28,16,16,21,19,172,6,2,4,13,0,7},    //W
  {21,21,13,13,12,11,22,21,134,4,2,4,8,0,10},    //X
  {21,20,10,11,10,10,10,11,103,3,2,2,5,2,6},     //Y
  {21,23,5,15,15,5,24,20,128,2,2,2,8,8,7},       //Z
  {13,14,10,10,10,10,13,13,93,2,2,2,29,2,29},    //I
  {20,20,13,20,19,12,17,20,141,3,3,4,4,4,4},     //O          
  {14,15,17,17,16,10,25,24,138,0,2,4,12,8,9},    //云        
  {17,20,17,12,33,28,23,20,170,3,4,7,13,6,4},    //苏
  {21,21,23,24,24,25,31,27,196,0,9,6,8,6,7},     //京
  {19,27,20,34,19,36,24,37,216,4,4,7,13,28,3},   //湘
  {17,14,23,27,36,40,26,27,210,4,13,4,16,14,14}, //鲁
  {24,24,32,38,34,32,17,22,223,9,6,10,11,12,9}, // 粤
  {22,20,33,37,25,24,24,25,210,13,3,6,12,8,7}     //蒙
};



你可能感兴趣的:(Opencv)