当前位置:   article > 正文

图像处理基本算法 车牌识别与定位_车牌识别算法

车牌识别算法

       进行车牌识别和定位首先要了解车牌的特征以及分类。

车牌主要包括以下几种:

蓝牌白字:普通小型车(其中包括政府机关专用号段、政法部门警车以外的行政用车)的牌照
黄牌黑字:大型车辆、摩托车、驾校教练车牌照
黑牌白字:涉外车辆牌照,式样和蓝牌基本相同
白牌:政法部门(公安、法院、检察院、国安、司法)警车、武警部队车辆、解放军军车的牌照都是白牌
警车:公安警车的牌照样式为[某·A1234警],除“警”为红字外其他的都是黑字,一共4位数字,含义与普通牌照相同

车牌定位的算法分为三类,一类是基于边缘的,一类是基于颜色的,一类是基于机器学习的,这三种方法我都做过实验,基于边缘的最简单也最有效,如果对于收费站和小区的应用,做到99%以上的检测率不是件难事,但如果场景复杂一点,误检会比较多,但并不会漏掉真正的车牌,只是虚警率高点,可以通过先验知识、颜色和后面的分割加以去除,误检不是重点。基于颜色的定位算法,从根本上讲也可以算是基于边缘的一种,无非是利用彩色边缘或者灰度图像边缘和颜色一起来定位,基于颜色的车牌定位算法用于高清图片效果不错,对于一般的场景我认为没必要用颜色进行定位,但初期用颜色先去除一些明显不是车牌的区域还是比较有效的。基于机器学习的算法进行车牌定位或者说检测,关键是找到好的特征和好的训练方法,不少人利用adaboost+haar特征进行车牌检测,从我的实验结果来看,检测率也能达到99%以上,但同时虚警率也非常高,会出现很多误检,而且很难把车牌的区域完整的检测出来,所以如果单独要用机器学习的算法还是不太可行,不过可以先利用边缘信息找到候选区域,然后用adaboost去去除非车牌区域,这个效果还是蛮不错的。

       对于边缘的检测,如果车牌在图像中占的比例不是很小,普通的差分和全局二值化就可以达到很好的效果,如果对于高清图像(譬如要检测几个车道)或者场景很复杂,导致车牌所占图像的比例很小,还有就是车牌处于比较暗的地方,而整个场景很亮,这个时候差分得到的边缘就不会很丰富,如果利用全局二值化就可能导致车牌区域检测不到边缘,解决办法一就是对图像进行灰度拉伸或增强,解决办法二就是换边缘检测的方法(譬如sobel),解决办法三就是改进二值化的方法。对于图像增强的方法我要特别提一下直方图均衡化,很多论文上都会说对输入图片先进行直方图均衡化,但我的实验发现,晚上的图片如果进行直方图均衡化操作后会导致噪点特别多,而且可能会导致车牌区域检测不到边缘,总之图像增强是一把双刃剑,需要慎重考虑。

       如果利用边缘进行定位,关键是要想办法一定要检测出车牌区域的边缘。

       车牌定位,利用边缘是王道,可以先粗检再精检,颜色可以用于精定位和去除误检,机器学习如果想要好的结果得需要好的特征,但目前好像还没有。我个人认为车牌定位的难点不在于找到车牌区域,而在于怎么对车牌区域进行更精确的定位,而精定位的难点在于左右精定位,以便于后面的分割算法。

这里给出一份基于颜色和形状的车牌识别的源代码,基本思路是提取车牌的特征然后根据特征进行筛选。

识别车牌用到的特征:颜色特征、形状特征、大小特征
a 颜色特征

