Jeremy Lin @HQU
车牌识别太出名了,我也就花几天来了解下这个系统,并结合OpenCV进行实现。下面是一些介绍:
车辆牌照识别(License Plate Recognition,LPR)技术作为交通管理自动化的重要手段和车辆检测系统的一个重要环节,在交通监视和控制中占有很重要的地位。LPR系统可以广泛应用于高速公路电子收费站、出入控制、公路流量监控、失窃车辆查询、停车场车辆管理、公路稽查、检测黑牌机动车、监控违章车辆的电子警察等需要车牌认证的重要场合,尤其是基于车牌识别技术的高速公路收费系统中,相对于射频卡等技术,可以实现不停车收费,提高了公路系统的运行效率。
车牌识别技术的研究最早出现在20世纪80年代,这个阶段的研究没有形成完整的系统体系,而是就某一具体的问题进行研究,通常采用简单的图像处理方法来解决。识别过程是使用工业电视摄像机拍下汽车的正前方图像,然后交给计算机进行简单处理,并且最终仍需要人工干预。进入20世纪90年代后,随着计算机视觉的发展和计算机性能的提高,开始出现了车牌识别的系统化研究。1990年A.S.
Johnson等运用计算机视觉技术和图像处理技术实现了车辆牌照的自动识别。该系统分为图像分割、特征提取和模板构造等三个部分,利用不同的阈值对应的直方图的不同,经过大量的统计实验确定车牌位置的图像直方图的阈值范围,从而根据特定的阈值对应的直方图分割出车牌,再利用预先设置的标准字符模板进行模式匹配,识别出字符。1994年Young Sung Soh[2]开发出一套实时车牌识别系统,该系统的准确率达到99.2%。国外还有许多车牌识别系统的报道,由于他们起步早,总体来讲其技术水平高于国内,特别在产品化方面。但由于中国车牌的格式与外国的有很大的差异,并有汉字的识别问题,所以国外关于识别率的研究只是具有参考价值。
国内在90年代也开始了车牌识别的研究。目前比较成熟的产品有中科院自动化研究所汉王公司的“汉王眼”,亚洲视觉科技有限公司、深圳吉通电子有限公司、中国信息产业部下属的中智交通有限公司等也有自己的产品。另外西安交通大学的图像处理和识别实验室、上海交通大学的计算机科学与工程系、清华大学人工智能国家重点实验室、浙江大学的自动化系等也做过类似的研究。
目前车牌识别系统面临的一些问题:(1)、在全天候的情况下,如何获取车辆号牌的清晰图像,即无论是白天还是黑夜,强逆光还是强顺光,刮风还是下雨,都希望将车辆的号牌图像拍清晰;(2)、在图像中如何正确的识别出车牌,即算法如何在一个尽量大的范围内适应各种天气情况下的图片。
因为不是我的研究重点,只是打算用这个系统练练手,所以我也就不打算考虑它所面临的那些问题,只是进行一般的代码实现。
一个典型的车辆牌照识别系统一般包括以下4个部分:车辆图像获取、车牌定位、车牌字符分割和车牌字符识别,如下图所示:

