图像处理包括图像二值化、车牌定位、字符分隔、字符识别。
每一步都关系系统成功与否以及好坏。
如果图片二值化不好就不方便车牌定位,如果定位的车牌图片不准确就谈不上字符的切割,字符图片切割不好就难以识别。
这些应该很好理解,可见成员之间需要很好的默契。
而我负责了图像处理中的字符分隔模块,起初我不知道位图形式以及如何读取位图,可见我获取信息的主动性和能力并不好。
非常感谢其他组员提供了读取位图像素数据的相关方法,才能使我能放心去思考切割的算法,而不必去担心如何获取数据的问题。
我使用了一种字符像素横向和纵向扫描的算法,得到字符在横向和纵向的像素分布波形,通常是缓慢的连续变化,车牌越模糊,变化越缓慢。
自然,波峰是字符区,波谷是字符间的空隙区。
它们的分界点并不明显,必然需要找到介于波峰与波谷之间的一个阀值,将波形变成01直方波形。
那么阀值自然是个关键,如果定得不准,就可能切不出所有字符,这是我之前遇到的问题,那时我固定了阀值,使它介于平均波峰值和平均波谷值之间的某个固定点,但这通常只能切割出模糊图片的部分字符,因为有些波峰和波谷并没有被切分开来。
于是我采用了另一种策略,即使用动态扫描,从最小的波谷扫到最大的波峰,并不断计算切得的波峰数量(实际就是字符数量)。
然后判断这个切割数是否符合实际车牌上的字符数量,如果符合,可以停止扫描,切割位置可以明确定在波峰和波谷的变化点上。
当然,我进行了各种优化,比如更多判断来排除各种车牌边框等干扰。
在DOS窗口上经过反复的数据显示测试,终于得到了非常不错的字————————————————————————————————————————————(1)利用字符像素XY方向扫描;(2)分析波形;(3)动态指定阀值;(4)获得01分布;(5)判断波形变化次数;(6)去干扰;(7)获得切割位置;时间有限,有不完善之处可以去本人博客提问:/flashforyou#pragma once#include <cstring>#include <cmath> //数学函数库#include <cstdio>#include <cstdlib>#include <cmalloc>#include "stdafx.h"#include <complex>#define WIDTHBYTES(bits) (((bits)+31)/32*4)/////////////////////////////////////typedef unsigned char BYTE;typedef unsigned short WORD;typedef unsigned long DWORD;typedef long LONG;///////////////////////////////////////***位图文件头信息结构定义//其中不包含文件类型信息(由于结构体的内存结构决定,要是加了的话将不能正确读取文件信息)typedef struct tagBITMAPFILEHEADER {DWORD bfSize; //文件大小WORD bfReserved1; //保留字,不考虑WORD bfReserved2; //保留字,同上DWORD bfOffBits; //实际位图数据的偏移字节数,即前三个部分长度之和} BITMAPFILEHEADER;///////////////////////////////////////***信息头BITMAPINFOHEADER结构,其定义如下:typedef struct tagBITMAPINFOHEADER{//public:DWORD biSize; //指定此结构体的长度,为40LONG biWidth; //位图宽LONG biHeight; //位图高WORD biPlanes; //平面数,为1WORD biBitCount; //采用颜色位数,可以是1,2,4,8,16,24,新的可以是32 DWORD biCompression; //压缩方式,可以是0,1,2,其中0表示不压缩DWORD biSizeImage; //实际位图数据占用的字节数LONG biXPelsPerMeter; //X方向分辨率LONG biYPelsPerMeter; //Y方向分辨率DWORD biClrUsed; //使用的颜色数,如果为0,则表示默认值(2^颜色位数)DWORD biClrImportant; //重要颜色数,如果为0,则表示所有颜色都是重要的} BITMAPINFOHEADER;/////////////////////////////////////////***调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。
24位和32位是不需要调色板的。
//(似乎是调色板结构体个数等于使用的颜色数。
)typedef struct tagRGBQUAD {//public:BYTE rgbBlue; //该颜色的蓝色分量BYTE rgbGreen; //该颜色的绿色分量BYTE rgbRed; //该颜色的红色分量BYTE rgbReserved; //保留值} RGBQUAD;//////////////////////////////////////typedef struct tagHSI {//public:double hsiH; //该颜色的Hdouble hsiS; //该颜色的Sdouble hsiI; //该颜色的Idouble hsiTh; //该颜色的Th} HSIInfo;//////////////////////////////////////typedef struct tagCUT {//public:int no; //切块编号int up; //切块的上坐标int down; //切块的下坐标int left; //切块的左坐标int right; //切块的右坐标} CUT;//////////////////////////////////////////////////***RGB输出函数//参数说明:<--调色板对象-->void showRgbQuan(tagRGBQUAD* pRGB){//printf("(%-3d,%-3d,%-3d) ",pRGB->rgbRed,pRGB->rgbGreen,pRGB->rgbBlue);if(pRGB->rgbRed>0)printf(" ");elseprintf("[X]");}//////////////////////////////////////////////////***设定位图文件头信息//参数说明:<>void MakeBmpHead(BITMAPFILEHEADER &pBmpHead,DWORD biSizeImage){pBmpHead.bfSize=biSizeImage+62;pBmpHead.bfReserved1=0;pBmpHead.bfReserved2=0;pBmpHead.bfOffBits=62;}/////////////////////////////////////////////////***设定位图信息头信息//参数说明:<位图信息头,为图宽,为图高>void MakeBmpInforHead(tagBITMAPINFOHEADER &pBmpInforHead,LONG width,LONG height){ pBmpInforHead.biSize=40;pBmpInforHead.biWidth=width;pBmpInforHead.biHeight=height;pBmpInforHead.biPlanes=1;pBmpInforHead.biBitCount=1;pBmpInforHead.biCompression=0;pBmpInforHead.biSizeImage=WIDTHBYTES(width) * height;pBmpInforHead.biXPelsPerMeter=3780;pBmpInforHead.biYPelsPerMeter=3780;pBmpInforHead.biClrUsed=0;pBmpInforHead.biClrImportant=0;}////////////////////////////////////////////////***设定调色板信息//参数说明:<调色板对象>void MakeColorPlant(tagRGBQUAD *pRgb,long nPlantNum) {for(int i=0;i<nPlantNum;i++){pRgb[i].rgbRed=i*(255);pRgb[i].rgbGreen=i*(255);pRgb[i].rgbBlue=i*(255);pRgb[i].rgbReserved=i;}}/////////////////////////////////////////////////***创建BMP文件//参数说明:<bmp文件名,调色板对象,宽度,高度>void MakeBmpHeader(char* strFile,tagRGBQUAD* dataOfBmp,LONG widthBmp,CUT cut){ FILE* pf;BITMAPFILEHEADER bitHead;BITMAPINFOHEADER bitInfoHead;//////////////int up=cut.up;int down=cut.down;int left=cut.left;int right=cut.right;LONG widthCut=LONG(right-left+1);LONG heightCut=LONG(up-down+1);int no=cut.no;char* charNo;if(no<10){charNo=new char[3];charNo[0]='_';charNo[1]=(char)(48+no);charNo[2]='\0';}else if(no<100){charNo=new char[4];charNo[0]='_';charNo[1]=(char)(48+no-no%10);charNo[2]=(char)(48+no%10);charNo[3]='\0';}else{printf("数字大,不考虑!");return;}//构造生成图片的文件名char* strnFile= new char[100];for(int i = 0; strFile[i] != '\0'; i++){if(i==99){printf("Name of File too long!");return;}strnFile[i]=strFile[i];if(strFile[i] == '.'){strnFile[i] = '\0';break;}}strcat(charNo,".bmp");strcat(strnFile,charNo);pf = fopen(strnFile,"wb");//打开文件//文件头信息设定WORD fileType=0x4D42;fwrite(&fileType,1,sizeof(WORD),pf); //===========================RMakeBmpInforHead(bitInfoHead,widthCut,heightCut);MakeBmpHead(bitHead,bitInfoHead.biSizeImage);fwrite(&bitHead,1,sizeof(tagBITMAPFILEHEADER),pf); //====================Rfwrite(&bitInfoHead,1,sizeof(BITMAPINFOHEADER),pf);//=======================R//调色板信息设定long nPlantNum = long(pow(2,double(bitInfoHead.biBitCount))); // Mix color Plant Number;tagRGBQUAD *pRgb = (tagRGBQUAD *)malloc(nPlantNum*sizeof(tagRGBQUAD));memset(pRgb,0,nPlantNum*sizeof(tagRGBQUAD)); //都设置为0MakeColorPlant(pRgb,nPlantNum);fwrite(pRgb,4,nPlantNum,pf); //===================R//写入像素信息BYTE *pColorData = (BYTE *)malloc(bitInfoHead.biSizeImage);memset(pColorData,0,bitInfoHead.biSizeImage*sizeof(BYTE));int l_width = WIDTHBYTES(widthCut); //4字节整倍化for(int i = down; i < up+1; i++ )for(int j = left; j < right+1; j++ ){int k = (i-down)*l_width + (j-left)/8; //k:取得该像素颜色数据在实际数据数组中的序号int index = i*widthBmp+j; //index:取得该像素在实际像素信息数组中的序号BYTE dataAns = (dataOfBmp[index].rgbBlue+dataOfBmp[index].rgbGreen+dataOfBmp[index].rgbRed)/3/128;//25 5/128或0/128switch((j-left)%8){case 0:pColorData[k] |= (dataAns<<7);break;case 1:pColorData[k] |= (dataAns<<6);break;case 2:pColorData[k] |= (dataAns<<5);break;case 3:pColorData[k] |= (dataAns<<4);break;case 4:pColorData[k] |= (dataAns<<3);break;case 5:pColorData[k] |= (dataAns<<2);break;case 6:pColorData[k] |= (dataAns<<1);break;case 7:pColorData[k] |= dataAns;break;}}fwrite(pColorData,1,bitInfoHead.biSizeImage,pf);//====================R printf("Build sucessful!\n");if(fclose(pf)==0){printf("closed!!!\n");}free(pColorData);free(pRgb);return ;}/////////////////////////////////////////////////////***处理BMP文件//参数说明:<车牌bmp文件名,切割字符数>void DealFile(char* strFile,int numOfCut){//puts(strFile);if(numOfCut<1)return;//防止输入负数或者0,因为无意义;BITMAPFILEHEADER bitHead;BITMAPINFOHEADER bitInfoHead;FILE* pfile;pfile = fopen(strFile,"rb"); //打开文件if(pfile!=NULL){printf("file bkwood.bmp open success.\n");//读取位图文件头信息//////////////////WORD fileType;fread(&fileType,1,sizeof(WORD),pfile);if(fileType != 0x4D42){printf("file is not .bmp file!");return ;}fread(&bitHead,1,sizeof(tagBITMAPFILEHEADER),pfile);////////////////////////////////////////读取位图信息头信息///////////////////fread(&bitInfoHead,1,sizeof(tagBITMAPINFOHEADER),pfile);//////////////////////////////////////}else{printf("file open fail!\n");return ;}tagRGBQUAD *pRgb ;if(bitInfoHead.biBitCount != 8){printf("非256色BMP位图!\n"); //输出像素信息return;}//读取调色盘结信息long nPlantNum = long(pow(2,double(bitInfoHead.biBitCount))); // Mix color Plant Number;pRgb=(tagRGBQUAD *)malloc(nPlantNum*sizeof(tagRGBQUAD));memset(pRgb,0,nPlantNum*sizeof(tagRGBQUAD));fread(pRgb,4,nPlantNum,pfile);printf("Color Plate Number: %d\n",nPlantNum);printf("\n");int width = bitInfoHead.biWidth;int height = bitInfoHead.biHeight;//分配内存空间把源图存入内存int l_width = WIDTHBYTES(width* bitInfoHead.biBitCount); //计算位图的实际宽度并确保它为4的倍数BYTE *pColorData=(BYTE *)malloc(height*l_width);memset(pColorData,0,height*l_width);long nData = height*l_width;//把位图数据信息读到数组里fread(pColorData,1,nData,pfile);//将位图数据转化为RGB数据tagRGBQUAD* dataOfBmp;dataOfBmp = (tagRGBQUAD *)malloc(width*height*sizeof(tagRGBQUAD));//用于保存各像素对应的RGB数据memset(dataOfBmp,0,width*height*sizeof(tagRGBQUAD)); //将内存空间dataOfBmp的前0个字节值设置为width*height*sizeof(tagRGBARAD);////处理8位图////////////////////////////int k,index=0;for(int i=0;i<height;i++)for(int j=0;j<width;j++){BYTE mixIndex= 0;k = i*l_width + j;mixIndex = pColorData[k];dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;index++;}////////////////////////////////////////printf("%d个像素数据信息:\n",width*height); //输出像素信息///读完了信息,马上关闭,与fopen对应fclose(pfile);//int projectionOnX[500];int *projectionOnX = new int[width]; //用来存储投影量的整型数组int *projectionOnY = new int[height]; //用来存储投影量的整型数组for(int i=0;i<width;++i){projectionOnX[i]=0;}for(int i=0;i<height;++i){projectionOnY[i]=0;}//////////////////////////////////粗略显示图片for(int i=height-1;i>=0;--i){if(i%6==0)printf("\n");for(int j=0;j<width;++j){if(i%6==0 && j%6==0){int k = i*width + j;//int num=dataOfBmp[k].rgbRed;showRgbQuan(&dataOfBmp[k]);//if(dataOfBmp[k].rgbRed>0) projectionOnX[j]+=1;}}printf("\n\n<自上而下横向扫描>:\n"); printf("<水平方向投影分布>\n\n"); //水平X轴上投影投影值记录/////////////////////////////////for(int i=height-1;i>=0;--i){//if(i%6==0)printf("\n");for(int j=0;j<width;++j){int k = i*width + j;//int num=dataOfBmp[k].rgbRed;if(dataOfBmp[k].rgbRed>0){projectionOnX[j]+=1;projectionOnY[i]+=1;}//end if}//end for}//end for////////////////////////////////for(int i=height-1;i>=0;--i){//if(i%6==0)printf("\n");for(int j=0;j<width;++j){int k = i*width + j;//int num=dataOfBmp[k].rgbRed;if(dataOfBmp[k].rgbRed>0){if(i==(height-1) || i==0){if(projectionOnX[j]>(height*0.8)){ projectionOnX[j]=0;}}if(j==(width-1) || j==0){if(projectionOnY[i]>(width*0.8)){projectionOnY[i]=0;}}}//end if}//end for}//end for//求平均投影值//获得一个01分布数组double rateChar=0.75;int minNum=int((1-rateChar)*width); int *min=new int[minNum];for (int m=0;m<minNum;++m){min[m]=height;}int averageMin=0;int maxNum=int(rateChar*width);int *max=new int[maxNum];for (int m=0;m<maxNum;++m){max[m]=0;}int maxAll=0;int averageMax=0;//for(int i=0;i<width;++i)projectionOnX[i]=averageMax;for(int m=0;m<width;++m){for (int i=0; i<minNum-1; ++i){// printf("[%d]",s);if(projectionOnX[m] < min[i]) //判断插入的条件{for(int j=minNum-2; j>=i; --j){ //插入地址的元素依次向后移动一位,min[j+1]=min[j];}min[i]=projectionOnX[m];break; //已经判断到插入位置并移位,不必再判断}}for (int i=0; i<maxNum-1; ++i){if(projectionOnX[m] > max[i]) //判断插入的条件{for(int j=maxNum-2; j>=i; --j){ //插入地址的元素依次向后移动一位,max[j+1]=max[j];}max[i]=projectionOnX[m];break; //已经判断到插入位置并移位,不必再判断}}}for(int m=0;m<minNum;++m){minAll+=min[m];}averageMin=int(maxAll/maxNum);for(int m=0;m<maxNum;++m){maxAll+=max[m];}averageMax=int(maxAll/maxNum);printf("[maxNum=%d][minNum=%d][maxAll=%d][minAll=%d][maxAverage=%d]-[averageMin= %d]",maxNum,minNum,maxAll,minAll,averageMax,averageMin);//for(int c=averageMin;c<averageMax;++c){///////////////////////////////////////////~~90%for(int c=min[0];c<max[0];++c){//min to max//100%int *arrOneZero=new int[width];for(int m=0;m<width;++m){arrOneZero[m]=(projectionOnX[m]>c)?1:0;}//遍历projectionOnX[],寻找空白峰值作为切割位置int cutNum=20; //切割位置的数量CUT *cutData=new CUT[cutNum];int mark=0; //指示切割位置记录下标int countOne=0; //投影值为0的列数int preNum=arrOneZero[0]; //用以记录上一列的投影值int *cutPos=new int[cutNum];for(int i=0;i<cutNum;i++)cutPos[i]=-1; //如果坐标为-1,可以认为不在数组中for(int m=0;m<width;++m){int newNum=arrOneZero[m];if(newNum==preNum){if(newNum==1){countOne+=1;}}else{if(countOne>3) //width/10)//黑色部分,也就是字体投影应该大于车牌宽度的10%{//切割记录cutData[mark].left=m-countOne-1;cutData[mark].right=m;mark+=1;//进行插入,并保持排序,以下是计算优先程度,粗略的情况下可有可无for (int i=0; i<cutNum-1; ++i){int index=cutPos[i];int num=projectionOnX[m];// printf("[%d]",lastMid);if(index=-1 || num <= projectionOnX[index] ) //判断插入的条件,=-1可以认为极小,可以插入{for(int j=cutNum-2; j>=i; --j){ //插入地址的元素依次向后移动一位,cutPos[j+1]=cutPos[j];}cutPos[i]=m;break; //已经判断到插入位置并移位,不必再判断}}//end forfor (int i=0; i<cutNum-1; ++i){int index=cutPos[i];if(index==-1)break;int num=projectionOnX[m-countOne-1];// printf("[%d]",s);if(index==-1 || num <= projectionOnX[index] )//判断插入的条件{for(int j=cutNum-2; j>=i; --j){ //插入地址的元素依次向后移动一位,cutPos[j+1]=cutPos[j];}cutPos[i]=m-countOne-1;break;//已经判断到插入位置并移位,不必再判断}}}//end ifcountOne=1;}//end elsepreNum=newNum; //用完了当前投影,注意保存为下一投影值 }//end forif(mark<numOfCut){continue;}////////////////////////////////////////显示投影波形图和切割位置for(int m=0;m<width;++m){//if(projectionOnX[m]>height*0.2){for(int n=0;n<projectionOnX[m];n+=2){printf("#");}printf("[第%d列]",m);int cut=0;for(int k=0;k<cutNum;++k){if(m==cutPos[k]){cut+=1;printf("<----可能切割[%d]-%d",k,arrOneZero[m]);if(cut==2)break;}}printf("\n");}/////////////////////////////////////printf("\n\n<自左向右竖向扫描>:\n");printf("<垂直方向投影分布>\n\n");int allOnY=0;int averageY=0;int *arrY=new int[height];for(int m=0;m<height;++m){arrY[m]=0;allOnY+=projectionOnY[m];}averageY=allOnY/height;int cutY[2]={0,height-1};int countY=0;for(int m=0;m<height;++m){arrY[m]=projectionOnY[m]>(averageY*3/4)?1:0; }//for(int i=0;i<height;++i)projectionOnY[i]=0;int preY=arrY[0];for(int m=0;m<height;++m){int newY=arrY[m];if(newY==preY){if(arrY[m]==1){countY+=1;}}else{if(countY>int(height*0.3)){cutY[0]=m-countY-1;////////////BUGER2010.8 cutY[1]=m;/////////////////////break;}countY=1;}preY=newY;}////////////显示for(int m=0;m<height;++m){for(int n=0;n<projectionOnY[m];n+=2){printf("#");}printf("[第%d行]",m);int cut=0;for(int k=0;k<2;++k){if(m==cutY[k]){cut+=1;printf("<----可能切割[%d]",k);if(cut==2)break;}}printf("\n");}///////////////////////////////////////////int unposible=0;int widthAverage=(cutData[numOfCut-1].right-cutData[0].left)/numOfCut;for(int m=0;m<mark;++m){cutData[m].no=m;cutData[m].up=cutY[1];cutData[m].down=cutY[0];int w=cutData[m].right-cutData[m].left;if(w>widthAverage){ //有人太穷,有人太富unposible+=(numOfCut-1);if(m==0){cutData[m].left=cutData[m].right-int(widthAverage*3/4);}if(m==(numOfCut-1)){cutData[m].right=cutData[m].left+int(widthAverage*3/4);}//break;}if(w<(widthAverage/3)){unposible+=1;}}if(unposible>numOfCut){continue;}printf("\n\n\n\n<切割位置显示:>\n\n");for(int m=0;m<mark;++m){MakeBmpHeader(strFile,dataOfBmp,width,cutData[m]);printf("切割[%d]--< 上%d,下%d,左%d,右%d >\n",m,cutData[m].up,cutData[m].down,cutData[m].left,cutData[m].right);}/////////printf("\n\n\n\n");/////break;}//////////////////////////////end for form min to maxfclose(pfile);if(bitInfoHead.biBitCount<24)free(pRgb);free(dataOfBmp);free(pColorData);printf("New File...\n");return ;}//***主应用程序int _tmain(int argc, _TCHAR* argv[]){char strfile[50];while(true){printf("Please input the .bmp file name:\n"); scanf("%s",strfile);DealFile(strfile,7);}return 0;}。