当前位置:   article > 正文

用形态学及HSV完成车牌照识别_c++ 获取hsv

c++ 获取hsv

本文为本人初学opencv的车牌照项目记录。
仅供参考。
使用opencv以及c++实现。

了解车牌照相关特性

1.车牌特点

1.1 车牌照的底色

​ 大多数小型非电动汽车均为蓝底白字,还有部分常见的新能源汽车为绿底黑字,这让我产生了想要用hsv色彩空间分离色彩区域的想法,但该绿底只占车牌整体约三分之二,因此仍需相关后续处理。

1.2 车牌内容

​ 车牌第一位是汉字,代表车户口所在省级行政单位。

​ 车牌第二位位英文字母,代表车户口所在地级市。

​ 车牌后几位为字母和数字混合存在。

​ 此外,由于英文字母 ‘ I ’ 与 ’ O ’ 的与数字 ‘ 1 ’ 与 ’ 0 '容易混淆
因此车牌照中无英文字母 I 与 O 。

​ 因此在模板匹配过程中可以有目标的按车牌照位数选择模板匹配样本。

1.3 车牌形态学特征

​ 车牌长宽比为440:140约为3:1。此数据可作为后续参考。

API设想与实现过程中的改变

如下论述均以一张图为例:

在这里插入图片描述

车牌照部分

1.1车牌照提取部分

设想:使用HSV色彩空间提取inRange部分,在进行形态学开闭运算,腐蚀膨胀操作。

效果如下:(以一张图为例)

在这里插入图片描述
在这里插入图片描述

实现过程中的改变:

​ 但是我们可以发现,HSV提取的绿色区域,与人类认知中的绿色差异较大,参考HSV的绿色范围仍需调整。

​ 经过多次测试,以及结合画图工具,深度理解H,S,V的相关含义后,我将inRange的参数更改如下:

inRange(binary_image_, Scalar(100, 43, 46), Scalar(124, 255, 255), mask1);//原有hsv色彩空间提供颜色区域范围
inRange(binary_image_, Scalar(90, 90, 80), Scalar(120, 160, 255), mask1);//更改后的hsv色彩空间颜色提取范围
  • 1
  • 2

更改后效果如下。
在这里插入图片描述

