首页 > 代码库 > 基于‘匹配’技术的车牌自动识别系统

基于‘匹配’技术的车牌自动识别系统

       随着智能停车场系统的兴起,车牌识别技术又着实火了一把。我也来了兴趣,大致浏览了下当前的现状,发现目前流行的车牌识别技术是基于神经网络的(这方面可参见Mastering OpenCV with Practical Computer Vision Projects一书,由机械工业出版社发行)。人工神经网络这么高深的东西,笔者不太懂,但是作为图像匹配技术出身,我认为这种小case需要那么复杂的东西来完成么?并且训练人工神经网络也是一件麻烦的事情。。。总之在各种不服气之下,笔者基于图像匹配技术编写了一个车牌自动识别算法,自己测试了几十张图像,自我感觉效果不错,所以拿出来显摆显摆,希望大家批评指正。

       先交代一下开发环境吧,本人基于OpenCV2.4.10,用VS2010开发,配合使用了WinGSL数学库,这些东西网上都有的下,我就不多说了。接下来言归正传。

我设计的车牌自动识别算法可大致分为7个大步骤,其中3个主要步骤中都使用了图像匹配技术,具体步骤如下:

       1,基于颜色的车牌分割得到二值化的图像,这一步主要基于绝大多数的车牌都是蓝色背景这一特点(非标非蓝底车牌的识别,需要相对复杂,本文暂不讨论)。

      

//////////////////////////////////////////////////////////////////////////// Step1: segment blue background//////////////////////////////////////////////////////////////////////////void SegmentBackground2(Mat& img, Mat& binary){	binary.create(2, img.size, CV_8UC1);	unsigned char *pSrcData, * pBinData;	int width = img.cols;	int height= img.rows;	unsigned char* data = http://www.mamicode.com/binary.ptr();>

 

       2. 对分割的结果进行同时进行连通区域标记和轮廓跟踪,根据轮廓对于每一个标记区域计算最小包围盒;根据连通区域面积对各独立区域进行排序。然后从大到小,按一定的准则开始合并这些连通区域,最终确定车牌区域。

int LabelBackgroundImg(Mat& binary, Mat& labelImg, std::vector<int>& area, std::vector<CContour>& exContours){	IplImage iplBinary = binary;	labelImg.create(2, binary.size, CV_32S);	int* pLabel = (int*)(labelImg.data);	int num = LabelImagewithExContours(&iplBinary, pLabel, exContours);	area.assign(num, 0);	int w = binary.cols;	int h = binary.rows;	for (int i = 0; i < h; ++ i)	{		int* labelDat = (int*)(labelImg.ptr(i));		for (int j = 0; j < w; ++ j)		{			if (labelDat[j] > 0)			{				int n = labelDat[j] - 1;				++ area[n];			}		}	}	return num;}void UniteComponets(std::vector<int>& area, std::vector<CContour>& exContours, RotatedRect& box, std::vector<int>& labels){	int n2 = exContours.size();	int nn = area.size();	int* ids = new int[nn];	int* area_ = new int[nn];	for (int i = 0; i < nn; ++ i){		ids[i] = i;		area_[i] = area[i];	}	for (int i = 0; i < nn - 1; ++ i)	{		for (int j = i + 1; j < nn; ++ j)		{			if (area_[i] < area_[j])			{				int t = area_[i];				area_[i] = area_[j];				area_[j] = t;				t = ids[i];				ids[i] = ids[j];				ids[j] = t;			}		}	}	delete[] area_;	int id = ids[0];	CPointSet ptSet;	ptSet.Resize(exContours[id].GetLenth());	exContours[id].ExportPointSet(ptSet);	int orgPtNum = ptSet.GetPntsCount();	CvPoint* orgPts = new CvPoint[orgPtNum];	ptSet.ExportData(orgPts);	Mat orgPtMat(1, orgPtNum, CV_32SC2, orgPts);	RotatedRect orgBox = minAreaRect(orgPtMat);	labels.clear();	labels.push_back(id);	for (int i = 1; i < nn; ++ i)	{		int id = ids[i];		ptSet.Resize(exContours[id].GetLenth());		exContours[id].ExportPointSet(ptSet);		int ptNum = ptSet.GetPntsCount();		CvPoint* pts = new CvPoint[orgPtNum + ptNum];		ptSet.ExportData(pts + orgPtNum);		Mat ptMat(1, ptNum, CV_32SC2, pts + orgPtNum);		RotatedRect box = minAreaRect(ptMat);		memcpy(pts, orgPts, sizeof(CvPoint) * orgPtNum);		Mat ptMatTotal(1, orgPtNum+ ptNum, CV_32SC2, pts);		RotatedRect boxTotal = minAreaRect(ptMatTotal);		if (boxTotal.boundingRect().area() < orgBox.boundingRect().area() + box.boundingRect().area() * 1.5)		{			labels.push_back(id);			orgBox = boxTotal;			orgPtNum += ptNum;			CvPoint* temp = orgPts;			orgPts = pts;			pts = temp;		}		delete[] pts;	}	delete[] orgPts;	delete[] ids;	box = orgBox;}

 

       3.重新得到车牌区域的二值图像。

