码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2014-12-16 22:23:02      阅读:270      评论:0      收藏:0      [点我收藏+]

标签:blog   ar   io   color   os   使用   sp   for   on   

       随着智能停车场系统的兴起,车牌识别技术又着实火了一把。我也来了兴趣,大致浏览了下当前的现状,发现目前流行的车牌识别技术是基于神经网络的(这方面可参见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 = binary.ptr();
	memset(data, 0xFF, binary.step * height);

	int i, j;

	double total = 0.0f;
	int counter = 0;
	unsigned char blue, green, red;
	for (i = 0; i < height; ++ i)
	{
		pSrcData = img.ptr(i);
		pBinData = binary.ptr(i);

		int k = 0;
		for (j = 0; j < width; ++ j)
		{
			blue = pSrcData[k];
			green= pSrcData[k + 1];
			red  = pSrcData[k + 2];

			double ave = (blue + green + red) / 3.0f;

			if (blue * 0.75 > green && green * 0.80 > red && ave > 32)
			{
				pBinData[j] = 0;

				++ counter;
				total += blue;
			}
			k += 3;
		}
	}
}

 

       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

       

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

标签:blog   ar   io   color   os   使用   sp   for   on   

原文地址:http://www.cnblogs.com/kewei9/p/4168051.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!