-
Notifications
You must be signed in to change notification settings - Fork 32
/
LicensePlateExtraction.py
224 lines (156 loc) · 9.1 KB
/
LicensePlateExtraction.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
import cv2
import numpy as np
import math
import Functions
img = cv2.imread("car.jpg")
# hsv transform - value = gray image
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
hue, saturation, value = cv2.split(hsv)
# kernel to use for morphological operations
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
# applying topHat/blackHat operations
topHat = cv2.morphologyEx(value, cv2.MORPH_TOPHAT, kernel)
blackHat = cv2.morphologyEx(value, cv2.MORPH_BLACKHAT, kernel)
# add and subtract between morphological operations
add = cv2.add(value, topHat)
subtract = cv2.subtract(add, blackHat)
# applying gaussian blur on subtract image
blur = cv2.GaussianBlur(subtract, (5, 5), 0)
# thresholding
thresh = cv2.adaptiveThreshold(blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 19, 9)
cv2.imshow("thresh", thresh)
# find contours
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
# get height and width
height, width = thresh.shape
# create a numpy array with shape given by threshed image value dimensions
imageContours = np.zeros((height, width, 3), dtype=np.uint8)
# list and counter of possible chars
possibleChars = []
countOfPossibleChars = 0
# loop to check if any (possible) char is found
for i in range(0, len(contours)):
# draw contours based on actual found contours of thresh image
cv2.drawContours(imageContours, contours, i, (255, 255, 255))
# retrieve a possible char by the result ifChar class give us
possibleChar = Functions.ifChar(contours[i])
# by computing some values (area, width, height, aspect ratio) possibleChars list is being populated
if Functions.checkIfChar(possibleChar) is True:
countOfPossibleChars = countOfPossibleChars + 1
possibleChars.append(possibleChar)
imageContours = np.zeros((height, width, 3), np.uint8)
ctrs = []
# populating ctrs list with each char of possibleChars
for char in possibleChars:
ctrs.append(char.contour)
# using values from ctrs to draw new contours
cv2.drawContours(imageContours, ctrs, -1, (255, 255, 255))
plates_list = []
listOfListsOfMatchingChars = []
for possibleC in possibleChars:
# the purpose of this function is, given a possible char and a big list of possible chars,
# find all chars in the big list that are a match for the single possible char, and return those matching chars as a list
def matchingChars(possibleC, possibleChars):
listOfMatchingChars = []
# if the char we attempting to find matches for is the exact same char as the char in the big list we are currently checking
# then we should not include it in the list of matches b/c that would end up double including the current char
# so do not add to list of matches and jump back to top of for loop
for possibleMatchingChar in possibleChars:
if possibleMatchingChar == possibleC:
continue
# compute stuff to see if chars are a match
distanceBetweenChars = Functions.distanceBetweenChars(possibleC, possibleMatchingChar)
angleBetweenChars = Functions.angleBetweenChars(possibleC, possibleMatchingChar)
changeInArea = float(abs(possibleMatchingChar.boundingRectArea - possibleC.boundingRectArea)) / float(
possibleC.boundingRectArea)
changeInWidth = float(abs(possibleMatchingChar.boundingRectWidth - possibleC.boundingRectWidth)) / float(
possibleC.boundingRectWidth)
changeInHeight = float(abs(possibleMatchingChar.boundingRectHeight - possibleC.boundingRectHeight)) / float(
possibleC.boundingRectHeight)
# check if chars match
if distanceBetweenChars < (possibleC.diagonalSize * 5) and \
angleBetweenChars < 12.0 and \
changeInArea < 0.5 and \
changeInWidth < 0.8 and \
changeInHeight < 0.2:
listOfMatchingChars.append(possibleMatchingChar)
return listOfMatchingChars
# here we are re-arranging the one big list of chars into a list of lists of matching chars
# the chars that are not found to be in a group of matches do not need to be considered further
listOfMatchingChars = matchingChars(possibleC, possibleChars)
listOfMatchingChars.append(possibleC)
# if current possible list of matching chars is not long enough to constitute a possible plate
# jump back to the top of the for loop and try again with next char
if len(listOfMatchingChars) < 3:
continue
# here the current list passed test as a "group" or "cluster" of matching chars
listOfListsOfMatchingChars.append(listOfMatchingChars)
# remove the current list of matching chars from the big list so we don't use those same chars twice,
# make sure to make a new big list for this since we don't want to change the original big list
listOfPossibleCharsWithCurrentMatchesRemoved = list(set(possibleChars) - set(listOfMatchingChars))
recursiveListOfListsOfMatchingChars = []
for recursiveListOfMatchingChars in recursiveListOfListsOfMatchingChars:
listOfListsOfMatchingChars.append(recursiveListOfMatchingChars)
break
imageContours = np.zeros((height, width, 3), np.uint8)
for listOfMatchingChars in listOfListsOfMatchingChars:
contoursColor = (255, 0, 255)
contours = []
for matchingChar in listOfMatchingChars:
contours.append(matchingChar.contour)
cv2.drawContours(imageContours, contours, -1, contoursColor)
# cv2.imshow("finalContours", imageContours)
# cv2.imwrite(temp_folder + '10 - finalContours.png', imageContours)
for listOfMatchingChars in listOfListsOfMatchingChars:
possiblePlate = Functions.PossiblePlate()
# sort chars from left to right based on x position
listOfMatchingChars.sort(key=lambda matchingChar: matchingChar.centerX)
# calculate the center point of the plate
plateCenterX = (listOfMatchingChars[0].centerX + listOfMatchingChars[len(listOfMatchingChars) - 1].centerX) / 2.0
plateCenterY = (listOfMatchingChars[0].centerY + listOfMatchingChars[len(listOfMatchingChars) - 1].centerY) / 2.0
plateCenter = plateCenterX, plateCenterY
# calculate plate width and height
plateWidth = int((listOfMatchingChars[len(listOfMatchingChars) - 1].boundingRectX + listOfMatchingChars[
len(listOfMatchingChars) - 1].boundingRectWidth - listOfMatchingChars[0].boundingRectX) * 1.3)
totalOfCharHeights = 0
for matchingChar in listOfMatchingChars:
totalOfCharHeights = totalOfCharHeights + matchingChar.boundingRectHeight
averageCharHeight = totalOfCharHeights / len(listOfMatchingChars)
plateHeight = int(averageCharHeight * 1.5)
# calculate correction angle of plate region
opposite = listOfMatchingChars[len(listOfMatchingChars) - 1].centerY - listOfMatchingChars[0].centerY
hypotenuse = Functions.distanceBetweenChars(listOfMatchingChars[0],
listOfMatchingChars[len(listOfMatchingChars) - 1])
correctionAngleInRad = math.asin(opposite / hypotenuse)
correctionAngleInDeg = correctionAngleInRad * (180.0 / math.pi)
# pack plate region center point, width and height, and correction angle into rotated rect member variable of plate
possiblePlate.rrLocationOfPlateInScene = (tuple(plateCenter), (plateWidth, plateHeight), correctionAngleInDeg)
# get the rotation matrix for our calculated correction angle
rotationMatrix = cv2.getRotationMatrix2D(tuple(plateCenter), correctionAngleInDeg, 1.0)
height, width, numChannels = img.shape
# rotate the entire image
imgRotated = cv2.warpAffine(img, rotationMatrix, (width, height))
# crop the image/plate detected
imgCropped = cv2.getRectSubPix(imgRotated, (plateWidth, plateHeight), tuple(plateCenter))
# copy the cropped plate image into the applicable member variable of the possible plate
possiblePlate.Plate = imgCropped
# populate plates_list with the detected plate
if possiblePlate.Plate is not None:
plates_list.append(possiblePlate)
# draw a ROI on the original image
for i in range(0, len(plates_list)):
# finds the four vertices of a rotated rect - it is useful to draw the rectangle.
p2fRectPoints = cv2.boxPoints(plates_list[i].rrLocationOfPlateInScene)
# roi rectangle colour
rectColour = (0, 255, 0)
cv2.line(imageContours, tuple(p2fRectPoints[0]), tuple(p2fRectPoints[1]), rectColour, 2)
cv2.line(imageContours, tuple(p2fRectPoints[1]), tuple(p2fRectPoints[2]), rectColour, 2)
cv2.line(imageContours, tuple(p2fRectPoints[2]), tuple(p2fRectPoints[3]), rectColour, 2)
cv2.line(imageContours, tuple(p2fRectPoints[3]), tuple(p2fRectPoints[0]), rectColour, 2)
cv2.line(img, tuple(p2fRectPoints[0]), tuple(p2fRectPoints[1]), rectColour, 2)
cv2.line(img, tuple(p2fRectPoints[1]), tuple(p2fRectPoints[2]), rectColour, 2)
cv2.line(img, tuple(p2fRectPoints[2]), tuple(p2fRectPoints[3]), rectColour, 2)
cv2.line(img, tuple(p2fRectPoints[3]), tuple(p2fRectPoints[0]), rectColour, 2)
cv2.imshow("detectedOriginal", img)
cv2.waitKey(0)
cv2.destroyAllWindows()