-
Notifications
You must be signed in to change notification settings - Fork 66
/
main.py
338 lines (267 loc) · 12.2 KB
/
main.py
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
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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
import cv2
import numpy as np
from skimage.filters import threshold_local
import tensorflow as tf
from skimage import measure
import imutils
def sort_cont(character_contours):
"""
To sort contours from left to right
"""
i = 0
boundingBoxes = [cv2.boundingRect(c) for c in character_contours]
(character_contours, boundingBoxes) = zip(*sorted(zip(character_contours, boundingBoxes),
key=lambda b: b[1][i], reverse=False))
return character_contours
def segment_chars(plate_img, fixed_width):
"""
extract Value channel from the HSV format of image and apply adaptive thresholding
to reveal the characters on the license plate
"""
V = cv2.split(cv2.cvtColor(plate_img, cv2.COLOR_BGR2HSV))[2]
T = threshold_local(V, 29, offset=15, method='gaussian')
thresh = (V > T).astype('uint8') * 255
thresh = cv2.bitwise_not(thresh)
# resize the license plate region to a canoncial size
plate_img = imutils.resize(plate_img, width=fixed_width)
thresh = imutils.resize(thresh, width=fixed_width)
bgr_thresh = cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR)
# perform a connected components analysis and initialize the mask to store the locations
# of the character candidates
labels = measure.label(thresh, neighbors=8, background=0)
charCandidates = np.zeros(thresh.shape, dtype='uint8')
# loop over the unique components
characters = []
for label in np.unique(labels):
# if this is the background label, ignore it
if label == 0:
continue
# otherwise, construct the label mask to display only connected components for the
# current label, then find contours in the label mask
labelMask = np.zeros(thresh.shape, dtype='uint8')
labelMask[labels == label] = 255
cnts = cv2.findContours(labelMask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if imutils.is_cv2() else cnts[1]
# ensure at least one contour was found in the mask
if len(cnts) > 0:
# grab the largest contour which corresponds to the component in the mask, then
# grab the bounding box for the contour
c = max(cnts, key=cv2.contourArea)
(boxX, boxY, boxW, boxH) = cv2.boundingRect(c)
# compute the aspect ratio, solodity, and height ration for the component
aspectRatio = boxW / float(boxH)
solidity = cv2.contourArea(c) / float(boxW * boxH)
heightRatio = boxH / float(plate_img.shape[0])
# determine if the aspect ratio, solidity, and height of the contour pass
# the rules tests
keepAspectRatio = aspectRatio < 1.0
keepSolidity = solidity > 0.15
keepHeight = heightRatio > 0.5 and heightRatio < 0.95
# check to see if the component passes all the tests
if keepAspectRatio and keepSolidity and keepHeight and boxW > 14:
# compute the convex hull of the contour and draw it on the character
# candidates mask
hull = cv2.convexHull(c)
cv2.drawContours(charCandidates, [hull], -1, 255, -1)
_, contours, hier = cv2.findContours(charCandidates, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if contours:
contours = sort_cont(contours)
addPixel = 4 # value to be added to each dimension of the character
for c in contours:
(x, y, w, h) = cv2.boundingRect(c)
if y > addPixel:
y = y - addPixel
else:
y = 0
if x > addPixel:
x = x - addPixel
else:
x = 0
temp = bgr_thresh[y:y + h + (addPixel * 2), x:x + w + (addPixel * 2)]
characters.append(temp)
return characters
else:
return None
class PlateFinder:
def __init__(self):
self.min_area = 4500 # minimum area of the plate
self.max_area = 30000 # maximum area of the plate
self.element_structure = cv2.getStructuringElement(shape=cv2.MORPH_RECT, ksize=(22, 3))
def preprocess(self, input_img):
imgBlurred = cv2.GaussianBlur(input_img, (7, 7), 0) # old window was (5,5)
gray = cv2.cvtColor(imgBlurred, cv2.COLOR_BGR2GRAY) # convert to gray
sobelx = cv2.Sobel(gray, cv2.CV_8U, 1, 0, ksize=3) # sobelX to get the vertical edges
ret2, threshold_img = cv2.threshold(sobelx, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
element = self.element_structure
morph_n_thresholded_img = threshold_img.copy()
cv2.morphologyEx(src=threshold_img, op=cv2.MORPH_CLOSE, kernel=element, dst=morph_n_thresholded_img)
return morph_n_thresholded_img
def extract_contours(self, after_preprocess):
_, contours, _ = cv2.findContours(after_preprocess, mode=cv2.RETR_EXTERNAL,
method=cv2.CHAIN_APPROX_NONE)
return contours
def clean_plate(self, plate):
gray = cv2.cvtColor(plate, cv2.COLOR_BGR2GRAY)
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
_, contours, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
if contours:
areas = [cv2.contourArea(c) for c in contours]
max_index = np.argmax(areas) # index of the largest contour in the area array
max_cnt = contours[max_index]
max_cntArea = areas[max_index]
x, y, w, h = cv2.boundingRect(max_cnt)
rect = cv2.minAreaRect(max_cnt)
rotatedPlate = plate
if not self.ratioCheck(max_cntArea, rotatedPlate.shape[1], rotatedPlate.shape[0]):
return plate, False, None
return rotatedPlate, True, [x, y, w, h]
else:
return plate, False, None
def check_plate(self, input_img, contour):
min_rect = cv2.minAreaRect(contour)
if self.validateRatio(min_rect):
x, y, w, h = cv2.boundingRect(contour)
after_validation_img = input_img[y:y + h, x:x + w]
after_clean_plate_img, plateFound, coordinates = self.clean_plate(after_validation_img)
if plateFound:
characters_on_plate = self.find_characters_on_plate(after_clean_plate_img)
if (characters_on_plate is not None and len(characters_on_plate) == 8):
x1, y1, w1, h1 = coordinates
coordinates = x1 + x, y1 + y
after_check_plate_img = after_clean_plate_img
return after_check_plate_img, characters_on_plate, coordinates
return None, None, None
def find_possible_plates(self, input_img):
"""
Finding all possible contours that can be plates
"""
plates = []
self.char_on_plate = []
self.corresponding_area = []
self.after_preprocess = self.preprocess(input_img)
possible_plate_contours = self.extract_contours(self.after_preprocess)
for cnts in possible_plate_contours:
plate, characters_on_plate, coordinates = self.check_plate(input_img, cnts)
if plate is not None:
plates.append(plate)
self.char_on_plate.append(characters_on_plate)
self.corresponding_area.append(coordinates)
if (len(plates) > 0):
return plates
else:
return None
def find_characters_on_plate(self, plate):
charactersFound = segment_chars(plate, 400)
if charactersFound:
return charactersFound
# PLATE FEATURES
def ratioCheck(self, area, width, height):
min = self.min_area
max = self.max_area
ratioMin = 3
ratioMax = 6
ratio = float(width) / float(height)
if ratio < 1:
ratio = 1 / ratio
if (area < min or area > max) or (ratio < ratioMin or ratio > ratioMax):
return False
return True
def preRatioCheck(self, area, width, height):
min = self.min_area
max = self.max_area
ratioMin = 2.5
ratioMax = 7
ratio = float(width) / float(height)
if ratio < 1:
ratio = 1 / ratio
if (area < min or area > max) or (ratio < ratioMin or ratio > ratioMax):
return False
return True
def validateRatio(self, rect):
(x, y), (width, height), rect_angle = rect
if (width > height):
angle = -rect_angle
else:
angle = 90 + rect_angle
if angle > 15:
return False
if (height == 0 or width == 0):
return False
area = width * height
if not self.preRatioCheck(area, width, height):
return False
else:
return True
class NeuralNetwork:
def __init__(self):
self.model_file = "./model/binary_128_0.50_ver3.pb"
self.label_file = "./model/binary_128_0.50_labels_ver2.txt"
self.label = self.load_label(self.label_file)
self.graph = self.load_graph(self.model_file)
self.sess = tf.Session(graph=self.graph)
def load_graph(self, modelFile):
graph = tf.Graph()
graph_def = tf.GraphDef()
with open(modelFile, "rb") as f:
graph_def.ParseFromString(f.read())
with graph.as_default():
tf.import_graph_def(graph_def)
return graph
def load_label(self, labelFile):
label = []
proto_as_ascii_lines = tf.gfile.GFile(labelFile).readlines()
for l in proto_as_ascii_lines:
label.append(l.rstrip())
return label
def convert_tensor(self, image, imageSizeOuput):
"""
takes an image and tranform it in tensor
"""
image = cv2.resize(image, dsize=(imageSizeOuput, imageSizeOuput), interpolation=cv2.INTER_CUBIC)
np_image_data = np.asarray(image)
np_image_data = cv2.normalize(np_image_data.astype('float'), None, -0.5, .5, cv2.NORM_MINMAX)
np_final = np.expand_dims(np_image_data, axis=0)
return np_final
def label_image(self, tensor):
input_name = "import/input"
output_name = "import/final_result"
input_operation = self.graph.get_operation_by_name(input_name)
output_operation = self.graph.get_operation_by_name(output_name)
results = self.sess.run(output_operation.outputs[0],
{input_operation.outputs[0]: tensor})
results = np.squeeze(results)
labels = self.label
top = results.argsort()[-1:][::-1]
return labels[top[0]]
def label_image_list(self, listImages, imageSizeOuput):
plate = ""
for img in listImages:
if cv2.waitKey(25) & 0xFF == ord('q'):
break
plate = plate + self.label_image(self.convert_tensor(img, imageSizeOuput))
return plate, len(plate)
if __name__ == "__main__":
findPlate = PlateFinder()
# Initialize the Neural Network
model = NeuralNetwork()
cap = cv2.VideoCapture('test_videos/test.MOV')
while (cap.isOpened()):
ret, img = cap.read()
if ret == True:
cv2.imshow('original video', img)
if cv2.waitKey(25) & 0xFF == ord('q'):
break
# cv2.waitKey(0)
possible_plates = findPlate.find_possible_plates(img)
if possible_plates is not None:
for i, p in enumerate(possible_plates):
chars_on_plate = findPlate.char_on_plate[i]
recognized_plate, _ = model.label_image_list(chars_on_plate, imageSizeOuput=128)
print(recognized_plate)
cv2.imshow('plate', p)
if cv2.waitKey(25) & 0xFF == ord('q'):
break
else:
break
cap.release()
cv2.destroyAllWindows()