每种车牌的底色具有各自特殊的RGB值,例如程序中使用的蓝底车牌的典型RGB值为 R = 28,G = 63, B = 138; 
当RGB值接近时则认为可能是车牌,将该点像素赋值为255,否则0;利用颜色特征可以获取二值图像,可以去除大部分的其他物体,但是会有很多的干扰。
b、大小特征
对二值图像进行膨胀处理和腐蚀处理之后,可以去掉很小的噪点。对于灰度图获取轮廓边缘,轮廓具有一定的面积。车牌应该具有一定的大小,如果面积过小则认为不是车牌。此法可以去除大部分的小面积的干扰物。
颜色特征和大小特征是初选。形状特征是精选。
c、形状特征
矩形度:提取轮廓之后,轮廓包围的面积和轮廓最小外接矩形的面积之比称为矩形度,值越接近1,则是矩形的概率越大。
长宽比:正常车牌的长宽比为3:1,最小外接矩形的长宽比越接近1则认为是车牌的概率最大。
如果三个筛选条件都符合,则是车牌的概率非常大。

  //主程序lpr.cpp     

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <iostream>
  4. #include<cv.h>
  5. #include<highgui.h>
  6. #include <cmath>
  7. #include "image.h"
  8. using namespace std;
  9. int main(){
  10. char imageName[12] = "1.jpg";
  11. char imageDstName[12] = "1_dst.jpg";
  12. char imageBwName[12] = "1_bw.jpg";
  13. IplImage* srcImage = NULL,*image = NULL,*bwImage = NULL;
  14. cvNamedWindow("srcImage",1);
  15. cvNamedWindow("bwImage",1);
  16. //cvShowImage("srcImage",srcImage);
  17. int imageWidth,imageHeight;
  18. int maxDif = 50;
  19. //找到蓝色区域
  20. int i= 0,j = 0;
  21. unsigned char * pPixel = NULL;
  22. unsigned char pixelR = 0,pixelG = 0,pixelB = 0;
  23. unsigned char R = 28,G = 63, B = 138;
  24. double length,area,rectArea;
  25. double rectDegree = 0.0; //矩形度
  26. double long2Short = 1.0; //体态比
  27. //计算边界序列的参数 长度 面积 矩形 最小矩形
  28. //并输出每个边界的参数
  29. CvRect rect;
  30. CvBox2D box;
  31. int imageCnt = 1;
  32. double axisLong = 0.0, axisShort = 0.0;
  33. double temp;
  34. while ((srcImage = cvLoadImage(imageName,1)) != NULL)
  35. {
  36. cvShowImage("srcImage",srcImage);
  37. cout<<imageName<<": "<<endl;
  38. imageWidth = srcImage->width;
  39. imageHeight = srcImage->height;
  40. image = cvCreateImage(cvSize(imageWidth,imageHeight),8,3);
  41. //image = cvCloneImage(srcImage);
  42. Image::cloneImage(srcImage,image);
  43. bwImage = cvCreateImage(cvGetSize(srcImage),srcImage->depth,1);
  44. //cvZero(bwImage);
  45. Image::ZerosImage(bwImage);
  46. for (i = 0; i< imageHeight;i++)
  47. {
  48. for (j = 0;j<imageWidth;j++)
  49. {
  50. pPixel = (unsigned char*)srcImage->imageData + i*srcImage->widthStep + j*3;
  51. pixelB = pPixel[0];
  52. pixelG = pPixel[1];
  53. pixelR = pPixel[2];
  54. if (abs(pixelB - B) < maxDif && abs(pixelG - G)< maxDif && abs(pixelR - R)< maxDif)
  55. {
  56. *((unsigned char*)bwImage->imageData + i*bwImage->widthStep + j) = 255;
  57. }else {
  58. *((unsigned char*)bwImage->imageData + i*bwImage->widthStep + j) = 0;
  59. }
  60. }
  61. }
  62. cvShowImage("bwImage",bwImage);
  63. cvSaveImage(imageBwName,bwImage);
  64. //cvWaitKey(0);
  65. //膨胀
  66. //cvDilate(bwImage,bwImage,0,3);
  67. Image::dilateImage(bwImage,bwImage);
  68. Image::dilateImage(bwImage,bwImage);
  69. Image::dilateImage(bwImage,bwImage);
  70. //cvErode (bwImage,bwImage,0,3);
  71. Image::erodeImage(bwImage,bwImage);
  72. Image::erodeImage(bwImage,bwImage);
  73. Image::erodeImage(bwImage,bwImage);
  74. cvShowImage("bwImage",bwImage);
  75. //cvWaitKey(0);
  76. //新图,将轮廓绘制到dst
  77. IplImage *dst = cvCreateImage(cvGetSize(srcImage),8,3);
  78. //dst = cvCloneImage(srcImage);//赋值为0
  79. Image::cloneImage(srcImage,dst);
  80. //寻找轮廓
  81. CvMemStorage *storage = cvCreateMemStorage(0);
  82. CvSeq * seq = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);
  83. CvSeq * tempSeq = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);
  84. int cnt = cvFindContours(bwImage,storage,&seq);//返回轮廓的数目
  85. cout<<"number of contours "<<cnt<<endl;
  86. cvShowImage("bwImage",bwImage); //难道使用cvFindContours会改变源图像?需要实现保存一下
  87. for (tempSeq = seq;tempSeq != NULL; tempSeq = tempSeq->h_next)
  88. {
  89. length = cvArcLength(tempSeq);
  90. area = cvContourArea(tempSeq);
  91. //筛选面积比较大的区域
  92. if (area > 1000 && area < 50000)
  93. {
  94. //cout<<"Points: "<<tempSeq->total<<endl;
  95. //外接矩形
  96. rect = cvBoundingRect(tempSeq,1);
  97. //绘制轮廓和外接矩形
  98. //cvDrawContours(dst,tempSeq,CV_RGB(255,0,0),CV_RGB(255,0,0),0);
  99. //cvRectangleR(dst,rect,CV_RGB(0,255,0));
  100. //cvShowImage("dst",dst);
  101. //绘制外接最小矩形
  102. CvPoint2D32f pt[4];
  103. box = cvMinAreaRect2(tempSeq,0);
  104. cvBoxPoints(box,pt);
  105. //下面开始分析图形的形状特征
  106. //长轴 短轴
  107. axisLong = sqrt(pow(pt[1].x -pt[0].x,2) + pow(pt[1].y -pt[0].y,2));
  108. axisShort = sqrt(pow(pt[2].x -pt[1].x,2) + pow(pt[2].y -pt[1].y,2));
  109. if (axisShort > axisLong)
  110. {
  111. temp = axisLong;
  112. axisLong = axisShort;
  113. axisShort= temp;
  114. }
  115. rectArea = axisLong*axisShort;
  116. rectDegree = area/rectArea;
  117. //体态比or长宽比 最下外接矩形的长轴和短轴的比值
  118. long2Short = axisLong/axisShort;
  119. if (long2Short> 2.2 && long2Short < 3.8 && rectDegree > 0.63 && rectArea > 3000 && rectArea <50000)
  120. {
  121. cout<<"Length: "<<length<<endl;
  122. cout<<"Area : "<<area<<endl;
  123. cout<<"long axis :"<<axisLong<<endl;
  124. cout<<"short axis: "<<axisShort<<endl;
  125. cout<<"long2Short: "<<long2Short<<endl;
  126. cout<<"rectArea: "<<rectArea<<endl;
  127. cout<<"rectDegree: "<<rectDegree<<endl;
  128. for(int i = 0;i<4;++i){
  129. cvLine(dst,cvPointFrom32f(pt[i]),cvPointFrom32f(pt[((i+1)%4)?(i+1):0]),CV_RGB(255,0,0));
  130. }
  131. }
  132. //cvShowImage("dst",dst);
  133. //cvWaitKey();
  134. }
  135. }
  136. cvShowImage("dst",dst);
  137. cvSaveImage(imageDstName,dst);
  138. //cvWaitKey(0);
  139. imageCnt++;
  140. sprintf(imageName,"%d.jpg",imageCnt);
  141. sprintf(imageBwName,"%d_bw.jpg",imageCnt);
  142. sprintf(imageDstName,"%d_dst.jpg",imageCnt);
  143. cout<<"\n\n";
  144. }
  145. return 0;
  146. }