void LicensePlate::preTreated(Mat& rawImage) {
	//金字塔放缩 改变大小 适应处理参数,未实现
    //HSV色彩空间提取
	Mat mask1,mask2;
	cvtColor(rawImage, binary_image_, CV_BGR2HSV);
	inRange(binary_image_, Scalar(90, 90, 80), Scalar(120, 160, 255), mask1);//h100,96,93
	inRange(binary_image_, Scalar(35, 43, 46), Scalar(77, 255, 255), mask2);

	bitwise_or(mask1, mask2, binary_image_);
	//形态学操作
	Mat kernel_x, kernel_y;
	kernel_x = getStructuringElement(MORPH_RECT, Size(17,4));
	morphologyEx(binary_image_, binary_image_, MORPH_CLOSE, kernel_x, Point(-1, -1), 7);

	kernel_x = getStructuringElement(MORPH_RECT, Size(20, 1));
	kernel_y = getStructuringElement(MORPH_RECT, Size(1, 19));
	dilate(binary_image_, binary_image_, kernel_x);
	erode(binary_image_, binary_image_, kernel_x);
	erode(binary_image_, binary_image_, kernel_y);
	dilate(binary_image_, binary_image_, kernel_y);
	
	medianBlur(binary_image_, binary_image_, 15);
	imshow("LicensePlatecheck", binary_image_);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
1.2车牌照倾斜处理与截取

设想:

1.通过最小矩形选择,排除超级大,超级小的面积,并且可以大概得到车牌的四个角点,进行透视变换,进而通过透视变换截取图像。

2.通过霍夫直线检测获取相交角点,再进行透视变换。(此方法由于噪点影响,导致车牌照的上边缘直线斜率与目标斜率相差较大,因此舍去。)

车牌倾斜处理:

下图效果为设想1的相关实现效果:
在这里插入图片描述
分析错误原因为:

四个角点获取之后,并未确定其具体位置。可能在透视变换过程中原图的左上角对应为透视变换后的右上角,导致了图片的旋转或扭曲。
因此需要通过冒泡排序法确定角点所在的位置。

//冒泡法排序四个角点的位置
for (i = 0; i < 3; ++i)  //比较n-1轮
{
	for (int j = 0; j < 3 - i; ++j)  //每轮比较n-1-i次,
	{
		if (k_check[j].x > k_check[j + 1].x)
		{
			temp.x = k_check[j].x;
			temp.y = k_check[j].y;
			k_check[j].x = k_check[j + 1].x;
			k_check[j].y = k_check[j + 1].y;
			k_check[j + 1].x = temp.x;
			k_check[j + 1].y = temp.y;
		}
	}	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

改正后效果:
在这里插入图片描述

(全景示意图,示意改位置点的下标)
在这里插入图片描述

但由于最小矩形的选择,以及车牌透视,近大远小的原因,导致车牌较为扭曲,因此需要矫正通过最小矩形获得的四个角点的位置。

矫正前的四个角点:
在这里插入图片描述
矫正方法:

在这里插入图片描述

通过对这张图片的观察,我们发现,下边缘由于光影影响较少的原因,边缘提取较为完整笔直,因此我的思路为:

通过从下往上遍历像素点,找到第一个白色像素点。

在这里插入图片描述
在这里插入图片描述
获取到点,并更新该位置的点的数据。

在这里插入图片描述
在上一张图片的row基础上,上移20个行,直接遍历该行的像素,直到遇到白色像素点结束。此方法可以节省遍历时间,并且也可以增加遍历过程的精确度。
在这里插入图片描述
通过这两个点的坐标,用点差法计算斜率k,以及最小矩形获得width,通过勾股定理确定新的角点。
在这里插入图片描述
矫正后的四个角点:
在这里插入图片描述
这四个角点更符合车牌近大远小的特点,为后续透视变换打下了好的基础。

透视变换+截取车牌照:

如果为蓝色底色车牌照
在这里插入图片描述
角点选择位置较好,可以获得完整图片在这里插入图片描述
而绿色车牌照,由于使用HSV色彩空间提取的原因+最小矩形框选,获得的四个角点,只截取到一半车牌照
在这里插入图片描述
通过绿色车牌照的长宽比与蓝色车牌不同,改变对应点的对应数据,更改了透视变换后的截取面积

if (height * 3 < width)
{
	object[0].x = 0; object[0].y = height / 2;
	object[1].x = 0; object[1].y = k_check[1].y / 2.3 + height / 2;
	object[2].x = width; object[2].y = height / 2;		object[3].x = width; 					object[3].y = k_check[1].y / 2.3 + height / 2;//2.3
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

更改后:
在这里插入图片描述

后续改进

后续可以先进行形态学操作,在进行HSV色彩空间提取矩形,可以实现更大的识别率。

字符切割部分

1.1 图像预处理

设想:常规操作,高斯滤波,灰度转换,阈值处理。

由于蓝色车牌照字母为白色,绿色车牌照字母为黑色,最后通过阈值处理后,蓝色车牌照为黑底白字,绿色车牌照为白底黑字:
在这里插入图片描述
在这里插入图片描述
最后通过统计图片中黑色像素点与白色像素点的个数,如果黑色像素点个数大于白色像素点个数,进行位运算,统一为白底黑字。
在这里插入图片描述

	//统一蓝绿底色
	for (int col = 0; col < cols; col++)
	{
		for (int row = 0; row < rows; row++)
		{
			if (binary_image_.at<uchar>(row, col) == 255) white_num++;
			else black_num++;
		}
	}

	if (black_num > white_num)
	{
		Mat dst;
		bitwise_not(binary_image_, dst);
		dst.copyTo(binary_image_);
	}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
切割图片上下左右

通过对于图片的分析,我们需要切除图片上下的铆钉,以及左右的大片黑色区域。

铆钉的切除

我们可以发现车牌字体上下均包含明显的与铆钉分开的横向白色区域。
在这里插入图片描述
在这里插入图片描述
通过把图像分为两半,遍历图像的每一行,统计每一行为白色的像素点个数M,M<60即将该行作为cRange函数的上下边界。

左右大块黑色切除

通过把图像分为两半,遍历图像的每一列,统计每一行为黑色的像素点个数M,M<20即将该行作为cRange的左右边界。
在这里插入图片描述在这里插入图片描述

切除后的效果图:
在这里插入图片描述
在这里插入图片描述

1.2 字符切割部分

设想:对图像进行预处理,通过矩形框选和cRange截取行列截取每一个字符。

通过返回矩形的center_point,以及字符的下标character_number,通过优先队列进行字符排序。

字符切割

进行腐蚀膨胀操作:

分别进行x方向上的,以及y方向上的。

Mat kernelY = getStructuringElement(MORPH_RECT, Size(1, 6));
Mat kernelX = getStructuringElement(MORPH_RECT, Size(2, 1));
  • 1
  • 2

由于x方向上处理,核如果过大会导致字符粘连,因此,x方向上处理的核比较小。x方向的处理仅仅是为了让汉字连在一起,因为其余的数字,字母本来就是一体的。
在这里插入图片描述

处理后的框选效果:
在这里插入图片描述
为了让图片拥有较为统一的尺寸,进行了resize操作,得到每个字符图片如下:

resize(character_check[i], character_check[i], Size(50, 50));
  • 1

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

字符排序

获取字符的center_point.x ,以及字符的下标character_number,通过优先队列排序

typedef pair<int, int> node_pair;
priority_queue <node_pair, vector<node_pair>, greater<node_pair> > q;
q.push(make_pair(x, character_number - 1));
vector<Mat> character_check;
for (int i = 0; i < character.size(); i++)
{
	character_number = q.top().second;
	character_check.push_back(character[character_number]);
	q.pop();	
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

排序效果由于图片过多,不再显示。

模板匹配

在最开始的时候,我们提到过,车牌的特性:

第一位为汉字,第二位为字母,其余为字母加数字。

根据这些特性,我们可以有目标的去匹配模板。

(该图片为忽略for循环的流程)
在这里插入图片描述
通过返回的每一个模板的匹配数值 score ,以及匹配的模板下标 i ,通过优先队列+switch来确定输出的数值。

priority_queue <pair<double,int>, vector<pair<double, int>>, less<pair<double, int>> > best_score;
best_score.push(make_pair(score, i));
int flag = best_score.top().second;//flag为最优匹配模板下标
switch (flag)
{
	case 0 :cout << "冀"; break;
    ……       
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

结果展示

选取四张图片

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

最后总结

车牌识别部分:

并且蓝色,绿色的HSV的范围值可以更加精进,避免因为绿色过灰,蓝色过黑而导致的提取过程中的偏差,不过此偏差,我猜测,也可以通过先进行形态学处理再进行HSV色彩空间提取最小矩形避免。仍未实现。

字符切割部分:

在字符截取过程中,由于字符倾斜,导致截取时会有其他字符出现,预想:可以通过掩膜或者字符去除过小连通面积进而降噪。仍未实现。

模板匹配部分:

在我的理解中,模板越多,精确率会越高,一个字符,由于字符在压缩resize过程中出现的倾斜程度增大,会影响在模板匹配过程中精准度。因此匹配模板的数量和差异化也显得更加重要。

头文件LicensePlate

#ifndef _LICENSEPLATE_
#define _LICENSEPLATE_

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <cmath>


using namespace cv;
using namespace std;
class LicensePlate
{

public:
	void preTreated(Mat& image);
	Mat plate(Mat& image);
protected:
	Mat raw_image_, binary_image_;
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

头文件character

#ifndef _CHARACTER_
#define _CHARACTER_

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>

using namespace cv;
using namespace std;
class Character
{

public:
	void preTreated(Mat& image);
	void characterRowCut();
	void characterColCut();
	vector<Mat> characterCut();
protected:
	Mat binary_image_, raw_image_;
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

头文件template

#ifndef _TEMPLATE_
#define _TEMPLATE_

#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>


using namespace cv;
using namespace std;
class Template
{

public:
	void getWordsMatch(vector<Mat>);
	double templateScore(string , Mat);	
};

#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

源文件LicensePlate

#pragma once
#include"licensePlate.h"


void LicensePlate::preTreated(Mat& rawImage) {
	//金字塔放缩 改变大小 适应处理参数
	Mat mask1,mask2;
	cvtColor(rawImage, binary_image_, CV_BGR2HSV);
	inRange(binary_image_, Scalar(90, 90, 80), Scalar(120, 160, 255), mask1);//h100,96,93
	inRange(binary_image_, Scalar(35, 43, 46), Scalar(77, 255, 255), mask2);

	bitwise_or(mask1, mask2, binary_image_);

	Mat kernel_x, kernel_y;
	kernel_x = getStructuringElement(MORPH_RECT, Size(17,4));
	morphologyEx(binary_image_, binary_image_, MORPH_CLOSE, kernel_x, Point(-1, -1), 7);

	kernel_x = getStructuringElement(MORPH_RECT, Size(20, 1));
	kernel_y = getStructuringElement(MORPH_RECT, Size(1, 19));
	dilate(binary_image_, binary_image_, kernel_x);
	erode(binary_image_, binary_image_, kernel_x);
	erode(binary_image_, binary_image_, kernel_y);
	dilate(binary_image_, binary_image_, kernel_y);
	
	medianBlur(binary_image_, binary_image_, 15);
}

Mat LicensePlate::plate(Mat& rawImage) {
	int i;
	vector<vector<Point>> contours;
	findContours(binary_image_, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
	rawImage.copyTo(raw_image_);

	int size = contours.size();
	for (i = 0; i < size; i++) {
		vector<Point> cn = contours[i];
		RotatedRect rect = minAreaRect(cn);

		Point2f k_check[4], object[4], temp;

		Size2f y = rect.size;
		int width = y.width;
		int height = y.height;
		int wh_temp;
	
		if (width < height)
		{
			wh_temp = width;
			width = height;
			height = wh_temp;
		}

		int area = width * height;

		//排除面积过小的闭合面积,需改进,通过直方图统计等方法改进,未实现
		if (area < 2000) continue;
		
		rect.points(k_check);

		
		//是否需要判断点的位置,判断位置!
		for (i = 0; i < 3; ++i)  //比较n-1轮
		{
			for (int j = 0; j < 3 - i; ++j)  //每轮比较n-1-i次,
			{
				if (k_check[j].x > k_check[j + 1].x)
				{
					temp.x = k_check[j].x;
					temp.y = k_check[j].y;
					k_check[j].x = k_check[j + 1].x;
					k_check[j].y = k_check[j + 1].y;
					k_check[j + 1].x = temp.x;
					k_check[j + 1].y = temp.y;
				}
			}
		}

		if (k_check[0].y > k_check[1].y)
		{
			temp.y = k_check[0].y;
			k_check[0].y = k_check[1].y;
			k_check[1].y = temp.y;
		}

		if (k_check[2].y > k_check[3].y)
		{
			temp.y = k_check[2].y;
			k_check[2].y = k_check[3].y;
			k_check[3].y = temp.y;
		}

		//如果为绿色 按比例增高范围1.3点的坐标
		//根据斜率估算3.4位置的点坐标
		vector<Point2f> k_point;
		Point2f p;
		int row, col;
		int rows = binary_image_.rows, cols = binary_image_.cols;
		int judge = 0;
		for (row = rows - 1; row > 0; row--)
		{
			int res = 0;
			for (int col = 0; col < cols; col++)
			{
				if (binary_image_.at<uchar>(row, col) == 255) res++;
				if (res != 0 && k_point.empty())
				{
					p = Point(col , row); 
					k_point.push_back(p);

					float a = p.y, b = p.x, c = k_check[3].x, d = k_check[3].y, e = k_check[0].x, f = k_check[0].y;
					float d1 = sqrt(pow((a - d),2) + pow((b - c),2));
					float d2 = sqrt(pow((a - f), 2) + pow((b - e), 2));

					if (d2 < d1)
					{
						for (int k = cols - 1; k > 0; k--)
						{
							if (binary_image_.at<uchar>(row - 20, k) == 255)
							{
								p = Point(k , row-20);
								k_point.push_back(p);
								judge = 0;
								break;
							}
						}
					}
					if (d2 > d1)
					{
						for (int k =0; k <cols; k++)
						{
							if (binary_image_.at<uchar>(row - 20, k) == 255)
							{
								p = Point(k, row - 20); 
								k_point.push_back(p);
								judge = 1;
								break;
							}
						}
					}
				}	
			}
		}

		double k=(k_point[1].y - k_point[0].y) / (k_point[1].x - k_point[0].x);
		double data_x = fabs(width * cos(fabs(k)));
		double data_y = fabs(width * sin(fabs(k)));
	
		
		if (judge == 0)
		{
			k_check[1].x = k_point[0].x;
			k_check[1].y = k_point[0].y;
			k_check[3].x = k_check[1].x + data_x;
			k_check[3].y = k_check[1].y - data_y;
		}
		else
		{
			k_check[3].x = k_point[0].x;
			k_check[3].y = k_point[0].y;
			k_check[1].x = k_check[3].x - data_x;
			k_check[1].y = k_check[3].y - data_y;
		}
		
		//绿色相框的改变
		if (height * 3 < width)
		{
			object[0].x = 0; object[0].y = height / 2;
			object[1].x = 0; object[1].y = k_check[1].y / 2.3 + height / 2;
			object[2].x = width; object[2].y = height / 2;
			object[3].x = width; object[3].y = k_check[1].y / 2.3 + height / 2;//2.3
		}
		else
		{
			object[0].x = 0; object[0].y = 0;
			object[1].x = 0; object[1].y = k_check[1].y / 2.3 ;
			object[2].x = width; object[2].y = 0;
			object[3].x = width; object[3].y = k_check[1].y / 2.3;//2.3
		}

		Mat plate(object[3].y, width, CV_32FC3);
		Mat H = getPerspectiveTransform(k_check, object);

		if ((width > (height * 2)) || (width < (height * 4))) {
			warpPerspective(raw_image_, plate, H, plate.size());
			plate.copyTo(raw_image_);
		}
		return raw_image_;
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189

源文件:character

#pragma once
#include"character.h"

void Character::preTreated(Mat& rawImage) 
{
	rawImage.copyTo(raw_image_);
	rawImage.copyTo(binary_image_);
	GaussianBlur(binary_image_, binary_image_, Size(3, 3), 0);
	cvtColor(binary_image_, binary_image_, COLOR_BGR2GRAY);

	threshold(binary_image_, binary_image_, 0, 255, THRESH_OTSU);
	int cols = binary_image_.cols;
	int rows = binary_image_.rows;
	int black_num = 0, white_num = 0;

	//统一蓝绿底色
	for (int col = 0; col < cols; col++)
	{
		for (int row = 0; row < rows; row++)
		{
			if (binary_image_.at<uchar>(row, col) == 255) white_num++;
			else black_num++;
		}
	}

	if (black_num > white_num)
	{
		Mat dst;
		bitwise_not(binary_image_, dst);
		dst.copyTo(binary_image_);
	}
}

void Character::characterColCut()
{
	//切割两侧噪声
	int row, col;
	int rows = binary_image_.rows;
	int cols = binary_image_.cols;
	//cout << "cols" << cols << "rows" << rows<<endl;
	int front_col = -1, end_col = -1;
	Mat crange2;
	for (col = 0; col < cols / 2; col++)
	{
		int res = 0;
		for (row = 0; row < rows; row++)
		{
			if (binary_image_.at<uchar>(row, col) == 255) res = res + 1;
		}
		if (res>60)
		{
			front_col = col;
			break;
		}
	}

	for (col = cols-1; col > cols / 2; col--)
	{
		int res = 0;
		for (row = 0; row < rows; row++)
		{
			if (binary_image_.at<uchar>(row, col) == 255) res = res + 1;
		}
		if (res >60)
		{
			end_col = col;
			break;
		}
	}

	if (front_col ==-1 && end_col ==-1)
	{
		crange2 = binary_image_.colRange(0, cols);
	}
	else if (front_col !=-1 && end_col ==-1)
	{
		crange2 = binary_image_.colRange(front_col, cols);
	}
	else if (front_col == -1 && end_col != -1)
	{
		crange2 = binary_image_.colRange(0, end_col);
	}
	else
	{
		crange2 = binary_image_.colRange(front_col, end_col);
	}
	crange2.copyTo(binary_image_);
}

void Character::characterRowCut()
{
	//直方图处理车牌照
	//水平方向切割铆钉
	int row, col;
	int rows = binary_image_.rows;
	int cols = binary_image_.cols;
	vector<int> range, range1;
	Mat range2;
	//水平切割铆钉等影响因素
	//双层循环遍历像素,统计每行黑值为零的个数
	//hd存储每行的黑色点
	for (row = 0; row < rows / 2; row++)
	{
		int res = 0;
		for (col = 0; col < cols; col++)
		{
			if (binary_image_.at<uchar>(row, col) == 0) res = res + 1;
		}
		
		if (res <20)
		{
			range.push_back(row);
		}
	}
	
	for (row = rows-1; row > rows*4/5; row--)
	{
		int res = 0;
		for (col = 0; col < cols; col++)
		{
			if (binary_image_.at<uchar>(row, col) == 0) res = res + 1;
		}


		if (res < 20)
		{
			range1.push_back(row);
		}
	}
	if (range1.empty() && !range.empty())
	{
		range2 = binary_image_.rowRange(range.back(), rows);
	}
	else if (range.empty() && !range1.empty())
	{
		range2 = binary_image_.rowRange(0, range1.back());
	}
	else if (range.empty() && range1.empty())
	{
		range2 = binary_image_.rowRange(0, rows);
	}
	else
	{
		range2 = binary_image_.rowRange(range.back(), range1.back());
	}
	range2.copyTo(binary_image_);
	imshow("cut", binary_image_);
}

vector<Mat> Character::characterCut()
{
	//截取字符
	Mat dstImage, dstImage1;
	
	binary_image_.copyTo(dstImage);
	binary_image_.copyTo(dstImage1);
	bitwise_not(dstImage, dstImage);
	Mat kernelY = getStructuringElement(MORPH_RECT, Size(1, 6));
	morphologyEx(dstImage, dstImage, MORPH_CLOSE, kernelY, Point(-1, -1), 3);
	Mat kernelX = getStructuringElement(MORPH_RECT, Size(2, 1));
	morphologyEx(dstImage, dstImage, MORPH_CLOSE, kernelX, Point(-1, -1), 2);
	//imshow("dilate", dstImage);

	vector<vector<Point>> contours;
	findContours(dstImage, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);

	typedef pair<int, int> node_pair;
	priority_queue <node_pair, vector<node_pair>, greater<node_pair> > q;
	int character_number = 0;
	vector<Mat> character;

	for (int i = 0; i < contours.size(); i++)
	{
		Rect rect = boundingRect(contours[i]);
		int width = rect.width;
		int height = rect.height;
		int area = rect.area();
		int x = rect.x;
		int y = rect.y;
		
		if (area < 900) continue;
		character_number++;

		q.push(make_pair(x, character_number - 1));

		dstImage1 = binary_image_.rowRange(y, y + height);
		dstImage1 = dstImage1.colRange(x, x + width);
	
		character.push_back(dstImage1);
	}

	vector<Mat> character_check;
	for (int i = 0; i < character.size(); i++)
	{
		character_number = q.top().second;
		character_check.push_back(character[character_number]);
		q.pop();
	}

	//此处应有字符降噪,但未实现

	for (int i = 0; i < character_check.size(); i++)
	{	
		resize(character_check[i], character_check[i], Size(50, 50));
		bitwise_not(character_check[i], character_check[i]);
	}
	
	return character_check;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209

源文件 template

#pragma once
#include"template.h"

double Template::templateScore(string template_path,Mat image)
{
	//tample 仅为string数组之一,需要for循环,循环string.size次
	priority_queue <double, vector<double>, less<double> > max_value;
	Mat input_text_image;
	vector<cv::String> template_path_all;
	glob(template_path, template_path_all, false);
	size_t count = template_path_all.size();
	//存入本路径所有图片
	for (int i = 0; i < count; i++)
	{
		double min_val, max_val;
		Mat result;
		int width = image.cols; int height = image.rows;
		input_text_image=imread(template_path_all[i]);
		resize(input_text_image, input_text_image, Size(width, height));
		//cout <<"s"<< input_text_image.type();
		cvtColor(input_text_image, input_text_image, COLOR_BGR2GRAY);
		//cout <<"2"<< input_text_image.type();
		//system("pause");
		threshold(input_text_image, input_text_image, 0, 255, THRESH_OTSU);
		matchTemplate(image, input_text_image, result, TM_CCOEFF);
		minMaxLoc(result, &min_val, &max_val);
		max_value.push(max_val);
	}
	return max_value.top();
}

void Template::getWordsMatch(vector<Mat> images)
{
	//获取每一个数字,汉字,英语的文件名
	string tample[16] = { "1", "2", "3", "5", "6", "7", "8", "9", "A", "C", "F", "K", "R", "V" ,"沪","冀" };
	//模板路径
	string input_image_path = "/车牌照识别/refer2/";

	string get_chinese_words_list[2];
	get_chinese_words_list[0] = input_image_path + tample[14];
	get_chinese_words_list[1] = input_image_path + tample[15];

	string get_english_words_list[6];
	get_english_words_list[0] = input_image_path + tample[8];
	get_english_words_list[1] = input_image_path + tample[9];
	get_english_words_list[2] = input_image_path + tample[10];
	get_english_words_list[3] = input_image_path + tample[11];
	get_english_words_list[4] = input_image_path + tample[12];
	get_english_words_list[5] = input_image_path + tample[13];

	string get_english_num_words_list[14];
	get_english_num_words_list[0] = input_image_path + tample[8];
	get_english_num_words_list[1] = input_image_path + tample[9];
	get_english_num_words_list[2] = input_image_path + tample[10];
	get_english_num_words_list[3] = input_image_path + tample[11];
	get_english_num_words_list[4] = input_image_path + tample[12];
	get_english_num_words_list[5] = input_image_path + tample[13];
	get_english_num_words_list[6] = input_image_path + tample[0];
	get_english_num_words_list[7] = input_image_path + tample[1];
	get_english_num_words_list[8] = input_image_path + tample[2];
	get_english_num_words_list[9] = input_image_path + tample[3];
	get_english_num_words_list[10] = input_image_path + tample[4];
	get_english_num_words_list[11] = input_image_path + tample[5];
	get_english_num_words_list[12] = input_image_path + tample[6];
	get_english_num_words_list[13] = input_image_path + tample[7];

	for (int index = 0; index < images.size(); index++)
	{
		//车牌第一个图片为汉字
		if (index == 0)
		{
			vector<double> best_score;
			for (int i = 0; i < 2; i++)
			{
				double score = Template::templateScore(get_chinese_words_list[i], images[index]);
				best_score.push_back(score);
			}
			if (best_score[0] > best_score[1])
			{
				cout << "沪";
			}
			else
			{
				cout << "冀";
			}
		}
		//车牌第二个图片为英文
		else if (index == 1)
		{
			priority_queue <pair<double,int>, vector<pair<double, int>>, less<pair<double, int>> > best_score;
			for (int i = 0; i < 6; i++)
			{
				double score = Template::templateScore(get_english_words_list[i], images[index]);
				best_score.push(make_pair(score, i));
			}
			int flag = best_score.top().second;
			switch (flag)
			{
				case 0:cout << "A"; break;
				case 1:cout << "C"; break;
				case 2:cout << "F"; break;
				case 3:cout << "K"; break;
				case 4:cout << "R"; break;
				case 5:cout << "V"; break;
			}
		}
		else
		{
			priority_queue <pair<double, int>, vector<pair<double, int>>, less<pair<double, int>> > best_score;
			for (int i = 0; i < 14; i++)
			{
				double score = Template::templateScore(get_english_num_words_list[i], images[index]);
				best_score.push(make_pair(score, i));
			}
			int flag = best_score.top().second;
			switch (flag)
			{
				case 0:cout << "A"; break;
				case 1:cout << "C"; break;
				case 2:cout << "F"; break;
				case 3:cout << "K"; break;
				case 4:cout << "R"; break;
				case 5:cout << "V"; break;
				case 6:cout << "1"; break;
				case 7:cout << "2"; break;
				case 8:cout << "3"; break;
				case 9:cout << "5"; break;
				case 10:cout << "6"; break;
				case 11:cout << "7"; break;
				case 12:cout << "8"; break;
				case 13:cout << "9"; break;
			}
		}
	}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
#pragma once

#include <opencv2/opencv.hpp>
#include "licensePlate.h"
#include "character.h"
#include "template.h"

using namespace cv;
using namespace std;

int main() 
{
	Mat raw_image = imread("/车牌照识别/3.png");//需要识别的车牌照路径
	Mat dst_image,dst_image1;
	vector<Mat> dst_image2;

	if (raw_image.empty()) {
		cout << "I'm empty!";
		return -1;
	}

	LicensePlate licensePlate;
	licensePlate.preTreated(raw_image);
	dst_image = licensePlate.plate(raw_image);

	Character character;
	character.preTreated(dst_image);
	character.characterColCut();
	character.characterRowCut();
	dst_image2 =character.characterCut();
	
	Template tamplate;
	tamplate.getWordsMatch(dst_image2);

	waitKey(0);
	destroyAllWindows();
	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/593831
推荐阅读
相关标签
  

闽ICP备14008679号