本程序主要实现的是车牌的定位与检测主要是利用申继龙论文里面的方法1、采集得到的图像2、把RGB图像转换成HSI彩色图像3、利用设定的H、S阈值得到二值图像4、对二值图像水平投影获得候选区域5、对候选区域的HSI图像边缘检测*/#include "stdafx.h"#include "opencv2/opencv.hpp"#include "opencv2/objdetect/objdetect.hpp"#include "opencv2/features2d/features2d.hpp"#include "opencv2/highgui/highgui.hpp"#include "opencv2/calib3d/calib3d.hpp"#include "opencv2/nonfree/nonfree.hpp"#include "opencv2/nonfree/features2d.hpp"#include "opencv2/imgproc/imgproc_c.h"#include "opencv2/legacy/legacy.hpp"#include "opencv2/legacy/compat.hpp"#include <iostream>#include <algorithm>#include <functional>#include <vector>#include <string>#include <stdlib.h>#include <stddef.h>#include <stdint.h>#include <math.h>using namespace std;using namespace cv;#define pi 3.14159265IplImage* srcImage=NULL;//存储原图片IplImage*srcImage1=NULL;//存储原始图片的副本IplImage* HSI=NULL;static IplImage* grayImage=NULL;//存储原图片灰度图static double posdouble=0.0;IplImage* channelOneImage=NULL;IplImage* channelTwoImage=NULL;IplImage* channelThreeImage=NULL;IplImage* plateImage=NULL;//存储车牌图像IplImage* grayPlateImage=NULL;//存储车牌灰度图像vector<IplImage*>characterImageList;//存储7个车牌字符图像的容器vector<int>xList;//存储7个车牌字符的起始和结束位置vector<vector<KeyPoint>>keyPointsList;//存储车牌字符特征点的集合vector<Mat>descriptorsMatList;//存储每一个车牌字符的特征点描述子矩阵vector<vector<Point>> contours;//用来存储经过闭开操作处理后的车牌轮廓double GetH(int r,int g,int b){double H=0;//H分量double fenZi=1/2.0*((r-g)+(r-b));double sq=pow(double(r-b),2)+(r-b)*(g-b);double fenMu=sqrt(sq);H=acos(fenZi/fenMu)*180/pi;if(b>g){H=360-H;}return H;}double GetS(int r,int g,int b){double s=0;//S分量int min=r;if(g<r){min=g;if(b<g){min=b;}}else{if(b<r){min=b;}}s=1-3.0*min/((r+g+b)*1.0);return s;}double GetI(int r,int g,int b){double i=0.0;i=1/3.0*(r+g+b);i=i/255.0;return i;}//通过公式来直接求H的值然后对H分量进行处理void doHByMath(IplImage* eleImage){int width=eleImage->width;int height=eleImage->height;for(int col=0;col<height;col++){uchar* ptr=(uchar*)(eleImage->imageData+col*eleImage->widthStep);//uchar* ptrGray=(uchar*)(grayImage->imageData+col*grayImage->widthStep);//for(int row=0;row<width;row++){int b=ptr[3*row];int g=ptr[3*row+1];int r=ptr[3*row+2];double H=GetH(r,g,b);double S=GetS(r,g,b);double I=GetI(r,g,b);if(H>=190&&H<=255&&S>0.3){ptrGray[row]=255;}else{ptrGray[row]=0;}if(I<0.25&&S<0.4){ptrGray[row]=0;}}}cvShowImage("H分量处理后的图像",grayImage);//考虑H分量处理后就提取尽量多的有用的区域vector<vector<cv::Point>>contours;Mat mtx = grayImage;//把IplImage类型转换成Mat类型findContours(mtx,contours,CV_RETR_EXTERNAL,CV_CHAIN_APPROX_NONE);Mat result(mtx.size(),CV_8U,Scalar(255));drawContours(result,contours,-1,Scalar(0),2);imshow("轮廓图",result);}//以下代买判断是否为蓝色像素点//返回0代表是白色点//返回1代表是蓝色点int IsBlueOrWhite(IplImage* image,int col,int row){CvScalar s;s = cvGet2D(image, col,row);int b=s.val[0];int g=s.val[1];int r=s.val[2];double H=GetH(r,g,b);double S=GetS(r,g,b);double I=GetI(r,g,b);if(H>=190&&H<=255&&S>0.3){return 1;}/*if(I<255&&S>0){}*//*double grayLevel = r * 0.299 + g * 0.587 + b * 0.114;if (grayLevel >= 200){return 0;}*/boolflagWhite2=(I>=0.95)||(I>=0.81&&I<0.95&&S<(18.0/180.0))||(I>=0.61&&I<=0.8&&S<(20.0/18 0.0))||(I>=0.61&&I<=0.8&&S<(20.0/180.0))||(I>=0.51&&I<=0.6&&S<(30.0/180.0));bool flagWhite1=(S<=0.3&&I>=0.6);if(flagWhite1||flagWhite2){return 0;}return 2;}int IsBlueOrWhite(int col,int row){return IsBlueOrWhite(srcImage,col,row);}bool IsEdgeOrNot(int col,int row){int flag00=IsBlueOrWhite(col-1,row-1);int flag10=IsBlueOrWhite(col,row-1);int flag20=IsBlueOrWhite(col+1,row-1);int flag01=IsBlueOrWhite(col-1,row);int flag11=IsBlueOrWhite(col,row);int flag21=IsBlueOrWhite(col+1,row);int flag02=IsBlueOrWhite(col-1,row+1);int flag12=IsBlueOrWhite(col,row+1);int flag22=IsBlueOrWhite(col+1,row+1);if(flag00==1&&flag10==1&&flag20==1){if(flag02==0&&flag12==0&&flag22==0){return true;//是蓝白边缘}}if(flag00==0&&flag10==0&&flag20==0){if(flag02==1&&flag12==1&&flag22==1){return true;//是蓝白边缘}}return false;}//对是蓝白边缘的一个处理void doIsEdge(int col ,int row){cvSet2D(grayImage, col-1,row,cvScalar(255));cvSet2D(grayImage, col,row,cvScalar(255));cvSet2D(grayImage, col+1,row,cvScalar(255));cvSet2D(grayImage, col-1,row-1,cvScalar(0));cvSet2D(grayImage, col,row-1,cvScalar(0));cvSet2D(grayImage, col+1,row-1,cvScalar(0));cvSet2D(grayImage, col-1,row+1,cvScalar(0));cvSet2D(grayImage, col,row+1,cvScalar(0));cvSet2D(grayImage, col+1,row+1,cvScalar(0));}//对不是蓝白边缘的处理void doNotEdge(int col,int row){cvSet2D(grayImage, col-1,row,cvScalar(0));cvSet2D(grayImage, col,row,cvScalar(0));cvSet2D(grayImage, col+1,row,cvScalar(0));cvSet2D(grayImage, col-1,row-1,cvScalar(0));cvSet2D(grayImage, col,row-1,cvScalar(0));cvSet2D(grayImage, col+1,row-1,cvScalar(0));cvSet2D(grayImage, col-1,row+1,cvScalar(0));cvSet2D(grayImage, col,row+1,cvScalar(0));cvSet2D(grayImage, col+1,row+1,cvScalar(0));}//边缘检测代码//检测蓝白边缘//通过构造3*3窗口void EdgeDetection(){int width=srcImage->width;int height=srcImage->height;for(int col=1;col<height;col+=3){for(int row=1;row<width;row+=3){//判断是否为蓝白边缘if(IsEdgeOrNot(col,row)){//设置3*3窗口中间一列的所有值为1 其他列为0doIsEdge(col,row);}else{//设置3*3窗口中所有元素的值为0doNotEdge(col,row);}}}cvShowImage("蓝白边缘检测后的车辆图像",grayImage);}//这个函数主要是实现提取每一个字符的sift特征//在提取sift特征我们先对每一个字符图片做尺寸的归一化处理void ExtractSiftFeature(){Mat image11= imread("C:/Users/wzafxj/Desktop/车牌车标识别论文/车牌识别论文集锦/字符模板/字符模板/G.bmp",0);SurfFeatureDetector surf(5000);//特征点选取的hessian 阈值为3000SurfDescriptorExtractor surfDesc;//SurfDescriptorExtractor是提取特征向量的描述子Mat descriptors;vector<KeyPoint> keypoints;//存储每个车牌字符的surf特征点CvSize size=cvSize(41,82);CvSize cvsize11=cvSize(30,62);resize(image11,image11,cvsize11);vector<DMatch> matches;DescriptorMatcher *pMatcher = new FlannBasedMatcher; // 使用Flann匹配算法vector<KeyPoint> keypoints11;surf.detect(image11,keypoints11);//这里是利用surf算法检测关键点Mat descriptors11;pute(image11,keypoints11,descriptors11);imshow("moban",image11);IplImage* pDstImage = cvCreateImage(size, grayPlateImage->depth, grayPlateImage->nChannels);for(int i=0;i<characterImageList.size();i++){cvResize(characterImageList[i],pDstImage,CV_INTER_AREA);Mat image=pDstImage;imshow("hahh",image);surf.detect(image,keypoints);//这里是利用surf算法检测关键点keyPointsList.push_back(keypoints);//接下来提取surf特征点pute(image,keypoints,descriptors);descriptorsMatList.push_back(descriptors);pMatcher->match(descriptors11, descriptors, matches);Mat imageMatches;drawMatches(image11,keypoints11, // 1st image and its keypointsimage,keypoints, // 2nd image and its keypointsmatches, // the matchesimageMatches, // the image producedScalar(255,255,255)); // color of the linesstring winName="WN"+i;imshow(winName,imageMatches);}}//显示已经分割出来的车牌字符void ShowSeparatedCharacterImage(){if(characterImageList.size()>0){for (int i=0;i<characterImageList.size();i++){const char* windowName="识别出来的车牌字符"+i;//注意这里前面必须得加const//否则会报错因为"识别出来的车牌字符" 是常量字符不能赋给不是常量的字符cvNamedWindow(windowName,1);cvShowImage(windowName,characterImageList[i]);}}}//根据xList里面存储的14个值来对void CharacterSegmentation(){int height=grayPlateImage->height;IplImage* characterSegImage=NULL;//定义一个临时存储字符图片CvRect roiGrayPlate;CvRect roiCharacter;for(int i=0;i<xList.size();i=i+2){int x1=xList[i];int x2=xList[i+1];int width=x2-x1;characterSegImage=cvCreateImage(cvSize(width,height),grayPlateImage->depth,grayPlateI mage->nChannels);roiGrayPlate=cvRect(x1,0,width,height);cvSetImageROI(grayPlateImage,roiGrayPlate);roiCharacter=cvRect(0,0,width,height);cvSetImageROI(characterSegImage,roiCharacter);cvCopy(grayPlateImage,characterSegImage);cvResetImageROI(characterSegImage);cvResetImageROI(grayPlateImage);characterImageList.push_back(characterSegImage);}//接下来就是对提取出来的字符进行sift特征提取,并且标准库中的字符模板进行sift 特征匹配,以达到字符识别的目的//不过进行上面的操作之前我们先把这七个字符显示出来再说ShowSeparatedCharacterImage();//接下来是对分割出来的车牌特征进行sift特征提取//ExtractSiftFeature();}//对字符为白色、背景为黑色的车牌做垂直投影//以此来达到字符分割的目的bool VerticalProjectionToPlateImage(IplImage* grayPlateImage){IplImage* paintx=cvCreateImage( cvGetSize(grayPlateImage),IPL_DEPTH_8U, 1 );cvZero(paintx);int* v=new int[grayPlateImage->width];memset(v,0,grayPlateImage->width*4); //作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法int x,y;CvScalar s,t;//再次提醒对于图像来说坐标原点是在图像的左上角//我们定义一个容器来存储车牌字符的每一个起始位置和结束位置//是这样的容器里面的第一个值是第一个字符的起始位置//第二个值既是第一个字符的结束位置,依次类推//但是第二个字符过后的14个像素单位里面可能出现字符点,我们不能提取,这个点就是车牌字符中的那个点//在字符的检测中,如果x2-x1的长度超过了20,我们就默认这个字符的检测已经结束,就是说已经检测出来这个字符了//注意除了第二个字符和第三个字符之间的距离超过了14个像素点之多外呢,其他每两个字符之间的距离应该也至少有2个像素//因此我们在取值的时候一定要注意到这个问题比如2 22 27 47 60 80 81 101 102 122 123 143 146 166 167//仔细观察60 80 81 中的81的取值不对应该跨两个像素点再去取应该取83 也就是xList的size()为双数的时候要进行判断//可惜我们还是没有考虑字符“1”的情形//通过仔细观察我们字符“1”它的高度至少超过30像素它的宽度在5左右主要是它离它后面的一个字符的距离有10像素左右bool flag1=false;bool flag2=false;int count=0;int x1=0;int x2=0;int countPoint=0;//设置这个是为了对中间因为有一个. 而存在8个多像素没有字符出现的情况所以我们设置一个这个来判断int countOne=0;//用来对字符‘1’的处理bool flagOne=false;//也是用来对字符1的处理bool flagoneOut=false;//为true 代表字符'1'出现了for(x=3;x<grayPlateImage->width;x++){int countCol=0;for(y=0;y<grayPlateImage->height;y++){s=cvGet2D(grayPlateImage,y,x);if(s.val[0]==0)v[x]++;if(s.val[0]==255){countCol++;}}if(xList.size()==4){countPoint++;}if(countCol>0&&flag1==false){if(countPoint>0&&countPoint<12){//对中间出现"." 的处理}else{if(xList.size()!=0&&xList.size()%2==0){if(xList.size()>0){//xList.back()获得容器的最后一个元素if(x-xList.back()<2){}else{xList.push_back(x);flag1=true;}}}else{xList.push_back(x);flag1=true;}}}if(flag1==true){if(countPoint>0&&countPoint<12){//对中间出现"." 的处理}else{//如果字符'1' 出现在最后一个位子我们就没有处理的必要了if(xList.size()<=12){if(countCol>0){flagOne==true;countOne=0;}if(countCol==0&&flagOne==false){countOne++;//如果countOne的值达到10左右(单位像素)且已不再countPoint处理的范围也就是已经过了中间的那个点}if(countOne>=12){flagOne==true;countOne=0;//代表很有可能这个字符是1flagoneOut=true;//代表是字符1出现了}}}}if(countCol>0||(countCol==0&&count<20)){if(countPoint>0&&countPoint<12){//对中间出现"." 的处理}else{//在这里我们加入一个字符1的处理if(flagoneOut==true){count=0;flag1=false;flagoneOut=false;xList.push_back(x);}else{if(count<20){if(flag1==true){count++;//对于一个字符来讲必须等到起始位置记录了,才让count的值++}}else{count=0;flag1=false;xList.push_back(x);}}}}else{if(countPoint>0&&countPoint<12){//对中间出现"." 的处理}else{count=0;flag1=false;xList.push_back(x);}}}//当xList里面存储的值的个数为13个(本来7个字符应该存储14个)的话,则把grayPlateImage->width存储到Xlist里面if(xList.size()<13)return false;if(xList.size()==13){xList.push_back(grayPlateImage->width);}if(xList.size()==15){xList.pop_back();//删除容器的最后一个元素}for(x=0;x<grayPlateImage->width;x++){for(y=0;y<v[x];y++){t.val[0]=255;cvSet2D(paintx,y,x,t);}}cvShowImage("车牌垂直积分投影",paintx);//到这一步xList里面存储的是14个值每两个值代表的是一个字符元素的起始位置和结束位置//接下来的操作就是我们利用这14个值来分割出字符来CharacterSegmentation();}//这里是对图像进行任意角度倾斜的函数,angel表示要旋转的角度//clockwise传递过来的值如果是为true则顺时针旋转,为false逆时针旋转IplImage* rotateImage(IplImage* src, int angle, bool clockwise){angle = abs(angle) % 180;if (angle > 90){angle = 90 - (angle % 90);}IplImage* dst = NULL;int width =(double)(src->height * sin(angle * CV_PI / 180.0)) +(double)(src->width * cos(angle * CV_PI / 180.0 )) + 1;int height =(double)(src->height * cos(angle * CV_PI / 180.0)) +(double)(src->width * sin(angle * CV_PI / 180.0 )) + 1;int tempLength = sqrt((double)src->width * src->width + src->height * src->height) + 10;int tempX = (tempLength + 1) / 2 - src->width / 2;int tempY = (tempLength + 1) / 2 - src->height / 2; //这里的tempX和tempY指的是图像的中心像素点int flag = -1;dst = cvCreateImage(cvSize(width, height), src->depth, src->nChannels);cvZero(dst);IplImage* temp = cvCreateImage(cvSize(tempLength, tempLength), src->depth, src->nChannels); //图像temp并没有多大的实际含义,//只不过是说把src图像size稍微扩大一点//内容和src完全一样cvZero(temp);cvSetImageROI(temp, cvRect(tempX, tempY, src->width, src->height));cvCopy(src, temp, NULL);cvResetImageROI(temp);if (clockwise)flag = 1;float m[6];int w = temp->width;int h = temp->height;m[0] = (float) cos(flag * angle * CV_PI / 180.);m[1] = (float) sin(flag * angle * CV_PI / 180.);m[3] = -m[1];m[4] = m[0];// 将旋转中心移至图像中间m[2] = w * 0.5f;m[5] = h * 0.5f;//CvMat M = cvMat(2, 3, CV_32F, m); //M是一个3 ×2 变换矩阵[A|b]//m0 m1 m2// m3 m4 m5//变换公式如下//dst(x + width(dst) / 2,y + height(dst) / 2)=//(m0x+m1y+b1,m3x+m4y+b2)cvGetQuadrangleSubPix(temp, dst, &M); //提取象素四边形,使用子象素精度dst提取的是一个四边形cvReleaseImage(&temp);return dst;}//获得车牌的倾斜角度然后校正//定义一个容器存储所有的countColvector<int> countColVector;struct maxTotalColAndAngel{int maxTotalCol;int angel;};vector <maxTotalColAndAngel> maxTotalColAndAngelVector;bool less_second(const maxTotalColAndAngel m1, maxTotalColAndAngel m2) { return m1.maxTotalCol < m2.maxTotalCol;}bool GetAngleAndRotate(IplImage* _image){IplImage* dstImage=NULL;//假定车牌的旋转角度控制在30度的范围之内bool _direction=false;//true代表顺时针int _actualAngle=0;//存储最后得到的旋转角度CvScalar s;int maxCount=0;//存储最大的那个投影值bool changeDirection=false;int flag=-2;//-1代表的是往一开始设定的相反的方向旋转1代表的是旋转的方向是对的但是已经过了最大值int Xnum=0;//这里我们可以先做下canny二值化来减少计算量for(int _angle=0;_angle<=30;_angle+=5){dstImage=rotateImage(_image,_angle,_direction);for(int x=dstImage->width-1;x>=0;x--){int countCol=0;for(int y=0;y<dstImage->height;y++){s=cvGet2D(dstImage,y,x);//ptr[row]=0;if(s.val[0]>0){countCol++;}}if(countCol>0){countColVector.push_back(countCol);}}sort(countColVector.begin(),countColVector.end());maxTotalColAndAngel _colandangel={0,0};_colandangel.angel=_angle;for (int i=countColVector.size()-1;i>=countColVector.size()-2;i--){_colandangel.maxTotalCol+=countColVector[i];}if(changeDirection==false&&_angle==5&&_colandangel.maxTotalCol<=maxTotalColAndA ngelVector[0].maxTotalCol){maxTotalColAndAngelVector.clear();changeDirection=true;_direction=!_direction;_angle=-5;continue;}maxTotalColAndAngelVector.push_back(_colandangel);countColVector.clear();//以便下次存储数据}sort(maxTotalColAndAngelVector.begin(),maxTotalColAndAngelVector.end(),less_second);_actualAngle=maxTotalColAndAngelVector[maxTotalColAndAngelVector.size()-1].angel;dstImage=rotateImage(_image,_actualAngle,_direction);//在maxTotalColAndAngelVector[maxTotalColAndAngelVector.size()-1].angel的正负5度之内寻找旋转角度的精确值if(_actualAngle>0){maxTotalColAndAngelVector.clear();for (int _angleDetail=_actualAngle-5;_angleDetail<=_actualAngle+5;_angleDetail++){dstImage=rotateImage(_image,_angleDetail,_direction);for(int x=dstImage->width-1;x>=0;x--){int countCol=0;for(int y=0;y<dstImage->height;y++){s=cvGet2D(dstImage,y,x);//ptr[row]=0;if(s.val[0]>0){countCol++;}}if(countCol>0){countColVector.push_back(countCol);}}sort(countColVector.begin(),countColVector.end());maxTotalColAndAngel _colandangel={0,0};_colandangel.angel=_angleDetail;for (int i=countColVector.size()-1;i>=countColVector.size()-2;i--){_colandangel.maxTotalCol+=countColVector[i];}maxTotalColAndAngelVector.push_back(_colandangel);countColVector.clear();//以便下次存储数据}sort(maxTotalColAndAngelVector.begin(),maxTotalColAndAngelVector.end(),less_second);_actualAngle=maxTotalColAndAngelVector[maxTotalColAndAngelVector.size()-1].angel;dstImage=rotateImage(_image,_actualAngle,_direction);}imshow("旋转后的车牌图像",(Mat)dstImage);//这里只是水平旋转的结果//我们把这个水平旋转的角度传给原始的车脸图像,对车脸图像进行旋转IplImage* srcDst=rotateImage(srcImage,_actualAngle,_direction);imshow("车脸图片的旋转",(Mat)srcDst);//下面还要加一个垂直旋转CvSize cvsize=cvSize(172,60);IplImage*plateImage2=cvCreateImage(cvsize,dstImage->depth,dstImage->nChannels); CvRect roiSrc =cvRect(dstImage->width/2-86,dstImage->height/2-30,172,60); cvSetImageROI(dstImage, roiSrc);CvRect roiPlate=cvRect(0,0,172,60);cvSetImageROI(plateImage2,roiPlate);cvCopy(dstImage,plateImage2);cvResetImageROI(dstImage);cvResetImageROI(plateImage2);imshow("裁剪后的图像",(Mat)plateImage2);//去除一些边边框框//我们最好在这里对plateImage2进行垂直旋转//对经过处理后的车牌进行垂直投影处理grayPlateImage=plateImage2;bool flagYN=VerticalProjectionToPlateImage(plateImage2);if(flagYN==false)return false;}//对提取出来的车牌图像的一个处理//把字符设置成白色//把背景设置成黑色bool DoWithPlateImage(){//首先把PlateImage进行灰度化处理grayPlateImage=cvCreateImage(cvGetSize(plateImage),8,1);cvCvtColor(plateImage,grayPlateImage,CV_RGB2GRAY);for(int col=0;col<grayPlateImage->height;col++){uchar* ptr=(uchar*)(grayPlateImage->imageData+col*grayPlateImage->widthStep);for(int row=0;row<grayPlateImage->width;row++){int flag=IsBlueOrWhite(plateImage,col,row);////flag为0代表是白色点flag为1代表是蓝色点if(flag==1||flag==2){ptr[row]=0;}else{ptr[row]=255;}}}//做一个开操作和闭操作cvShowImage("经处理后的车牌图像",grayPlateImage);//我们最好对车牌定一个统一的尺寸CvSize cvsize=cvSize(175,70);IplImage* pDstImage = cvCreateImage(cvsize, grayPlateImage->depth, grayPlateImage->nChannels);cvResize(grayPlateImage, pDstImage, CV_INTER_AREA ); //象素关系重采样当图像缩小时候,该方法可以避免波纹出现。