-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathAnnPredict.cpp
260 lines (227 loc) · 6.36 KB
/
AnnPredict.cpp
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
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
259
260
#include "AnnPredict.h"
string AnnPredict::ZHCHARS[] = { "川", "鄂", "赣", "甘", "贵", "桂", "黑", "沪", "冀", "津", "京", "吉", "辽", "鲁", "蒙", "闽", "宁", "青", "琼", "陕", "苏", "晋", "皖", "湘", "新", "豫", "渝", "粤", "云", "藏", "浙" };
char AnnPredict::CHARS[] = { '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' };
AnnPredict::AnnPredict(const char* ann_model, const char* ann_zh_model)
{
ann = ANN_MLP::load(ann_model);
ann_zh = ANN_MLP::load(ann_zh_model);
annHog = new HOGDescriptor(Size(32, 32), Size(16, 16), Size(8, 8), Size(8, 8), 3);
}
AnnPredict::~AnnPredict()
{
ann->clear();
ann.release();
ann_zh->clear();
ann_zh.release();
if (annHog) {
delete annHog;
annHog = 0;
}
}
string AnnPredict::doPredict(Mat final_plate)
{
//预处理
//灰度化
Mat gray;
cvtColor(final_plate, gray, COLOR_BGR2GRAY);
//二值化
Mat shold;
threshold(gray, shold, 0, 255, THRESH_OTSU + THRESH_BINARY);
//imshow("二值化车牌", shold);
if (!clearMaoDing(shold)) {
return string("未识别到车牌");
}
//imshow("去铆钉后", shold);
//求字符轮廓
vector<vector<Point>> contours;
findContours(
shold, //输入图像
contours, //输出图像
RETR_EXTERNAL, //取外接轮廓
CHAIN_APPROX_NONE //取轮廓上所有的像素点
);
RotatedRect rotatedRect;
vector<Rect> vec_ann_rects;
for each (vector<Point> points in contours)
{
Rect rect = boundingRect(points); //取最小外接矩形(可旋转/带角度)
//rectangle(final_plate, rect, Scalar(0, 0, 255)); //画红色矩形
Mat m = shold(rect);
if (verifyCharSize(m)) {
vec_ann_rects.push_back(rect);
}
}
//cout << "size: " << vec_ann_rects.size() << endl;
//for each (Rect rect in vec_ann_rects)
//{
// rectangle(final_plate, rect, Scalar(0, 255, 0)); //画绿色矩形
//}
//imshow("字符轮廓", final_plate);
//排序:从左到右
sort(vec_ann_rects.begin(), vec_ann_rects.end(), [](const Rect& rect1, const Rect& rect2) {
return rect1.x < rect2.x;
});
//获取城市字符框的索引
int cityIndex = getCityIndex(vec_ann_rects);
//反推汉字字符框的位置
Rect chineseRect;
getChineseRect(vec_ann_rects[cityIndex], chineseRect);
vector<Mat> plateCharMats; //保存7个字符图块
plateCharMats.push_back(shold(chineseRect)); //放入中文字符
if (vec_ann_rects.size() < 6) {
return string("未识别到车牌");
}
int count = 6;
for (int i = cityIndex; i < vec_ann_rects.size() && count; i++, count--)
{
plateCharMats.push_back(shold(vec_ann_rects[i])); //放入城市及之后六个字符
}
char winName[100];
for (int i = 0; i < plateCharMats.size(); i++)
{
sprintf(winName, "$d 字符", i);
imshow(winName, plateCharMats[i]);
}
string str_plate;
predict(plateCharMats, str_plate);
return str_plate;
}
//每行像素值的变化次数
//清楚铆钉行&判断是否适合后面的分割字符
bool AnnPredict::clearMaoDing(Mat& src) {
int maxChangeCount = 12;
vector<int> changes; //统计每行的变化次数
for (int i = 0; i < src.rows; i++)
{
int changeCount = 0; //记录第i行的变化次数
for (int j = 0; j < src.cols - 1; j++)
{
char pixel_1 = src.at<char>(i, j); //前像素
char pixel_2 = src.at<char>(i, j + 1); //后像素
if (pixel_1 != pixel_2) {
//像素发生了改变
changeCount++;
}
}
changes.push_back(changeCount);
}
//车牌字符区域的跳变条件
//满足条件的行数约等于字符的高度
int charRows = 0;
for (int i = 0; i < src.rows; i++)
{
if (changes[i] >= 12 && changes[i] <= 60) {
//当前第i行属于车牌字符区域
charRows++;
}
}
//判断字符占比
float heightPercent = charRows * 1.0 / src.rows; //字符高度占比
if (heightPercent <= 0.4) {
return false; //车牌字符高度占比不符合要求
}
//字符区域 约等于白色像素的个数
float src_area = src.rows * src.cols;
float areaPercent = countNonZero(src) * 1.0 / src_area;
if (areaPercent <= 0.2 || areaPercent >= 0.5) {
return false; //车牌字符面积占比不合要求
}
for (int i = 0; i < changes.size(); i++)
{
int changeCount = changes[i];
if (changeCount < maxChangeCount) {
//属于铆钉所在像素行,全部 抹黑(修改像素值为0)
for (int j = 0; j < src.cols; j++)
{
src.at<char>(i, j) = 0;
}
}
}
return true;
}
bool AnnPredict::verifyCharSize(Mat src)
{
//理想宽高比
float aspect = 0.5f;
//真实宽高比
float realAspect = float(src.cols) / float(src.rows);
//字符最小高度
float minHeight = 10.0f;
float maxHeight = 35.0f;
float error = 0.7f; //容错率
float maxAspect = aspect + aspect * error;
float minAspect = 0.05f;
float src_area = src.rows * src.cols;
float areaPercent = countNonZero(src) * 1.0 / src_area;
if (src.rows >= minHeight && src.rows <= maxHeight //高度
&& realAspect >= minAspect && realAspect <= maxAspect //宽高比
&& areaPercent <= 0.8f) {
return true;
}
return false;
}
//获取城市字符索引,参考:show/get_city_character_throry.png
int AnnPredict::getCityIndex(vector<Rect> rects)
{
int cityIndex = 0;
for (int i = 0; i < rects.size(); i++)
{
Rect rect = rects[i];
int midX = rect.x + rect.width / 2;
if (midX < 136 / 7 * 2 && midX > 136 / 7) {
cityIndex = i;
break;
}
}
return cityIndex;
}
void AnnPredict::getChineseRect(Rect cityRect, Rect& chineseRect)
{
//宽度,比城市字符宽一点
float width = cityRect.width * 1.15f;
int cityX = cityRect.x; //城市字符框x
int x = cityX - width;
chineseRect.x = x >= 0 ? x : 0;
chineseRect.y = cityRect.y;
chineseRect.width = width;
chineseRect.height = cityRect.height;
}
void AnnPredict::predict(vector<Mat> plateCharMats, string& str_plate)
{
for (int i = 0; i < plateCharMats.size(); i++)
{
Mat plate_char = plateCharMats[i];
Mat features;
getHogFeatures(annHog, plate_char, features);
Mat sample = features.reshape(1, 1);
Mat response;
Point maxLoc;
Point minLoc;
if (i) { //非0即true
//字母 + 数字
ann->predict(sample, response);
minMaxLoc(response, 0, 0, &minLoc, &maxLoc);
int index = maxLoc.x; //样本索引值(CHAR数组的索引值)
str_plate += CHARS[index];
}
else { //中文
ann_zh->predict(sample, response);
minMaxLoc(response, 0, 0, &minLoc, &maxLoc);
int index = maxLoc.x; //样本索引值(ZHCHAR数组的索引值)
str_plate += ZHCHARS[index];
}
}
}
void AnnPredict::getHogFeatures(HOGDescriptor* hog, Mat src, Mat& dst)
{
//归一化
Mat trainImg = Mat(hog->winSize, CV_32S);
resize(src, trainImg, hog->winSize);
//计算特征
vector<float> descriptor;
hog->compute(trainImg, descriptor, hog->winSize);
Mat feature(descriptor);
feature.copyTo(dst);
feature.release();
trainImg.release();
}