2023年11月28日发(作者:海马s5质量怎么样)
OpenCVC++案例实战?《车牌号识别》
OpenCV C++案例实战?《车牌号识别》
前?
本?将使?OpenCV C++ 进?车牌号识别。
?、车牌检测
原图如图所?。本案例的需求是进?车牌号码识别。所以,?先我们得定位车牌所在的位置,然后将车牌切割出来。接下来我们就来看看是
如何实现。
1.1.图像预处理
?先经过?些常规的图像预处理,我们可以提取出图像的?致轮廓。然后根据轮廓的特征进?步确定我们所需要查找的轮廓。在这?,不同
的图像需要根据本?图像特征设定预处理算法。所以,本案例的?个缺点就是不具有鲁棒性,只针对特定需求。
1
2
3
4
5
6
7
8
9
10
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//
使?形态学开操作去除?些?轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
如图为经过?值化后的图像,接下来我们就可以使?findContours寻找我们需要的轮廓。根据图像的轮廓特征就可以定位到车牌所在位
置,然后将其从原图中切割出来,以便后续的识别?作。在这?,我定义了?个License结构体,?于存储ROI图像,以及其相对于原图所
在位置。这样在后续的绘制?作中,我们就可以定位到ROI所在位置。
1.2.轮廓提取
1
2
3
4
5
6
//
?定义车牌结构体
struct License
{
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
// RETR_EXTERNAL
使?找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//
根据?积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//
使?多边形近似,进?步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//
计算矩形区域宽??
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//ROI
截取区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
1.3.功能效果
如图为从汽车上定位到的车牌,并将其切割出来以便下?的识别?作。
1.4.功能源码
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
//ROI--
获取车牌所在区域车牌定位
bool Get_License_ROI(Mat src, License &License_ROI)
{
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//
使?形态学开操作去除?些?轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
// RETR_EXTERNAL
使?找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//
根据?积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//
使?多边形近似,进?步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//
计算矩形区域宽??
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//ROI
截取区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
if (License_ROI.mat.empty())
{
return false;
}
return true;
}
?、字符切割
2.1.图像预处理
通过刚才的车牌定位,我们已经将车牌从原图中切割出来了。接下来,我们还需要将车牌上的字符??切割出来,以便进?后续的识别?
作。同理,我们也需要对车牌做同样的预处理操作。
1
2
3
4
5
6
7
8
9
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
经过灰度、阈值、形态学操作后的图像如下图所?。
2.2.轮廓提取
接下来我们进?轮廓提取就可以提取出车牌上的每?个字符了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//
由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//
计算外接矩形宽??
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
如图为切
割出来的字符。不过这?有?个?问题就是,我们切割出来的字符并不是按车牌号码那样顺序排列。所以,在这?我们还得对其重新进?排
序,使其按车牌顺序排列。
1
2
3
4
5
6
7
8
9
10
11
12
13
//
冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
2.3.功能效果
2.4.功能源码
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
//ROI
获取车牌每?个字符区域
bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//
由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//
计算外接矩形宽??
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
//
将筛选出来的字符轮廓按照其左上?点坐标从左到右依次顺序排列
//
冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
if (Character_ROI.size() != 7)
{
return false;
}
return true;
}
三、字符识别
3.1.读取?件
如图所?,为模板图像以及对应的label。我们需要读取?件,进?匹配。在这?我使?UTF8ToGB函数实现读取txt?件,?的是为了在控
制台显?中?时,不会出现乱码情况。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
读取?件图?
bool Read_Data(string filename,vector<Mat>&dataset)
{
vector<String>imagePathList;
glob(filename, imagePathList);
if (imagePathList.empty())return false;
for (int i = 0; i < imagePathList.size(); i++)
{
Mat image = imread(imagePathList[i]);
resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
dataset.push_back(image);
}
return true;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
读取?件标签
bool Read_Data(string filename, vector<string>&data_name)
{
fstream fin;
fin.open(filename, ios::in);
if (!fin.is_open())
{
cout << \"can not open the file!\" << endl;
return false;
}
string s;
while (std::getline(fin, s))
{
string str = UTF8ToGB(s.c_str()).c_str();
data_name.push_back(str);
}
fin.close();
return true;
}
3.2.字符匹配
在这?,我的思路是:使??个for循环,将我们切割出来的字符与现有的模板进?匹配。?这个匹配算法是求两张图像的像素差,以此来
判断图像的相似程度。具体是使?OpenCV absdiff函数计算两张图像的像素差.。
如图为使?absdiff得到的效果图。接下来,我们只需要计算图像中灰度值为0的像素点个数就可以了。像素点个数最少的那个label即为我
们的匹配结果。当然,此?法肯定是会存在误识别的情况的。进?字符匹配的?法还有:模板匹配,基于Hu矩轮廓匹配。?家可以试试。
3.3.功能源码
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
//
识别车牌字符
bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
string filename = \"data/\";
vector<Mat>dataset;
if (!Read_Data(filename, dataset)) return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
Mat roi_gray;
cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);
Mat roi_thresh;
threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
int minCount = 1000000;
int index = 0;
for (int j = 0; j < dataset.size(); j++)
{
Mat temp_gray;
cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);
Mat temp_thresh;
threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
//
计算两张图?的像素差,以此判断两张图?是否相同
Mat dst;
absdiff(roi_thresh, temp_thresh, dst);
int count = pixCount(dst);
if (count < minCount)
{
minCount = count;
index = j;
}
}
result_index.push_back(index);
}
return true;
}
四、效果显?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
显?最终效果
bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);
vector<string>data_name;
if (!Read_Data(\"data_\", data_name))return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
cout << data_name[result_index[i]] << \" \";
//putText
中?显?会乱码,所以采?下?代码
CvxText text(\"C://Windows/Fonts/?正粗?宋简体.ttf\");//
字体
string str = data_name[result_index[i]]; //string char
转
const char*msg = str.data();
IplImage *temp; //Mat IplImage
转
temp = &IplImage(src);
text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
}
return true;
}
在这?,为了使?putText显?中?,我这?加了?些额外的代码。如果需要使?putText显?中?效果的朋友可以??百度?下如何配置
五、源码
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
#include
#include
#include
?本读写
#include
控制台输出中?乱码
#include\"CvxText.h\" //putText
显?中?乱码
using namespace std;
using namespace cv;
//
?定义车牌结构体
struct License
{
Mat mat; //ROI
图?
Rect rect; //ROI
所在矩形
};
//ROI--
获取车牌所在区域车牌定位
bool Get_License_ROI(Mat src, License &License_ROI)
{
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
//
使?形态学开操作去除?些?轮廓
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat open;
morphologyEx(thresh, open, MORPH_OPEN, kernel);
// RETR_EXTERNAL
使?找到最外轮廓
vector<vector<Point>>contours;
findContours(open, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
vector<vector<Point>>conPoly(contours.size());
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
double peri = arcLength(contours[i], true);
//
根据?积筛选出可能属于车牌区域的轮廓
if (area > 1000)
{
//
使?多边形近似,进?步确定车牌区域轮廓
approxPolyDP(contours[i], conPoly[i], 0.02*peri, true);
if (conPoly[i].size() == 4)
{
//
计算矩形区域宽??
Rect box = boundingRect(contours[i]);
double ratio = double(box.width) / double(box.height);
if (ratio > 2 && ratio < 4)
{
//ROI
截取区域
Rect rect = boundingRect(contours[i]);
License_ROI = { src(rect),rect };
}
}
}
}
if (License_ROI.mat.empty())
{
return false;
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
return false;
}
return true;
}
//ROI
获取车牌每?个字符区域
bool Get_Character_ROI(License &License_ROI, vector<License>&Character_ROI)
{
Mat gray;
cvtColor(License_ROI.mat, gray, COLOR_BGR2GRAY);
Mat thresh;
threshold(gray, thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
Mat close;
morphologyEx(thresh, close, MORPH_CLOSE, kernel);
vector<vector<Point>>contours;
findContours(close, contours, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
for (int i = 0; i < contours.size(); i++)
{
double area = contourArea(contours[i]);
//
由于我们筛选出来的轮廓是?序的,故后续我们需要将字符重新排序
if (area > 200)
{
Rect rect = boundingRect(contours[i]);
//
计算外接矩形宽??
double ratio = double(rect.height) / double(rect.width);
if (ratio > 1)
{
Mat roi = License_ROI.mat(rect);
resize(roi, roi, Size(50, 100), 1, 1, INTER_LINEAR);
Character_ROI.push_back({ roi ,rect });
}
}
}
//
将筛选出来的字符轮廓按照其左上?点坐标从左到右依次顺序排列
//
冒泡排序
for (int i = 0; i < Character_ROI.size()-1; i++)
{
for (int j = 0; j < Character_ROI.size() - 1 - i; j++)
{
if (Character_ROI[j].rect.x > Character_ROI[j + 1].rect.x)
{
License temp = Character_ROI[j];
Character_ROI[j] = Character_ROI[j + 1];
Character_ROI[j + 1] = temp;
}
}
}
if (Character_ROI.size() != 7)
{
return false;
}
return true;
}
//txt
从?件中读取中?,防?乱码
string UTF8ToGB(const char* str)
{
string result;
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
string result;
WCHAR *strSrc;
LPSTR szRes;
//
获得临时变量的??
int i = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);
strSrc = new WCHAR[i + 1];
MultiByteToWideChar(CP_UTF8, 0, str, -1, strSrc, i);
//
获得临时变量的??
i = WideCharToMultiByte(CP_ACP, 0, strSrc, -1, NULL, 0, NULL, NULL);
szRes = new CHAR[i + 1];
WideCharToMultiByte(CP_ACP, 0, strSrc, -1, szRes, i, NULL, NULL);
result = szRes;
delete[]strSrc;
delete[]szRes;
return result;
}
//
读取?件图?
bool Read_Data(string filename,vector<Mat>&dataset)
{
vector<String>imagePathList;
glob(filename, imagePathList);
if (imagePathList.empty())return false;
for (int i = 0; i < imagePathList.size(); i++)
{
Mat image = imread(imagePathList[i]);
resize(image, image, Size(50, 100), 1, 1, INTER_LINEAR);
dataset.push_back(image);
}
return true;
}
//
读取?件标签
bool Read_Data(string filename, vector<string>&data_name)
{
fstream fin;
fin.open(filename, ios::in);
if (!fin.is_open())
{
cout << \"can not open the file!\" << endl;
return false;
}
string s;
while (std::getline(fin, s))
{
string str = UTF8ToGB(s.c_str()).c_str();
data_name.push_back(str);
}
fin.close();
return true;
}
//
计算像素点个数
int pixCount(Mat image)
{
int count = 0;
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
int count = 0;
if (image.channels() == 1)
{
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
if (image.at<uchar>(i, j) == 0)
{
count++;
}
}
}
return count;
}
else
{
return -1;
}
}
//
识别车牌字符
bool License_Recognition(vector<License>&Character_ROI, vector<int>&result_index)
{
string filename = \"data/\";
vector<Mat>dataset;
if (!Read_Data(filename, dataset)) return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
Mat roi_gray;
cvtColor(Character_ROI[i].mat, roi_gray, COLOR_BGR2GRAY);
Mat roi_thresh;
threshold(roi_gray, roi_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
int minCount = 1000000;
int index = 0;
for (int j = 0; j < dataset.size(); j++)
{
Mat temp_gray;
cvtColor(dataset[j], temp_gray, COLOR_BGR2GRAY);
Mat temp_thresh;
threshold(temp_gray, temp_thresh, 0, 255, THRESH_BINARY | THRESH_OTSU);
//
计算两张图?的像素差,以此判断两张图?是否相同
Mat dst;
absdiff(roi_thresh, temp_thresh, dst);
int count = pixCount(dst);
if (count < minCount)
{
minCount = count;
index = j;
}
}
result_index.push_back(index);
}
return true;
}
//
显?最终效果
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
//
显?最终效果
bool Draw_Result(Mat src, License &License_ROI, vector<License>&Character_ROI,vector<int>&result_index)
{
rectangle(src, License_ROI.rect, Scalar(0, 255, 0), 2);
vector<string>data_name;
if (!Read_Data(\"data_\", data_name))return false;
for (int i = 0; i < Character_ROI.size(); i++)
{
cout << data_name[result_index[i]] << \" \";
//putText
中?显?会乱码,所以采?下?代码
CvxText text(\"C://Windows/Fonts/?正粗?宋简体.ttf\");//
字体
string str = data_name[result_index[i]]; //string char
转
const char*msg = str.data();
IplImage *temp; //Mat IplImage
转
temp = &IplImage(src);
text.putText(temp, msg, Point(License_ROI.rect.x + Character_ROI[i].rect.x, License_ROI.rect.y + Character_ROI[i].rect.y),Scalar(0,0,255));
}
return true;
}
int main()
{
Mat src = imread(\"\");
if (src.empty())
{
cout << \"No image!\" << endl;
system(\"pause\");
return -1;
}
License License_ROI;
if (Get_License_ROI(src, License_ROI))
{
vector<License>Character_ROI;
if (Get_Character_ROI(License_ROI, Character_ROI))
{
vector<int>result_index;
if (License_Recognition(Character_ROI, result_index))
{
Draw_Result(src, License_ROI, Character_ROI,result_index);
}
else
{
cout << \"未能识别字符!\" << endl;
system(\"pause\");
return -1;
}
}
else
{
cout << \"未能切割出字符!\" << endl;
system(\"pause\");
return -1;
}
}
else
{
cout << \"未定位到车牌位置!\" << endl;
system(\"pause\");
return -1;
}
323
324
325
326
327
328
imshow(\"src\", src);
waitKey(0);
system(\"pause\");
return 0;
}
总结
本?使?OpenCV C++进?车牌号识别,关键步骤有以下?点。
1、车牌定位。案例需求是进?车牌识别。那么我们就得知道车牌在什么位置。将车牌找到之后,需要将车牌切割出来,作为?个整体进?
下??作。
2、字符分割。我们得到了车牌,需要将车牌上的字符??分割出来才能进?下?的识别?作。有个?细节就是需要将字符重新排序。
更多推荐
车牌识别
发布评论