车辆图像获取
车辆图像获取是车牌识别的第一步,也是很重要的一步,车辆图像的好坏对后面的工作有很大的影响。如果车辆图像的质量太差,连人眼都没法分辨,那么肯定不会被机器所识别出来。车辆图像都是在实际现场拍摄出来的,实际环境情况比较复杂,图像受天气和光线等环境影响较大,在恶劣的工作条件下系统性能将显著下降。
现有的车辆图像获取方式主要有两种:一种是由彩色摄像机和图像采集卡组成,其工作过程是:当车辆检测器(如地感线圈、红外线等)检测到车辆进入拍摄范围时,向主机发送启动信号,主机通过采集卡采集一幅车辆图像,为了提高系统对天气、环境、光线等的适应性,摄像机一般采用自动对焦和自动光圈的一体化机,同时光照不足时还可以自动补光照明,保证拍摄图片的质量;另一种是由数码照相机构成,其工作过程是:当车辆检测器检测到车辆进入拍摄范围时,直接给数码照相机发送一个信号,数码相机自动拍摄一幅车辆图像,再传到主机上,数码相机的一些技术参数可以通过与数码相机相连的主机进行设置,光照不足时也需要自动开启补光照明,保证拍摄图片的质量。
车牌定位
车牌定位的主要工作是从摄入的汽车图像中找到汽车牌照所在位置,并把车牌从该区域中准确地分割出来,供字符分割使用。因此,牌照区域的确定是影响系统性能的重要因素之一,牌照的定位与否直接影响到字符分割和字符识别的准确率。目前车牌定位的方法很多,但总的来说可以分为以下4类:(1)基于颜色的分割方法,这种方法主要利用颜色空间的信息,实现车牌分割,包括彩色边缘算法、颜色距离和相似度算法等;(2)基于纹理的分割方法,这种方法主要利用车牌区域水平方向的纹理特征进行分割,包括小波纹理、水平梯度差分纹理等;(3)基于边缘检测的分割方法;(4)基于数学形态法的分割方法。
本文为了代码实现上的方便,我采用的是基于边缘检测的分割方法。主要是利用水平投影方法和垂直投影方法进行车牌定位。
车牌字符分割
要识别车牌字符,前提是先进行车牌字符的正确分割与提取。字符分割的任务是把多列或多行字符图像中的每个字符从整个图像中切割出来成为单个字符。车牌字符的正确分割对字符的识别是很关键的。传统的字符分割算法可以归纳为以下三类:直接分割法、基于识别基础上的分割法、自适应分割线类聚法。直接分割法简单,但它的局限是分割点的确定需要较高的准确性;基于识别基础上的分割法是把识别和分割结合起来,但是需要识别的高准确性,它根据分类和识别的耦合程度又有不同的划分;自适应分割线聚类法是要建立一个分类器,用它来判断图像的每一列是否是分割线,它是根据训练样本来进行自适应学习的神经网络分类器,但对于粘连字符训练困难。也有直接把字符组成的单词当作一个整体来识别的,诸如运用马尔科夫数学模型等方法进行处理,这些算法主要应用于印刷体文本识别。
本文的同样为了实现上的简单,采用的是垂直投影方法。
车牌字符识别
与一般印刷体字符识别相比,车牌字符识别尤其自身的特点,它是文字识别技术与车牌图像自身因素协调兼顾的综合技术,目前,车牌字符识别算法主要是基于模板匹配、特征匹配或神经网络的方法。我国的车牌字符包括50多个汉字,25个大写英文字母,10个数字,总共也就80多个字符,鉴于车牌识别系统的特殊性,如果照搬普通汉字识别的方法,对文字细化后再提取其结构或统计特征,非但得不到意想的结果,反而会降低识别率。
这一部分我没有实现。


