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

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

	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",
	"云", "苏","京", "湘", "鲁","粤","蒙"
};







使用VC++6.0做开发工具, 采用简单的SDI框架结构 ,一次处理一幅位图(有兴趣的可以作成MDI) 1)位图信息的数据是从左下往右下为一行,一行一行往上排的。 2)每行像素应该是4的倍数,不足的地方用空点补齐,读的时候注意跳过冗余点。 3)主要数据都存在Doc里面,BMP的主要数据存在一个由ImgData指向的BYTE型的内存空间(根据位图的大小,动态分配的)。 4)数据读进来以后,注 意向内存中贴图,以保证刷新的效率。 5)程序执行流程 应用程序生成--》打开--》CDipView的OnFileOpen 函数--》 调用CDipDoc的FileOpen 函数--》并使用myDoc->UpdateAllViews(NULL); 刷新 自动调用CDipView的OnPaint函数--》调用CDipView的OnDraw函数----一个像素点一个像素点的画 //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 比较重要的地方 读BMP文件,只能打开256色 (可以是灰度) 显示和内存贴图技术 关于调色板: 调色板实际上是一个数组,4个BYTE 分别是 B,G,R,和 Reserved 每一个像素点都有一个相应的数组。 关于VC和windows 的绘图机制: 使用GDI(图形设备接口)对象,通常使用CDC 类,CPaintDC也一样(device-context)设备上下文 windows下的MFC编程机制,消息驱动,事件等待! 全局的app(应用程序对象) 注意 手工分配内存的清除 和CDC对象的删除 以释放系统的GDI资源 每一个new操作符都要对应一个delete 虽然已经弄出来了,还是希望大家好好读读源程序。 你们以后的工作: 在菜单中添加菜单项,通过ClassWizzard 生成消息响应函数(当然也可手动添加), 所有的操作应当是对 BYTE* ImgData;进行的。 在完成相应的功能后 将 isnewfile 和 isnewiamge 置为真 ,并使用myDoc->UpdateAllViews(NULL); 刷新 当然,可以更加有个性化一点,有能力的同学可以自己完成。 随着课程的进行,菜单功能逐渐丰富,最后完成基本的数字图像处理的功能,而不必最后一下完成一个大的作业。
<think>首先,用户的问题非常具体:他们想要使用Python实现车牌字符分割框选的代码。这是在引用提供的背景下,所有引用都涉及Python中的车牌识别。参考包括有关如何执行此操作的信息:[^1]有一个项目代码,[^2]提供了流程,[^3]讨论了形态学变换进行分割,[^4]涵盖了图像处理的基础知识。用户问题:"我想寻找使用Python实现车牌字符分割框选的代码请问Python实现车牌字符分割框选的代码示例"翻译为:"我正在寻找使用Python实现车牌字符分割框选的代码。请问,Python实现车牌字符分割框选的代码示例吗?"我需要提供一个回应,其中包括:1.使用格式清晰的LaTeX数学表达式。2.以中文作答。3.根据系统指示,在末尾生成相关问题。4.结构清晰,一步一步地解决问题。关键点:-"字符分割框选"可能是指对字符进行分割并在图像中绘制边界框。这正是[^3]中描述的形态学变换方法。-引用建议使用OpenCV库进行图像处理,并且[^2]提到使用OpenCV和Tesseract。-我应该提供代码示例并解释步骤。-确保代码以代码块形式呈现,如系统示例中所示。回应的结构:1.**介绍**:简要说明车牌字符分割及其重要性,参考引用资料。2.**步骤分解**:利用引用资料,概述使用Python和OpenCV进行字符分割的关键步骤。-如[^2],[^3],[^4]所述:图像预处理、二值化、形态学操作(膨胀、腐蚀)、分割(例如使用查找轮廓)。3.**代码示例**:提供一个使用OpenCV的Python代码示例。包括:-读取图像-预处理(灰度化,模糊化,阈值处理)-形态学操作以分割字符-查找轮廓并为每个字符绘制边界框-保存或显示结果4.**注意事项**:添加提示,例如安装库(opencv-python等)以及参数微调的需求。5.**相关问题生成**:在末尾添加相关问题,以吸引用户进一步参与。生成相关问题:-基于主题内容,例如字符识别的挑战、替代方法等。-确保问题自然衔接。最后,添加参考标识符,例如[^1],[^2]等。由于引用内容来自用户消息,所以我可以直接用它们,但要自然地融入其中。代码片段:-确保正确编写Python代码。-使用OpenCV函数,如`cv2.cvtColor`、`cv2.threshold`、`cv2.morphologyEx`、`cv2.findContours`、`cv2.drawContours`或`cv2.boundingRect`用于框选。-基于常见实践提供简化本;用户可从参考资料获取更复杂的项目。内容大纲:-**介绍**:在车牌识别过程中,字符分割是关键一步(参考[^3],[^4])。-**关键步骤**:1.**图像预处理**:转换为灰度、高斯模糊以减少噪声([^4])。2.**二值化**:阈值处理以创建二值图像。3.**形态学操作**:如[^3]所述,使用腐蚀和膨胀分离字符。4.**查找轮廓**:检测字符区域并绘制边界框。-**代码示例**:```pythonimportcv2importnumpyasnp#读取图像image=cv2.imread(&#39;license_plate.jpg&#39;)#转换为灰度gray=cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)#高斯模糊blurred=cv2.GaussianBlur(gray,(5,5),0)#二值化(自适应阈值法)thresh=cv2.threshold(blurred,0,255,cv2.THRESH_BINARY_INV+cv2.THRESH_OTSU)[1]#形态学操作:闭合以连接字符kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))closed=cv2.morphologyEx(thresh,cv2.MORPH_CLOSE,kernel)#查找轮廓contours,_=cv2.findContours(closed.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)#绘制边界框forcontourincontours:ifcv2.contourArea(contour)>100:#过滤小区域x,y,w,h=cv2.boundingRect(contour)cv2.rectangle(image,(x,y),(x+w,y+h),(0,255,0),2)#在原始图像上绘制绿色框#显示结果cv2.imshow(&#39;Result&#39;,image)cv2.waitKey(0)cv2.destroyAllWindows()```-**注意**:这只是一个基础示例;实际应用中参数可能需要调整(参考[^4])。-**相关问题**:在最后添加。确保使用中文。</think> ### 使用Python实现车牌字符分割框选的代码示例 在车牌识别系统中,字符分割是核心步骤之一,通过分割可将车牌图像中的每个字符独立提取出来,便于后续识别。该方法基于引用中提到的形态学变换算法(参考引用[^3])和OpenCV图像处理技术(参考引用[^4])。以下是一个完整的Python代码示例,使用OpenCV实现车牌字符分割和框选(即在图像上绘制每个字符的边界框)。 #### 关键步骤概述 1. **图像预处理**:包括灰度化、高斯模糊和二值化,以简化图像特征。 2. **形态学操作**:使用膨胀和腐蚀处理分离字符(基于引用[^3]的形态学变换原理)。 3. **字符分割与框选**:查找轮廓后计算边界框,并在原图上绘制矩形框。 4. **显示结果**:输出框选后的图像。 #### Python代码示例 ```python import cv2 import numpy as np # 步骤1: 读取车牌图像 image = cv2.imread(&#39;license_plate.jpg&#39;) # 替换为你的车牌图像路径 if image is None: print("错误: 无法加载图像。请检查文件路径或格式(支持.jpg/.png等)") exit() # 步骤2: 图像预处理 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 灰度化 blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯模糊(降噪) _, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU) # 二值化(OTSU自动阈值) # 步骤3: 形态学操作分割字符 kernel = np.ones((3, 3), np.uint8) # 形态学核 dilated = cv2.dilate(binary, kernel, iterations=1) # 膨胀(连接相邻字符) eroded = cv2.erode(dilated, kernel, iterations=1) # 腐蚀(分离字符)[^3] # 步骤4: 查找轮廓并框选字符 contours, _ = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) result_image = image.copy() # 复制原图以绘制框选 # 筛选并绘制边界框 for contour in contours: area = cv2.contourArea(contour) if area > 100: # 过滤过小区域(根据实际调整阈值) x, y, w, h = cv2.boundingRect(contour) cv2.rectangle(result_image, (x, y), (x+w, y+h), (0, 255, 0), 2) # 绘制绿色矩形框 # 步骤5: 显示和保存结果 cv2.imshow(&#39;原始车牌图像&#39;, image) cv2.imshow(&#39;字符分割与框选结果&#39;, result_image) cv2.waitKey(0) # 按任意键继续 cv2.destroyAllWindows() # 可选: 保存结果图像 cv2.imwrite(&#39;result_with_boxes.jpg&#39;, result_image) print("操作完成! 结果已显示并可保存。") ``` #### 使用说明 1. **依赖库安装**: ```bash pip install opencv-python numpy ``` 2. **参数调整建议**: - **形态学核大小**:`kernel`尺寸(示例中为$(3, 3)$)可根据字符间距调整。较大值适合密集字符。 - **区域过滤阈值**:`if area > 100`的阈值100需基于图像分辨率调整(高清图可增至500+)。 - **预处理的优化**:如车牌光照不均,可使用自适应阈值(参考引用[^4])替换OTSU: ```python binary = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2) ``` 3. **预期效果**: - 输入车牌图像处理后,每个字符周围会显示绿色矩形框。 - 输出示例:原始图像和框选结果并排显示,便于验证分割准确性。 #### 注意事项 - **输入图像质量**:算法对噪声敏感,建议使用引用[^4]中的高斯模糊等预处理技术提升分割精度。 - **局限性**:复杂背景或遮挡可能导致分割失败(参考引用[^2]的流程介绍)。 - **扩展应用**:分割后可与OCR库(如Tesseract)结合实现完整识别(参考引用[^1]的项目资源)。 如需完整项目代码和文档,可访问引用中的资源链接[^1]。 ###
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值