基于opencv的车牌识别(三)车牌ROI提取,字符分割及识别

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/carloswc/article/details/76141587

接上文,我们通过一些列预处理获得了较为明显的车牌区域,接下来我们我们将车牌区域提取出来,利用的是中国车牌的大小长宽比,能够将车牌外接矩形与其他的外接矩形区分出来。代码如下:

	vector<vector<Point>>contours1;
	findContours(closeImg, contours1, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	Mat result(plate.size(), CV_8U, Scalar(255));
	//drawContours(result, contours1, -1, 0, 2);
	Mat contourPlate = threshImg.clone();
	vector<RotatedRect>plateRects;
	for (int i = 0; i < contours1.size(); i++)
	{
		RotatedRect rect = minAreaRect(contours1[i]);
		if (verifySizes(rect)) {
			plateRects.push_back(rect);
		}
	}
	cout << plateRects.size();
	Mat carPlate = threshPlate(plateRects[0].boundingRect());
	imshow("plateArea", carPlate);

车牌判断函数:

//-----------------------------------【verifySizes函数】----------------------------------
//		描述:判断是否为车牌区域
//-----------------------------------------------------------------------------------------------
bool verifySizes(RotatedRect mr)
{
	float error = 0.4;
	//Spain car plate size: 52x11 scale 4,7272  
	float scale = 3.667;
	//Set a min and max area. All other patchs are discarded  
	int min = 150 * scale *100; // minimum area  
	int max = 200 * scale * 200; // maximum area  
								 //Get only patchs that match to a respect ratio.  
	float rmin = scale - scale*error;
	float rmax = scale + scale*error;

	int area = mr.size.height * mr.size.width;
	float r = (float)mr.size.width / (float)mr.size.height;
	if (r<1)
		r = (float)mr.size.height / (float)mr.size.width;

	if ((area < min || area > max) || (r < rmin || r > rmax)) {
		return false;
	}
	else {
		return true;
	}

}


接下来我们对车牌区域进行处理,同样适用轮廓提取法,利用长款比及大小区分,原理与车牌提取类似,只是中文字符会出现上下或者左右分块的问题,我们单独列出来中文字符的处理
	vector<vector<Point>> contours;
	vector<Vec4i>hierarchy;
	findContours(carPlate, contours, hierarchy, CV_RETR_EXTERNAL,
		CV_CHAIN_APPROX_NONE);
	Mat contour_plate = carPlate.clone();
	vector<RotatedRect>rects;
	vector<float>point_x;
	vector<float>widths;
	vector<float>heights;
	Point2f tempPoint[4];
	vector<Mat>chars;
	Mat image_ROI;
	float y0;
	/*************************英文字符处理**************************/
	for (int i = 0; i<contours.size(); i++)
	{
		//绘制轮廓的最小外结矩形  
		RotatedRect rect = minAreaRect(contours[i]);
		Point2f P[4];
		rect.points(P);
		if ((P[0].y - P[1].y)>100 && (P[0].y - P[1].y) < 150
			&& (P[2].x - P[1].x) > 50 && (P[2].x - P[1].x) < 100)
		{
			point_x.push_back(P[1].x);
			widths.push_back(rect.size.width);
			heights.push_back(rect.size.height);
			rects.push_back(rect);
			image_ROI = cut_plate(Rect(P[1], rect.size));
			chars.push_back(image_ROI);
			y0 = P[1].y;
		}
		for (int j = 0; j <= 3; j++)
		{
			line(contour_plate, P[j], P[(j + 1) % 4], Scalar(255), 2);
			//cout << "P" << j << "(" << P[j].x << "," << P[j].y << ")" << endl;
		}

	}
	cout << rects.size()<<endl;

	/***************************中文字符处理*********************/
	float sum = 0;
	float x0 = point_x[0] - (point_x[5] - point_x[1]) / 4;
	float aveWidth = (widths[0] + widths[1] + widths[2] + widths[3] + widths[4] + widths[5]) / 6;
	float aveHeight = (heights[0] + heights[1] + heights[2] + heights[3] + heights[4] + heights[5]) / 6;
	image_ROI = cut_plate(Rect(x0, y0, aveWidth + 10, aveHeight + 10));
	chars.insert(chars.begin(), image_ROI);
	imshow("chars1", chars[0]);
	imshow("chars2", chars[1]);
	imshow("chars3", chars[2]);
	imshow("chars4", chars[3]);
	imshow("chars5", chars[4]);
	imshow("chars6", chars[5]);
	imshow("chars7", chars[6]);
	imshow("contours", contour_plate);
	for (int i = 0; i < chars.size(); i++)
	{
		charRecognize(chars[i], i, i);
	}
	cout << "chars.size=" << chars.size() << '\n' << "车牌号码为" << endl;
	showChars(G_PlateChar);
	waitKey();
	return 0;
字符识别函数,也即为特征提取函数:

//*****************************字符识别函数*************************************/
void charRecognize(Mat src, int num, int char_num)
{
	int k, i, j;
	int char_begin, char_end;
	int num_t[CHARACTER] = { 0 };
	switch (num)
	{
	case 0:char_begin = 36; char_end = 42; break;
	case 1:char_begin = 10; char_end = 35; break;
	case 2:char_begin = 0; char_end = 35; break;
	case 3:char_begin = 0; char_end = 35; break;
	case 4:char_begin = 0; char_end = 35; break;
	case 5:char_begin = 0; char_end = 35; break;
	case 6:char_begin = 0; char_end = 35; break;
	default:break;
	}
	for (k = 0; k<8; k++)
	{
		for (j = int(k / 2) * 15; j<int(k / 2 + 1) * 15; j++)
		{
			for (i = (k % 2) * 10; i<(k % 2 + 1) * 10; i++)
			{
				num_t[k] += src.at<uchar>(i, j) / 255;
			}
		}
		num_t[8] += num_t[k];  // 第9个特征 前8个特征的和作为第9个特征值
	}
	for (i = 0; i<20; i++)  //以下特征也是 固定算法得到的 
		num_t[9] += src.at<uchar>(10, i) / 255;
	for (i = 0; i<20; i++)
		num_t[10] += src.at<uchar>(20, i) / 255;
	for (i = 0; i<20; i++)
		num_t[11] += src.at<uchar>(30, i) / 255;
	for (j = 0; j<40; j++)
		num_t[12] += src.at<uchar>(j, 7) / 255;
	for (j = 0; j<40; j++)
		num_t[13] += src.at<uchar>(j, 10) / 255;
	for (j = 0; j<40; j++)
		num_t[14] += src.at<uchar>(j, 13) / 255;
	int num_tt[CHARACTER] = { 0 };
	int matchnum = 0;  //可以说是 匹配度或 相似度
	int matchnum_max = 0;
	int matchcode = 0;         // 匹配号
							   //int matchtempnum[10]={0};

	j = 0;

	for (k = char_begin; k <= char_end; k++)
		//for(k=40;k<42;k++)
	{
		matchnum = 0;

		for (i = 0; i<8; i++) //区域的匹配
		{
			//	num_tt[i]= abs(num_t[i]-num[k][i]);	  
			if (abs(num_t[i] - Num_Templete[k][i]) <= 2)//与模板里的相应值进行匹配
				matchnum++;//两者相减,如果绝对值小于2,标记匹配成功一次
		}

		if (Num_Templete[k][i] - abs(num_t[i]) <= 8)//对第9个特征进行匹配 
			matchnum += 2;
		for (i = 9; i<CHARACTER; i++)  // 横竖的匹配  
		{
			if (Num_Templete[k][i] >= 5)  //特征值 大于5 
			{
				if (abs(num_t[i] - Num_Templete[k][i]) <= 1)
					matchnum += 2;
			}
			else if (num_t[i] == Num_Templete[k][i])
			{
				matchnum += 2;
			}
		}
		if (matchnum>matchnum_max)
		{
			matchnum_max = matchnum;  //保留最大的 匹配 
			matchcode = k;  //记录 识别的字符的 索引 
							//matchtempnum[j]=matchnum_min
		}
	}
	//识别输出  存放输出结果
	cout << "templete";
	for (i = 0; i < 15; i++)
	{
		cout << num_t[i] << " ";
	}
	cout << endl;
	G_PlateChar[char_num] = PlateCode[matchcode]; //保存下该字符
}
常用的模板,可通过上述的特征提取函数自行列出,本人手头没有大量车牌,只对自己所用车牌进行模板的修改,其他可自行修改提高准确度:

const int Num_Templete[TEMPLETENUM][CHARACTER] =
{
	{ 16,19,10,12,10,10,15,18,110,3,2,2,3,3,3 },     //0
	{ 9,11,10,10,10,10,9,10,79,2,2,2,0,2,12 },       //1
	{ 18,19,3,18,10,10,23,22,123,4,2,2,7,6,8 },      //2
	{ 119, 58, 150 ,61 ,150, 118 ,94, 87, 837, 18 ,0, 0 ,15, 15, 15 },      //3
	{ 2,18,11,22,20,21,11,18,123,2,4,2,6,7,5 },      //4
	{ 23,19,20,12,9,20,18,22,143,2,4,4,6,6,6 },      //5
	{ 6,13,17,8,15,20,18,20,117,2,2,4,5,7,6 },       //6
	{ 21,21,0,20,8,12,9,11,102,2,2,2,2,8,15 },       //7
	{ 9 ,72 ,126, 116, 116, 118, 6, 70, 633 ,9 ,15 ,13 ,25, 29, 33 },     //8
	{ 16,18,15,21,7,19,13,7,116,3,2,2,6,6,5 },       //9
	{ 10,10,16,16,20,20,18,19,129,2,4,2,8,3,6 },     //A
	{ 24,20,20,19,22,22,24,20,171,4,8,4,6,6,6 },     //B
	{ 18,19,20,4,20,8,17,21,127,3,2,4,4,4,4 },       //C
	{ 23,19,11,20,12,20,22,21,148,3,3,3,4,4,4 },     //D
	{ 23,19,21,9,22,8,23,23,148,2,2,2,6,6,6 },       //E
	{ 139,148,143,77,117,69,98,61,852,20,14,14,40,40,40 },        //F
	{ 17,18,22,14,12,24,18,21,146,4,7,4,4,6,6 },     //G
	{ 14,20,18,22,17,22,16,20,149,4,1,4,2,2,2 },     //H
	{ 0,17,0,20,3,20,18,22,100,2,2,4,2,2,2 },        //J
	{ 19,20,26,10,20,20,20,22,157,4,4,4,3,5,11 },    //K
	{ 20,0,20,0,20,0,25,20,105,2,2,2,2,2,2 },        //L
	{ 20,10,27,17,20,10,22,14,140,1,3,3,4,1,5 },     //M
	{ 21,12,25,17,26,12,18,18,149,3,5,3,5,5,6 },     //N 
	{ 23,19,18,20,21,8,22,0,131,3,3,2,4,4,4 },       //P
	{ 18,104,123, 117, 119 ,122, 13, 85, 701, 12, 16 ,14, 29 ,32 },     //Q
	{ 26,19,21,18,21,17,20,21,163,4,3,4,4,6,5 },     //R
	{ 18,18,18,10,8,17,17,22,128,4,3,4,6,6,6 },      //S
	{ 22,18,10,10,10,10,10,10,100,2,2,2,33,2,2 },    //T
	{ 18,12,20,10,20,10,19,21,130,3,3,3,2,2,2 },     //U
	{ 20,19,20,20,15,14,9,10,127,4,4,2,9,1,8 },      //V
	{ 21,25,26,28,16,16,21,19,172,6,2,4,13,0,7 },    //W
	{ 21,21,13,13,12,11,22,21,134,4,2,4,8,0,10 },    //X
	{ 21,20,10,11,10,10,10,11,103,3,2,2,5,2,6 },     //Y
	{ 21,23,5,15,15,5,24,20,128,2,2,2,8,8,7 },       //Z
	{ 13,14,10,10,10,10,13,13,93,2,2,2,29,2,29 },    //I
	{ 20,20,13,20,19,12,17,20,141,3,3,4,4,4,4 },     //O          //36
	{ 14,15,17,17,16,10,25,24,138,0,2,4,12,8,9 },    //云        //37
	{ 17,20,17,12,33,28,23,20,170,3,4,7,13,6,4 },    //苏
	{ 21,21,23,24,24,25,31,27,196,0,9,6,8,6,7 },     //京
	{ 19,27,20,34,19,36,24,37,216,4,4,7,13,28,3 },   //湘
	{ 17,14,23,27,36,40,26,27,210,4,13,4,16,14,14 }, //鲁
	{ 0,1,4,99,0,10,0,6,120,1,16,8,14,8,8 }, // 粤
	{ 22,20,33,37,25,24,24,25,210,13,3,6,12,8,7 }     //蒙
};

//车牌字符
char *PlateCode[TEMPLETENUM] =
{
	"0", "1", "2", "3", "4" ,
	"5","6", "7", "8", "9",
	"A", "B", "C", "D","E",
	"F", "G","H", "J", "K",
	"L", "M", "N","P", "Q",
	"R", "S", "T", "U", "V",
	"W","X", "Y", "Z", "I", "O",
	"云", "苏","京", "湘", "鲁","粤","蒙"
};







猜你喜欢

转载自blog.csdn.net/carloswc/article/details/76141587