代码如下:
全部文件下载地址
- // LPRmain.cpp : 快速车牌识别系统的主文件
- // 本文件是系统的车牌定位和字符分割部分的代码,本系统引用的是OpenCV2.4.7,IDE是VS2012
- // Author:LinJianmin
- // Email: jianmin1990@outlook.com
- // University:Huaqiao University
- // Date: 2014-1
-
- #include "LPR.h"
- int main()
- {
- //////////////////////////////////////////////////////////////////////////////////////////////
- //第一部分:图像采集/图像载入(本系统直接读取图像)
-
- //若采用camera采集示例如下:
- /* VideoCapture cap(0);
- if (!cap.isOpened())
- {
- return -1;
- }
- Mat car_frame;
- cap >> car_frame;
- */
-
- //图像载入
- Mat car_img;
- car_img = imread("E:\\LPR\\car.bmp");
-
- if (!car_img.data)
- {
- cout<<"Please check the input image"<<endl;
- }
-
- /////////////////////////////////////////////////////////////////////////////////////////////////
- // 第二部分:车牌定位
-
- //转变成灰度图像
- Mat gray;
- cvtColor(car_img,gray,CV_BGR2GRAY);
-
- //高斯滤波器滤波去噪(可选)
- /* int ksize = 3;
- Mat g_gray;
- Mat G_kernel = getGaussianKernel(ksize,0.3*((ksize-1)*0.5-1)+0.8);
- filter2D(gray,g_gray,-1,G_kernel);
- //Sobel算子(x方向和y方向)
- Mat sobel_x,sobel_y;
- Sobel(g_gray,sobel_x,CV_16S,1,0,3);
- Sobel(g_gray,sobel_y,CV_16S,0,1,3);
- Mat abs_x,abs_y;
- convertScaleAbs(sobel_x,abs_x);
- convertScaleAbs(sobel_y,abs_y);
- Mat grad;
- addWeighted(abs_x,0.5,abs_y,0.5,0,grad);
- Mat img_bin;
- threshold(grad,img_bin,0,255,CV_THRESH_BINARY |CV_THRESH_OTSU);
- */
- //二值化
- Mat gray_bi;
- threshold(gray,gray_bi,0,255,CV_THRESH_OTSU);
-
- //灰度拉伸
- // Mat equ_img;
- // equalizeHist(gray_bi,equ_img);
- float num[256], p[256],p1[256];
- memset(num,0,sizeof(num));// 清空三个数组
- memset(p,0,sizeof(p));
- memset(p1,0,sizeof(p1));
- long wMulh = gray_bi.cols * gray_bi.rows;
- for (int i = 0; i < gray_bi.cols; i++)
- {
- for (int j = 0; j < gray_bi.rows; j++)
- {
- int v = gray_bi.at<uchar>(j,i);
- num[v]++;
- }
- }
- for (int i = 0; i < 256; i++)//存放图像各个灰度级的出现概率
- {
- p[i] = num[i] / wMulh;
- }
- for (int i = 0; i < 256; i++)//求存放各个灰度级之前的概率和
- {
- for (int k = 0; k <= i; k++)
- {
- p1[i]+=p[k];
- }
- }
- for (int x = 0; x < gray_bi.cols; x++)
- {
- for (int y = 0; y < gray_bi.rows; y++)
- {
- int v = gray_bi.at<uchar>(y,x);
- gray_bi.at<uchar>(y,x) = p1[v]*255 + 0.5;
- }
- }
- //边缘增强
- Mat gray_c;
- Canny(gray_bi,gray_c,50,150,3);
-
- //水平投影和垂直投影
- int imgR[400] = {0};
- bool tag = false;
- int imgTop =0;int imgBottom = 0;
- int img_h1,img_h2;
- for (int ht = 0; ht < gray_c.rows; ht++)
- {
- for (int wt = 0; wt < gray_c.cols; wt++)
- {
- if (gray_c.at<uchar>(ht,wt) != 0)
- {
- imgR[ht]++;
- }
- }
- if ( (!tag)&& imgR[ht] > 10)
- {
- img_h1 = ht;
- tag = true;
- }
- if (tag && imgR[ht] <10)
- {
- img_h2 =ht;
- tag = false;
- }
- if (img_h2-img_h1<50 && img_h2 - img_h1 >25)
- {
- imgTop = img_h1;
- imgBottom = img_h2;
- break;
- }
- }
-
- int imgR_w[300] = {0};
- int img_w1 = 0;
- int img_w2 = 0;
- int imgRight = 0;
- int imgLeft = 0;
- bool tag2 = false;
- for (int wt_new = 2; wt_new < gray_c.cols; wt_new++)
- {
- for (int ht_new = imgTop; ht_new < imgBottom; ht_new++)
- {
- if (255 == gray_c.at<uchar>(ht_new,wt_new))
- {
- imgR_w[wt_new]++;
- }
- }
-
- if ( (!tag2) && (imgR_w[wt_new-2] +imgR_w[wt_new-1] + imgR_w[wt_new])/3 > 5)
- {
- img_w1 = wt_new;
- tag2 = true;
- }
- if (tag2 && (imgR_w[wt_new-2] +imgR_w[wt_new-1] + imgR_w[wt_new])/3 <5)
- {
- img_w2 =wt_new;
- tag2 = false;
- }
- if (img_w2 - img_w1 <180 && img_w2-img_w1 > 70)
- {
- imgLeft = img_w1;
- imgRight = img_w2;
- break;
- }
- }
-
- Mat imgroi;
- //imgroi = car_img( Rect(imgTop,imgLeft,imgRight-imgLeft,imgTop-imgBottom));
- imgroi = car_img( Rect(imgLeft,imgTop+10,115,30));
-
- //////////////////////////////////////////////////////////////////////////////////////
- //第三部分:字符分割
- //二值化
- Mat roi_g, roi_bi;
- cvtColor(imgroi,roi_g,CV_BGR2GRAY);
- threshold(roi_g,roi_bi,0,255,CV_THRESH_OTSU);
-
- bool lab = false; //是否进入一个字符分割状态
- bool black = false; //是否发现黑点
- bool change = false;
- int xnum = 0;
- int rect_left;
- int rect_right;
- //Rect selection;
- Mat fg1,fg2,fg3,fg4,fg5,fg6,fg7;
- for (int wt = 0; wt < roi_bi.cols; wt++)
- {
- int count = 0;
- for (int ht = 0; ht < roi_bi.rows; ht++)
- {
- if ((255==roi_bi.at<uchar>(ht,wt)) && (!change))
- {
- count++;
- change = true;
- }
- else if((0==roi_bi.at<uchar>(ht,wt))&&(change))
- {
- count++;
- change = false;
- }
- }
- if (!lab&&(count>5))
- {
- rect_left = wt - 3;
- lab = true;
- }
- if (lab&&(count<5)&&(wt>(rect_left+8))&& (xnum<7) )
- {
- rect_right = wt + 2;
- lab = false;
- CvPoint pt1,pt2;
- pt1.x = rect_left;
- pt1.y = 0;
- pt2.x = rect_right;
- pt2.y = roi_bi.cols-1;
- int s_x = pt1.x + 1;
- int s_y = pt1.y;
- int s_width = rect_right - rect_left +1;
- int s_height = roi_bi.rows -1;
- if (xnum == 0)
- {
- fg1 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 1)
- {
- fg2 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 2)
- {
- fg3 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 3)
- {
- fg4 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 4)
- {
- fg5 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 5)
- {
- fg6 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- if (xnum == 6)
- {
- fg7 = roi_bi(Rect(s_x,s_y,s_width,s_height));
- }
- xnum++;
- }
- }
- cvWaitKey(50000);
- return 0;
- }
本文地址:http://blog.csdn.net/linj_m/article/details/17761655
更多资源请 关注博客:LinJM-机器视觉 微博:林建民-机器视觉
|