void SetBinaryImage(Mat& binary, Mat& lable, int labelsTotalNum, std::vector<int> plateLabels){	unsigned char* labelMap = new unsigned char[labelsTotalNum + 1];	memset(labelMap, 0xff, labelsTotalNum + 1);	for (unsigned int i = 0; i < plateLabels.size(); ++ i)	{		int id = plateLabels[i] + 1;	//因为标记是从1开始的,而数组下标从0开始		labelMap[id] = 0x00;	}	unsigned char* bDat = binary.data;	memset(bDat, 0xff, binary.step * binary.rows);	int id;	for (int i = 0; i < binary.rows; ++ i)	{		int* lDat = (int*)(lable.ptr(i));		bDat = binary.ptr(i);		for (int j = 0; j < binary.cols; ++ j)		{			id = lDat[j];			if (id > 0)				bDat[j] = labelMap[id];		}	}	delete[] labelMap;}

 

       4.对图像进行归一化处理,用以解决图像大小不一的问题。得到大小相同的车牌区域的图像。

void NormalizeImage(Rect& rect, RotatedRect& box, Mat& binaryImg, Mat& plateImg, 					RotatedRect& newBox, Mat& newBinaryImg, Mat& newPlateImg){	Rect areRect = box.boundingRect();	double ratio = 300.0 / areRect.width;	if (75.0 / areRect.height > ratio)		ratio = 75.0 / areRect.height;	int imgSize[2];		imgSize[0] = int(rect.height* ratio + 100.5f);	imgSize[1] = int(rect.width * ratio + 100.5f);	newBox.center.x = float(imgSize[1] / 2.0);	newBox.center.y = float(imgSize[0] / 2.0);	newBox.angle = box.angle;	newBox.size.width = float(box.size.width  * ratio);	newBox.size.height= float(box.size.height * ratio);	Point2f srcPnts[3];	srcPnts[0].x = float(rect.x);	srcPnts[0].y = float(rect.y);	srcPnts[1] = srcPnts[0];	srcPnts[1].x += rect.width;	srcPnts[2] = srcPnts[0];	srcPnts[2].y += rect.height;	Point2f dstPnts[3];	dstPnts[0].x = 50;	dstPnts[0].y = 50;	dstPnts[1] = dstPnts[0];	dstPnts[1].x += float(rect.width * ratio);	dstPnts[2] = dstPnts[0];	dstPnts[2].y += float(rect.height* ratio);		Mat transMat = getAffineTransform(srcPnts, dstPnts);	//有没有简单的方法求这个变换?	Size dstSize(imgSize[1], imgSize[0]);		newBinaryImg.create(2, imgSize, binaryImg.type());	newBinaryImg = Scalar(255, 0, 0);	warpAffine(binaryImg, newBinaryImg, transMat, dstSize, INTER_NEAREST, BORDER_TRANSPARENT);//是否可用resize函数替代	newPlateImg.create(2, imgSize, plateImg.type());	newPlateImg = Scalar(255, 255, 255);	warpAffine(plateImg, newPlateImg, transMat, dstSize, INTER_LINEAR, BORDER_TRANSPARENT);	//是否可用resize函数替代}// Step2.2: normalize binary image blocks//////////////////////////////////////////////////////////////////////////void Dilate_ErodeBinaryImg(Mat& binaryImg){	int height= binaryImg.rows;	int width = binaryImg.cols;	Mat dist;	distanceTransform(binaryImg, dist, CV_DIST_L2, CV_DIST_MASK_PRECISE);	int i, j;	for (i = 0; i < height; ++ i)	{		float* distDat = (float*)(dist.ptr(i));		unsigned char* binDat = (unsigned char*)(binaryImg.ptr(i));		for (j = 0; j < width; ++ j)		{			if (distDat[j] < 7.5)				binDat[j] = 0;		}	}	dilate(binaryImg, binaryImg, Mat(), Point(-1, -1), 3);}

 

       5.通过图像匹配技术,得到车牌区域的透视投影下的校正图像。

bool RectifyPlateImage(Mat& Img, Mat& binary, RotatedRect& box, Mat& rectifiedImg){	Mat edge = binary.clone();	Canny(binary, edge, 50, 120, 5);	//可用简单的方法来实现	Mat iedge;	edge.convertTo(iedge, -1, -1.0, 255);	//for debug	imwrite(_T("F:\\我的VC10应用程序\\plate num rec\\test\\test_contour_Img.bmp"), iedge);	Mat dist;	distanceTransform(iedge, dist, CV_DIST_L1, CV_DIST_MASK_PRECISE);	for (int i = 0; i < dist.rows; ++ i)	{		float* dat = dist.ptr<float>(i);		for (int j = 0; j < dist.cols; ++ j)			dat[j] = sqrt(dat[j]);	}	int w = Img.cols;	int h = Img.rows;		Point2f verts[4];	box.points(verts);	Point2f normalVerts[4];	GetMatchVertexs(verts, normalVerts);	normalVerts[0].x -= 5; normalVerts[0].y -= 5;	normalVerts[1].x += 5; normalVerts[1].y -= 5;	normalVerts[2].x += 5; normalVerts[2].y += 5;	normalVerts[3].x -= 5; normalVerts[3].y += 5;	FitRectangle(dist, normalVerts);	Point2f orgVerts[4];	float dd = 3;		orgVerts[0].x = 32 - dd;	orgVerts[0].y = 32 - dd;		orgVerts[1].x =288 + dd;	orgVerts[1].y = 32 - dd;		orgVerts[2].x =288 + dd;	orgVerts[2].y = 96 + dd;	orgVerts[3].x = 32 - dd;	orgVerts[3].y = 96 + dd;	Mat transMat = getPerspectiveTransform(normalVerts, orgVerts);	Size sizeDst(320, 128);	warpPerspective(Img, rectifiedImg, transMat, sizeDst);	return true;}void GetMatchVertexs(Point2f* verts, Point2f* normalVerts){	int indexs[4];	double maxMV = verts[0].x * verts[0].y;	double minMV = verts[0].x * verts[0].y;	double maxDV = verts[0].x / verts[0].y;	double minDV = verts[0].x / verts[0].y;	memset(indexs, 0x00, sizeof(int) * 4);		for (int i = 1; i < 4; ++ i)	{		double tempM = verts[i].x * verts[i].y;		double tempD = verts[i].x / verts[i].y;		if (tempM > maxMV)		{			maxMV = tempM;			indexs[2] = i;		}		else if (tempM < minMV)		{			minMV = tempM;			indexs[0] = i;		}		if (tempD > maxDV)		{			maxDV = tempD;			indexs[1] = i;		}		else if (tempD < minDV)		{			minDV = tempD;			indexs[3] = i;		}	}	for (int i = 0; i < 4; ++ i)		normalVerts[i] = verts[indexs[i]];}

 

       6.通过图像匹配技术,进一步分割得到各字符图像区域,

int GetBlueSpacePoints(Mat& templateImg, Point2d* templatePnts){	int h = templateImg.rows;	int w = templateImg.cols;	int cc = 0;	for (int i = 0; i < h; ++ i)	{		unsigned char* pDat = templateImg.ptr(i);		for (int j = 0; j < w; ++ j)		{			if (pDat[j] == 0)			{				templatePnts[cc].x = j;				templatePnts[cc].y = i;				++ cc;			}		}	}	return cc;}bool BlueSpaceMatch(Mat& image, Point2d* templatePnts, int nCount, double val, Point2d center, 					double& scaleRatio, double& centerShiftX, double& centerShiftY){	double a_b[3];	double deltaX[3];	double AL[3];	double MM[9];	Point2d* pPntSet = new Point2d[nCount];	memcpy(pPntSet, templatePnts, sizeof(Point2d) * nCount);	double* pL = new double[nCount];	double* pA = new double[nCount * 3];	for (int j = 0; j < nCount; ++ j){		pPntSet[j].x -= center.x;		pPntSet[j].y -= center.y;	}	double va0, va1, va2, va3, va4;	double sx, sy;	double perror;	perror = 0.0f;	a_b[0] = scaleRatio;	a_b[1] = centerShiftX;	a_b[2] = centerShiftY;	for (int i = 0; i < 100; ++ i)	{						for (int j = 0; j < nCount; ++ j)		{			sx =  a_b[0] * pPntSet[j].x + a_b[1];			sy =  a_b[0] * pPntSet[j].y + a_b[2];			va0 = GetDataValue(image, sx, sy);			va1 = GetDataValue(image, sx + 0.5, sy);			va2 = GetDataValue(image, sx - 0.5, sy);			va3 = GetDataValue(image, sx, sy + 0.5);			va4 = GetDataValue(image, sx, sy - 0.5);			double dX = va1 - va2;			double dY = va3 - va4;			pA[j] = dX * pPntSet[j].x + dY * pPntSet[j].y;			pA[j + 1 * nCount] = dX;			pA[j + 2 * nCount] = dY;									// A			pL[j] = val - va0;										// L = G - G(‘)		}		double error = 0.0;		for(int j = 0; j < nCount; ++ j)			error += pL[j] * pL[j];		if(i == 0)			perror = error;		if (perror < error)		{			for (int i = 0; i < 3; ++ i)			{				deltaX[i] *= 0.5;				a_b[i] -= deltaX[i];			}			int j;			for(j = 0; j < 3; ++ j){				if(fabs(deltaX[j]) > 0.0000001)					break;			}			if(j == 3)				break;			continue;		}		perror = error;		int nOff_M = 0;		for(int j = 0; j < 3; ++ j){			for(int jj = 0; jj < 3; ++ jj){				MM[nOff_M + jj] = 0.0;				for(int kk = 0; kk < nCount; ++ kk)					MM[nOff_M + jj] += pA[j * nCount + kk] * pA[jj * nCount + kk]; 			}							// M = A * A(T)			nOff_M += 3;		}		gslExInverseMatrix(MM, 3);			// M = M(-) 	 			int j;		for(j = 0; j < 3; ++ j){			AL[j] = 0.0;				// AL = A * L			for(int kk = 0; kk < nCount; ++ kk)				AL[j] += pA[j * nCount + kk] * pL[kk];		}		for(j = 0; j < 3; ++ j){			deltaX[j] = 0.0;			for(int jj = 0; jj < 3; ++ jj)				deltaX[j] += MM[j * 3 + jj] * AL[jj]; 		}		// deltaX = (A * A(T))(-) * A(T) * L		for(j = 0; j < 3; j++){			if(fabs(deltaX[j]) > 0.0000001)				break;		}		if(j == 3)			break;		for(j = 0; j < 3; j++)			a_b[j] += deltaX[j];		// a = a + delta / b = b + delta	}	delete[] pL;	delete[] pA;	scaleRatio = a_b[0];	centerShiftX = a_b[1];	centerShiftY = a_b[2];	delete [] pPntSet;	return true;}int GetCharBlockRect(double& scaleRatio, double& centerShiftX, double& centerShiftY, Rect* rects){	Size templateImgSize;	templateImgSize.width = 256;	templateImgSize.height= 64;//	Point2d center;	center.x = templateImgSize.width / 2;	center.y = templateImgSize.height/ 2;	Rect templateBlockRect[7];	int xx[7] = {1, 36, 85, 120, 155, 190, 225};	for (int i = 0; i < 7; ++ i)	{		templateBlockRect[i].x = xx[i];		templateBlockRect[i].y = 4;		templateBlockRect[i].width = 29;		templateBlockRect[i].height= 57;	}////	for (int i = 0; i < 7; ++ i)	{		templateBlockRect[i].x -= int(center.x + 0.5f);		templateBlockRect[i].y -= int(center.y + 0.5f);		rects[i].x = int(templateBlockRect[i].x * scaleRatio + centerShiftX + 0.5f);		rects[i].y = int(templateBlockRect[i].y * scaleRatio + centerShiftY + 0.5f);		rects[i].width = int(templateBlockRect[i].width * scaleRatio + 0.5f);		rects[i].height= int(templateBlockRect[i].height* scaleRatio + 0.5f);	}	return 7;}int ExtractCharBlockImg(Mat& plateImg, Rect* rects, Mat* blockImgs){	Size dstSize;	dstSize.width = 30;	dstSize.height= 60;	for (int i = 0; i < 7; ++ i)	{		int size[2];		size[0] = dstSize.width;		size[1] = dstSize.height;		blockImgs[i].create(2, size, plateImg.type());		Mat temp(plateImg, rects[i]);		resize(temp, blockImgs[i], dstSize);	}	return 7;}

 

       7.通过图像匹配技术,识别各个字符。

int RecognizeWord(InputArray blockImg);int RecognizeChar(InputArray blockImg);int RecognizeNumChar(InputArray blockImg);int RecognizeBlockImg(Mat& blockImg, int i){	if (i < 0 || i > 6)		return -1;		Mat grayImg;	cvtColor(blockImg, grayImg, CV_RGB2GRAY);	Mat grayImgInv;	grayImg.convertTo(grayImgInv, -1, -1.0, 255);	Mat binaryImg;	adaptiveThreshold(grayImgInv, binaryImg, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 51, 9);	Mat binaryImgSmooth;	GaussianBlur(binaryImg, binaryImgSmooth, Size(3, 3), 1.25f, 1.25f);	imwrite(_T("F:\\我的VC10应用程序\\plate num rec\\test\\test_block_binary_char.bmp"), binaryImgSmooth);	int nRes = -1;	if (i == 0)		nRes = RecognizeWord(binaryImgSmooth);	else if (i == 1)		nRes = RecognizeChar(binaryImgSmooth);	else 		nRes = RecognizeNumChar(binaryImgSmooth);	return nRes;}void MatchTemplatesWord(InputArray templatsImg_, InputArray charImg_, std::vector<double>& results);void MatchTemplatesChar(InputArray templatsImg_, InputArray charImg_, std::vector<double>& results);void MatchTemplatesNumChar(InputArray templatsImg_, InputArray numImg_, std::vector<double>& results);int RecognizeWord(InputArray blockImg){	Mat tempImg = imread(_T("..\\template\\word_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE);	std::vector<double> results;	MatchTemplatesWord(tempImg, blockImg, results);	double minVal = results[0];	unsigned int nl = 0;	for (unsigned int i = 1; i < results.size(); ++ i)	{		if (results[i] < minVal)		{			minVal = results[i];			nl = i;		}	}	return nl;}int RecognizeChar(InputArray blockImg){	Mat tempImg = imread(_T("..\\template\\char_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE);	std::vector<double> results;	MatchTemplatesChar(tempImg, blockImg, results);	double minVal = results[0];	unsigned int nl = 0;	for (unsigned int i = 1; i < results.size(); ++ i)	{		if (results[i] < minVal)		{			minVal = results[i];			nl = i;		}	}	return nl;}int RecognizeNumChar(InputArray blockImg){	Mat tempImg = imread(_T("..\\template\\num_char_grayScale_AutoSize_Smooth.bmp"), CV_LOAD_IMAGE_GRAYSCALE);	std::vector<double> results;	MatchTemplatesNumChar(tempImg, blockImg, results);	double minVal = results[0];	unsigned int nl = 0;	for (unsigned int i = 1; i < results.size(); ++ i)	{		if (results[i] < minVal)		{			minVal = results[i];			nl = i;		}	}	return nl;}

 

       代码比较多,有部分不影响主线的代码就没有贴出来了。如果大家有兴趣讨论可以私信我哈。秋秋31667460

       

基于‘匹配’技术的车牌自动识别系统