//image类

//image.h

  1. #pragma once
  2. #include "cv.h"
  3. #include<highgui.h>
  4. class Image
  5. {
  6. public:
  7. Image(void);
  8. ~Image(void);
  9. static void cloneImage(IplImage *src,IplImage*dst);
  10. static void ZerosImage(IplImage * src);
  11. static void dilateImage(IplImage* src, IplImage *dst);
  12. static void erodeImage(IplImage* src,IplImage*dst);
  13. static void rgb2gray(IplImage* src, IplImage* dst);
  14. static void gray2bw(IplImage* src, IplImage* dst,unsigned char value);
  15. };

//image.cpp

  1. #include "Image.h"
  2. Image::Image(void)
  3. {
  4. }
  5. Image::~Image(void)
  6. {
  7. }
  8. void Image::cloneImage(IplImage *src,IplImage*dst){
  9. int nChannels = src->nChannels;
  10. int imageHeight = src->height;
  11. int imageWidth = src->width;
  12. unsigned char *pPixel= NULL;
  13. unsigned char *pPixel2= NULL;
  14. if (nChannels == 1)
  15. {
  16. for (int i = 0; i< imageHeight;i++)
  17. {
  18. for (int j = 0; j< imageWidth ;j++)
  19. {
  20. pPixel = (unsigned char *)src->imageData + i*src->widthStep+j;
  21. pPixel2 = (unsigned char *)dst->imageData + i*dst->widthStep+j;
  22. pPixel2[0] = pPixel[0];
  23. }
  24. }
  25. }else if (nChannels == 3)
  26. {
  27. for (int i = 0; i< imageHeight;i++)
  28. {
  29. for (int j = 0; j< imageWidth ;j++)
  30. {
  31. pPixel = (unsigned char *)src->imageData + i*src->widthStep+3*j;
  32. pPixel2 = (unsigned char *)dst->imageData + i*dst->widthStep+3*j;
  33. pPixel2[0] = pPixel[0];
  34. pPixel2[1] = pPixel[1];
  35. pPixel2[2] = pPixel[2];
  36. }
  37. }
  38. }
  39. }
  40. void Image::ZerosImage(IplImage * src){
  41. int nChannels = src->nChannels;
  42. int imageHeight = src->height;
  43. int imageWidth = src->width;
  44. unsigned char *pPixel= NULL;
  45. unsigned char *pPixel2= NULL;
  46. if (nChannels == 1)
  47. {
  48. for (int i = 0; i< imageHeight;i++)
  49. {
  50. for (int j = 0; j< imageWidth ;j++)
  51. {
  52. pPixel = (unsigned char *)src->imageData + i*src->widthStep+j;
  53. pPixel[0] = 0;
  54. }
  55. }
  56. }else if (nChannels == 3)
  57. {
  58. for (int i = 0; i< imageHeight;i++)
  59. {
  60. for (int j = 0; j< imageWidth ;j++)
  61. {
  62. pPixel = (unsigned char *)src->imageData + i*src->widthStep + 3*j;
  63. pPixel[0] = 0;
  64. pPixel[1] = 0;
  65. pPixel[2] = 0;
  66. }
  67. }
  68. }
  69. }
  70. //膨胀
  71. void Image::dilateImage(IplImage* src, IplImage *dst){
  72. int nChannels = src->nChannels;
  73. int imageHeight = src->height;
  74. int imageWidth = src->width;
  75. IplImage * tmpDst = cvCreateImage(cvSize(imageWidth,imageHeight),src->depth,src->nChannels);
  76. ZerosImage(tmpDst);
  77. unsigned char *pPixel= NULL;
  78. unsigned char *pPixel2= NULL;
  79. CvSize windowSize = cvSize(3,3);
  80. int SW2 = windowSize.width/2;
  81. int SH2 = windowSize.height/2;
  82. if (nChannels == 1)
  83. {
  84. for (int i = 1; i< imageHeight - 1;i++)
  85. {
  86. for (int j = 1; j< imageWidth - 1 ;j++)
  87. {
  88. pPixel = (unsigned char *)tmpDst->imageData + i*tmpDst->widthStep+j;
  89. for (int m = i - SH2 ; m <= i+SH2; m++)
  90. {
  91. for (int n = j - SW2; n <= j+SW2;n++)
  92. {
  93. pPixel2 = (unsigned char *)src->imageData + m*src->widthStep + n;
  94. if (pPixel2[0] == 255)
  95. {
  96. pPixel[0] = 255;
  97. }
  98. }
  99. }
  100. }
  101. }
  102. }else if (nChannels == 3)
  103. {
  104. //不是灰度图像,报错
  105. }
  106. //拷贝图像
  107. cloneImage(tmpDst,dst);
  108. //cvShowImage("dst",dst);
  109. //cvWaitKey(0);
  110. }
  111. void Image::erodeImage(IplImage* src,IplImage*dst){
  112. int nChannels = src->nChannels;
  113. int imageHeight = src->height;
  114. int imageWidth = src->width;
  115. IplImage * tmpDst = cvCreateImage(cvSize(imageWidth,imageHeight),src->depth,src->nChannels);
  116. ZerosImage(tmpDst);
  117. unsigned char *pPixel= NULL;
  118. unsigned char *pPixel2= NULL;
  119. CvSize windowSize = cvSize(3,3);
  120. int SW2 = windowSize.width/2;
  121. int SH2 = windowSize.height/2;
  122. int flag = 0;
  123. if (nChannels == 1)
  124. {
  125. for (int i = 1; i< imageHeight - 1;i++)
  126. {
  127. for (int j = 1; j< imageWidth - 1 ;j++)
  128. {
  129. pPixel = (unsigned char *)tmpDst->imageData + i*tmpDst->widthStep+j;
  130. flag = 0;
  131. for (int m = i - SH2 ; m <= i+SH2; m++)
  132. {
  133. for (int n = j - SW2; n <= j+SW2;n++)
  134. {
  135. pPixel2 = (unsigned char *)src->imageData + m*src->widthStep + n;
  136. if (pPixel2[0] == 0)
  137. {
  138. flag = 1;
  139. }
  140. }
  141. if (flag == 0)
  142. {
  143. //
  144. pPixel[0] = 255;
  145. }else {
  146. pPixel[0] = 0;
  147. }
  148. }
  149. }
  150. }
  151. }else if (nChannels == 3)
  152. {
  153. //不是灰度图像,报错
  154. }
  155. //拷贝图像
  156. cloneImage(tmpDst,dst);
  157. //cvShowImage("dst",dst);
  158. //cvWaitKey(0);
  159. }
  160. void Image::rgb2gray(IplImage* src, IplImage* dst){
  161. //Gray = 0.29900 * R + 0.58700 * G + 0.11400 * B
  162. int nChannels = src->nChannels;
  163. int imageHeight = src->height;
  164. int imageWidth = src->width;
  165. unsigned char *pPixel= NULL;
  166. unsigned char *pPixel2= NULL;
  167. unsigned char R ,G, B;
  168. unsigned char grayPixle = 0;
  169. if (nChannels == 3)
  170. {
  171. for (int i = 0; i< imageHeight;i++)
  172. {
  173. for (int j = 0; j< imageWidth ;j++)
  174. {
  175. pPixel = (unsigned char *)src->imageData + i*src->widthStep+j*3;
  176. pPixel2 = (unsigned char *)dst->imageData + i*dst->widthStep+j;
  177. B = pPixel[0];
  178. G = pPixel[1];
  179. R = pPixel[2];
  180. grayPixle = (unsigned char)(0.29900 * R + 0.58700 * G + 0.11400 * B);
  181. pPixel2[0] = grayPixle;
  182. }
  183. }
  184. }else if (nChannels == 1)
  185. {
  186. //不是RGB图像,报错
  187. }
  188. }
  189. void Image::gray2bw(IplImage* src, IplImage* dst,unsigned char value){
  190. int nChannels = src->nChannels;
  191. int imageHeight = src->height;
  192. int imageWidth = src->width;
  193. unsigned char *pPixel= NULL;
  194. unsigned char *pPixel2= NULL;
  195. if (nChannels == 1)
  196. {
  197. for (int i = 0; i< imageHeight;i++)
  198. {
  199. for (int j = 0; j< imageWidth ;j++)
  200. {
  201. pPixel = (unsigned char *)src->imageData + i*src->widthStep+j;
  202. pPixel2 = (unsigned char *)dst->imageData + i*dst->widthStep+j;
  203. if (pPixel[0] > value)
  204. {
  205. pPixel2[0] = 255;
  206. }else {
  207. pPixel2[0] = 0;
  208. }
  209. }
  210. }
  211. }else if (nChannels == 3)
  212. {
  213. //不是灰度图像,报错
  214. }
  215. }

效果图:(最终的效果用红色框框出)









源码以及测试图片下载地址:http://download.csdn.net/detail/renshengrumenglibing/5073845


声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/994216
推荐阅读
  

闽ICP备14008679号