|
|
@@ -1,25 +1,109 @@
|
|
|
+from pylab import array, zeros, inv, dot, svd, shape, floor
|
|
|
from xml.dom.minidom import parse
|
|
|
from Error import Error
|
|
|
from Point import Point
|
|
|
from Character import Character
|
|
|
+from GrayscaleImage import GrayscaleImage
|
|
|
|
|
|
+'''
|
|
|
+ Creates a license plate object based on an XML file. The image should be
|
|
|
+ placed in a folder 'images' the xml file in a folder 'xml'
|
|
|
+
|
|
|
+ TODO: perhaps remove non required XML lookups
|
|
|
+'''
|
|
|
class LicensePlate:
|
|
|
|
|
|
- def __init__(self, file_path):
|
|
|
+ def __init__(self, xml_title):
|
|
|
try:
|
|
|
- self.dom = parse(file_path)
|
|
|
+ self.dom = parse('../XML/' + str(xml_title))
|
|
|
except IOError:
|
|
|
Error("Incorrect file name given.")
|
|
|
- return None
|
|
|
else:
|
|
|
properties = self.get_properties()
|
|
|
|
|
|
- self.image = str(properties['uii']) + '.' + str(properties['type'])
|
|
|
- self.width = properties['width']
|
|
|
- self.height = properties['height']
|
|
|
+ self.image = GrayscaleImage('../images/' + str(properties['uii']) + '.' + str(properties['type']))
|
|
|
+ self.width = int(properties['width'])
|
|
|
+ self.height = int(properties['height'])
|
|
|
|
|
|
- info = self.set_plate()
|
|
|
+ self.read_xml()
|
|
|
+
|
|
|
+ # sets the entire license plate of an image
|
|
|
+ def retrieve_data(self, corners):
|
|
|
+ x0, y0 = corners[0].to_tuple()
|
|
|
+ x1, y1 = corners[1].to_tuple()
|
|
|
+ x2, y2 = corners[2].to_tuple()
|
|
|
+ x3, y3 = corners[3].to_tuple()
|
|
|
+
|
|
|
+ M = max(x0, x1, x2, x3) - min(x0, x1, x2, x3)
|
|
|
+ N = max(y0, y1, y2, y3) - min(y0, y1, y2, y3)
|
|
|
+
|
|
|
+ matrix = array([
|
|
|
+ [x0, y0, 1, 0, 0, 0, 0, 0, 0],
|
|
|
+ [ 0, 0, 0, x0, y0, 1, 0, 0, 0],
|
|
|
+ [x1, y1, 1, 0, 0, 0, -M*x0, -M*y1, -M],
|
|
|
+ [ 0, 0, 0, x1, y1, 1, 0, 0, 0],
|
|
|
+ [x2, y2, 1, 0, 0, 0, -M*x2, -M*y2, -M],
|
|
|
+ [ 0, 0, 0, x2, y2, 1, -N*x2, -N*y2, -N],
|
|
|
+ [x3, y3, 1, 0, 0, 0, 0, 0, 0],
|
|
|
+ [ 0, 0, 0, x3, y3, 1, -N*x3, -N*y3, -N]
|
|
|
+ ])
|
|
|
+
|
|
|
+ P = inv(self.get_transformation_matrix(matrix))
|
|
|
+ data = array([zeros(M, float)] * N)
|
|
|
|
|
|
+ for i in range(0, M):
|
|
|
+ for j in range(0, N):
|
|
|
+ or_coor = dot(P, ([[i],[j],[1]]))
|
|
|
+ or_coor_h = or_coor[1][0] / or_coor[2][0], or_coor[0][0] / or_coor[2][0]
|
|
|
+ data[j][i] = self.pV(or_coor_h[0], or_coor_h[1])
|
|
|
+
|
|
|
+ return data
|
|
|
+
|
|
|
+ def get_transformation_matrix(self, matrix):
|
|
|
+ # Get the vector p and the values that are in there by taking the SVD.
|
|
|
+ # Since D is diagonal with the eigenvalues sorted from large to small on
|
|
|
+ # the diagonal, the optimal q in min ||Dq|| is q = [[0]..[1]]. Therefore,
|
|
|
+ # p = Vq means p is the last column in V.
|
|
|
+ U, D, V = svd(matrix)
|
|
|
+ p = V[8][:]
|
|
|
+
|
|
|
+ return array([
|
|
|
+ [ p[0], p[1], p[2] ],
|
|
|
+ [ p[3], p[4], p[5] ],
|
|
|
+ [ p[6], p[7], p[8] ]
|
|
|
+ ])
|
|
|
+
|
|
|
+ def pV(self, x, y):
|
|
|
+ image = self.image
|
|
|
+
|
|
|
+ '''Get the value of a point x,y in the given image, where x and y are not
|
|
|
+ necessary integers, so the value is interpolated from its neighbouring
|
|
|
+ pixels.'''
|
|
|
+ if image.in_bounds(x, y):
|
|
|
+ x_low = floor(x)
|
|
|
+ x_high = floor(x + 1)
|
|
|
+ y_low = floor(y)
|
|
|
+ y_high = floor(y + 1)
|
|
|
+ x_y = (x_high - x_low) * (y_high - y_low)
|
|
|
+
|
|
|
+ a = x_high - x
|
|
|
+ b = y_high - y
|
|
|
+ c = x - x_low
|
|
|
+ d = y - y_low
|
|
|
+
|
|
|
+ return image[x_low, y_low] / x_y * a * b \
|
|
|
+ + image[x_high, y_low] / x_y * c * b \
|
|
|
+ + image[x_low , y_high] / x_y * a * d \
|
|
|
+ + image[x_high, y_high] / x_y * c * d
|
|
|
+
|
|
|
+ return 0
|
|
|
+
|
|
|
+ # Testing purposes
|
|
|
+ def show(self):
|
|
|
+ from pylab import imshow, show
|
|
|
+ imshow(self.data, cmap="gray")
|
|
|
+ show()
|
|
|
+
|
|
|
def get_properties(self):
|
|
|
children = self.get_children("properties")
|
|
|
|
|
|
@@ -34,7 +118,7 @@ class LicensePlate:
|
|
|
return properties
|
|
|
|
|
|
# TODO : create function for location / characters as they do the same
|
|
|
- def set_plate(self):
|
|
|
+ def read_xml(self):
|
|
|
children = self.get_children("plate") # most recent version
|
|
|
|
|
|
for child in children:
|
|
|
@@ -43,26 +127,22 @@ class LicensePlate:
|
|
|
elif child.nodeName == "identification-letters":
|
|
|
self.country = child.firstChild.data
|
|
|
elif child.nodeName == "location":
|
|
|
- corners = self.get_children("quadrangle", child)
|
|
|
-
|
|
|
- self.corners = []
|
|
|
-
|
|
|
- for corner in corners:
|
|
|
- if corner.nodeName == "point":
|
|
|
- self.corners.append(Point(corner))
|
|
|
-
|
|
|
+ self.corners = self.get_corners(child)
|
|
|
elif child.nodeName == "characters":
|
|
|
- characters = child.childNodes
|
|
|
+ nodes = child.childNodes
|
|
|
|
|
|
- self.license_characters = []
|
|
|
+ self.characters = []
|
|
|
|
|
|
- for character in characters:
|
|
|
+ for character in nodes:
|
|
|
if character.nodeName == "character":
|
|
|
- self.license_characters.append(Character(character))
|
|
|
-
|
|
|
+ value = self.get_node("char", character).firstChild.data
|
|
|
+ corners = self.get_corners(character)
|
|
|
+ data = self.retrieve_data(corners)
|
|
|
+ image = NormalizedCharacterImage(data=data)
|
|
|
+
|
|
|
+ self.characters.append(Character(value, corners, image))
|
|
|
else:
|
|
|
pass
|
|
|
- #print child.nodeName
|
|
|
|
|
|
def get_node(self, node, dom=None):
|
|
|
if not dom:
|
|
|
@@ -71,4 +151,15 @@ class LicensePlate:
|
|
|
return dom.getElementsByTagName(node)[0]
|
|
|
|
|
|
def get_children(self, node, dom=None):
|
|
|
- return self.get_node(node, dom).childNodes
|
|
|
+ return self.get_node(node, dom).childNodes
|
|
|
+
|
|
|
+ def get_corners(self, child):
|
|
|
+ nodes = self.get_children("quadrangle", child)
|
|
|
+
|
|
|
+ corners = []
|
|
|
+
|
|
|
+ for corner in nodes:
|
|
|
+ if corner.nodeName == "point":
|
|
|
+ corners.append(Point(corner))
|
|
|
+
|
|
|
+ return corners
|