# -*- coding: utf-8 -*- import os import math import time import sys import numpy from copy import deepcopy from scipy import misc import gtk image = misc.imread('b.jpg') mini = misc.imresize(image, (39, 200)) BLACK = 0 WHITE = 255 class Rectangle: def __init__(self, topleft_corner, bottomright_corner): self.tlc = topleft_corner self.width = abs(self.tlc[1] - bottomright_corner[1]) self.height = abs(self.tlc[0] - bottomright_corner[0]) def __str__(self): return 'Top left corner: (%s, %s), width: %s, height: %s' %\ (self.tlc[0], self.tlc[1], self.width, self.height) class Line: def __init__(self, start=None, end=None, line_tuple=None): if start and end: self.start = start self.end = end elif line_tuple: self.start = line_tuple[0] self.end = line_tuple[1] else: raise self.position = -1 if (start[0] - end[0]) != 0: self.position = 'Vertical' self.size = abs(self.start[0] - self.end[0]) self.pixels = [(a, self.start[1]) for a in range(self.size)] else: self.position = 'Horizontal' self.size = abs(self.start[1] - self.end[1]) self.pixels = [(self.start[0], a) for a in range(self.size)] def __str__(self): return '%s line, starts: %s, ends: %s, lenght: %s' % (self.position, self.start, self.end, self.size) def __len__(self): return self.size def __contains__(self, item): if self.position == 'Vertical': return item[1] in self.pixels[1] def draw_line(image, line): if line.position == 'Vertical': for i in xrange(len(line)): a = line.start[1] b = line.start[0] image[b, (a + 1)] = numpy.uint(127) else: for i in xrange(len(line)): image[line.start[0] + i, line.start[1]] = numpy.uint(127) return image def show_image(list_images): window = gtk.Window(gtk.WINDOW_TOPLEVEL) window.connect('destroy', gtk.main_quit) vbox = gtk.HBox() window.add(vbox) for path_image in list_images: image = gtk.Image() image.set_from_file(path_image) vbox.add(image) window.show_all() gtk.main() def sobel_filter(input_image): copy = input_image.copy() black_and_white = input_image.copy() def apply_filter(sliced_image): x = abs((sliced_image[0][0] + 2 * sliced_image[0][1] +\ sliced_image[0][1]) - (sliced_image[2][0] +\ sliced_image[2][1] * 2 + sliced_image[2][2])) y = abs((sliced_image[0][2] + 2 * sliced_image[1][2] +\ sliced_image[2][2]) - (sliced_image[0][0] +\ sliced_image[1][0] * 2 + sliced_image[2][0])) return int(math.sqrt(x ** 2 + y ** 2)) for i in xrange(copy.shape[0]-1): for j in xrange(copy.shape[1]-1): if 0 < i and 0 < j: value = apply_filter(input_image[i - 1:i + 2, j - 1:j + 2]) copy[i][j] = value if value < 180: black_and_white[i][j] = numpy.uint32(0) else: black_and_white[i][j] = numpy.uint32(255) return (copy[1: -1, 1: -1], black_and_white) def plate_position(im, draw_lines=False): image = im.copy() min_width = int(image.shape[0] / 7) min_height = int(image.shape[1] / 20) wrong_pixels_allowed = 3 def detect_horizontal_lines(image): list_of_rows = [] # height = shape[0] for height in xrange(image.shape[0]): data = {'end_position': None, 'start_position': None, } # width = shape[1] for width in xrange(image.shape[1]): # We are "on" a line if image[height][width] == WHITE: # This is the first pixel of a line if not data['start_position']: data['start_position'] = (height, width) wrong_pixels_allowed = 5 # In this case, a line is not long enough elif (image[height][width] == BLACK) and\ data['start_position'] and\ ((width - data['start_position'][1]) < min_width): if wrong_pixels_allowed > 0: wrong_pixels_allowed -= 1 else: data['start_position'] = None elif data['start_position'] and\ (min_width <= (width - data['start_position'][1])) and\ (image[height][width] == BLACK): if wrong_pixels_allowed > 0: wrong_pixels_allowed -= 1 else: data['end_position'] = (height, width) # In this case, we have a line which is long enough if image[height][width] == BLACK and data['end_position']: # __init__(self, start, end): list_of_rows.append(Line(deepcopy(data['start_position']), deepcopy(data['end_position']))) if draw_lines: for i in xrange(data['start_position'][1], data['end_position'][1]): image[height][i] = numpy.uint32(127) data['start_position'] = None data['end_position'] = None return list_of_rows def detect_vertical_lines(image): list_of_columns = [] # width for width in xrange(image.shape[1]): data = {'end_position': None, 'start_position': None, } # height for height in xrange(image.shape[0]): # We are "on" a line if image[height][width] == WHITE: # This is the first pixel of a line if not data['start_position']: data['start_position'] = (height, width) # In this case, a line is not long enough elif (image[height][width] == BLACK) and\ data['start_position'] and\ ((height - data['start_position'][0]) < min_height): data['start_position'] = None # In this case, a line is long enough, so # XXX Never gets in elif data['start_position'] and\ (min_height <= (height - data['start_position'][0])) and\ (image[height][width] == BLACK): data['end_position'] = (height, width) # In this case, we have a line which is long enough # XXX Never gets in if image[height][width] == BLACK and data['end_position']: if draw_lines: for i in xrange(data['start_position'][0], data['end_position'][0]): image[i][width] = numpy.uint32(127) copy = Line(deepcopy(data['start_position']), deepcopy(data['end_position'])) list_of_columns.append(copy) data['start_position'] = None data['end_position'] = None return list_of_columns def distance_lines(point1, point2, type_of_line): if type_of_line == 'Vertical': distance = abs(point1[1] - point2[1]) else: distance = abs(point1[0] - point2[0]) return distance rows = detect_horizontal_lines(image) columns = detect_vertical_lines(image) list_parallel_rows = [] # We first try to detect parallel horizontal lines, of the same width, and # the same position horizontally # We'll use 1% of the size for the margin of error # Seems to work margin_error = int(image.shape[1] / 100) for i in xrange(len(rows)): for j in xrange(len(rows)): if abs(rows[j].start[1] - rows[i].start[1]) <= margin_error and\ abs(rows[j].end[1] - rows[i].end[1]) <= margin_error: if ((rows[i], rows[j]) not in list_parallel_rows) and\ ((rows[j], rows[i]) not in list_parallel_rows): if distance_lines(rows[i].start, rows[j].start, 'Horizontal') > margin_error: if rows[i].start[0] < rows[j].start[0]: list_parallel_rows.append((rows[i], rows[j])) else: list_parallel_rows.append((rows[j], rows[i])) # Same here vertical lines list_parallel_columns = [] for i in xrange(len(columns)): for j in xrange(i + 1, len(columns)): if abs(columns[i].start[0] - columns[j].start[0]) <=\ (margin_error) and\ abs(columns[i].end[0] - columns[j].end[0]) <= (margin_error): if (columns[i], columns[j]) not in list_parallel_columns and\ (columns[j], columns[i]) not in list_parallel_columns: if distance_lines(columns[i].start, columns[j].start, 'Vertical') > margin_error: if columns[i].start[1] < columns[j].start[1]: list_parallel_columns.append((columns[i], columns[j])) else: list_parallel_columns.append((columns[j], columns[i])) def intersection_lines(line1, line2): vertical_line = line1 if line1.position == 'Vertical' else line2 horizontal_line = line1 if line1.position == 'Horizontal' else line2 full_v_line = Line((0, vertical_line.start[1]), (image.shape[0] - 1, vertical_line.start[1])) full_h_line = Line((vertical_line.start[0], 0), (vertical_line.start[0], image.shape[1] - 1)) for pixel in full_v_line.pixels: if pixel in full_h_line.pixels: return pixel def distance(point1, point2): return math.sqrt((point1[0] - point2[0]) ** 2 +\ (point1[1] - point2[1]) ** 2) def point_on_segment(segment, point): return point in segment.pixels def on_segment_or_close(point, line, min_dist): return (point_on_segment(line, point) <= min_dist) or\ (distance(point, line) <= min_dist) # Now we try to detect rectangles rectangles = [] min_distance = image.shape[1] / 40 for first_row, second_row in list_parallel_rows: for first_col, second_col in list_parallel_columns: intersection1 = intersection_lines(first_row, first_col) intersection2 = intersection_lines(second_row, second_col) # We try to see if the intersection between the lines are close # enough to the end of a segment if on_segment_or_close(intersection1, first_row, min_distance) and\ on_segment_or_close(intersection1, first_col, min_distance): if on_segment_or_close(intersection2, second_row, min_distance) and\ on_segment_or_close(intersection2, second_col, min_distance): rec = Rectangle(first_row.start, second_col.end) rectangles.append(rec) # Since we've detected rectangles, we now have to check for the good # proportions. A plate is 52 cm for the width and 10.5 cm for the height. # So the good proportions is approximately 5. # We'll accept values between 4.8 and 5.2. def is_good(rec): if rec.width / 5.6 < rec.height < rec.width / 4.4: return True else: return False #for rec in rectangles: #draw_rectangle(image, rec) rectangles = [rec for rec in rectangles if is_good(rec)] final_rectangles = [] if len(rectangles) > 0: final_rectangles.append(rectangles[0]) if len(rectangles) != 1: for rec in rectangles: for rec2 in rectangles: if not (rec.tlc[0] - rec2.tlc[0] <= 2) and not\ (rec.tlc[1] - rec2.tlc[1] <= 2): final_rectangles.append(rec) final_rectangles.append(rec2) # Now, we get rid of the duplicates final_rectangles = list(set(final_rectangles)) if len(final_rectangles) == 1: rec = final_rectangles.pop() return rec class Point: def __init__(self, pos, value=None): self.pos = pos self.value = value def __str__(self): return 'Value: %s, position: %s' % (self.value, self.pos) def character_segmentation(image): def reset_values(): current.pos = 0 current.value = c[0] next.pos = 1 next.value = c[1] def next_element(): current.pos += 1 current.value = c[current.pos + 1] next.pos += 1 next.value = c[next.pos + 1] def delete_blank(image, c): c = c[current.pos + 1:] image = image[:, current.pos + 1:] return (image, c) def save_character(image, c): list_characters.append((deepcopy(image[:, 0:current.pos + 1]), current.pos + 1)) return delete_blank(image, c) def extract_character(image, c): # The second digit starts here # We get the highest point while current.value < next.value: next_element() # annd we'll try to go a little further # The width of the previous character will be the limit highest = deepcopy(current) while list_characters[0][1] > current.pos and\ current.value > max(c) / 10: if current.value < next.value: highest = deepcopy(current) next_element() current = highest # Then we try to go down, to find the end while current.value + 270 > next.value: next_element() image, c = save_character(image, c) # dash is the dash on the plate between digiets and letters len_dash = None list_characters = [] vp = vertical_projection(image) hp = horizontal_projection(image) # First of all, we remove the top and the bottom of the car plate using # the horizontal projection for i in xrange(2): index_max = hp.index(max(hp)) if index_max < (image.shape[0] / 2): hp = hp[index_max + 1:] image = image[index_max + 1:][:] else: hp = hp[:index_max + 1] image = image[:index_max + 1][:] # Then, we try to remove the county written in Irish above the numbers # First thing to do is to get rid of the european part of the plate c = [max(vp) - a for a in vp] new = [value for value in c if value < max(c) / 10][0] margin_error = 425 # We'll see if there is a part we can get rid of on the right current = Point(c.index(new), new) next = Point(current.pos + 1) next.value = c[next.pos] while (current.value + margin_error >= next.value): next_element() image, c = delete_blank(image, c) # We know that in an Irish number plate, there between 4 and 10 numbers # AA - BB - CCCCCC where AA stands for the year of registration (2 digits) # BB is used for the county letter(s) (one or two characters) # CCCCCC is the number of the car (from 1 to 6 digits) # First thing to do is getting the two digits corresponding to the year # We get the biggest value from the left reset_values() while current.value < next.value: next_element() # At this stage, the highest value is current # Now, we'll try to know where this character stops while current.value > max(c) / 10: next_element() image, c = save_character(image, c) reset_values() # We now remove the "blank" between the first and the second character while (current.value < max(c) / 10) or\ (current.value + max(c) / 10) >= next.value: next_element() image, c = delete_blank(image, c) reset_values() # The second digit starts here # We get the highest point while current.value < next.value: next_element() # annd we'll try to go a little further # The width of the previous character will be the limit highest = deepcopy(current) while list_characters[0][1] > current.pos and current.value > max(c) / 10: if current.value < next.value: highest = deepcopy(current) next_element() current = highest # Then we try to go down, to find the end while current.value + 270 > next.value: next_element() image, c = save_character(image, c) reset_values() # Now, we try to get remove the dash while current.value < next.value: next_element() # At this stage we should the highest point on it # We'll try do find where the next character begins while (current.value + margin_error >= next.value): next_element() len_dash = current.pos image, c = delete_blank(image, c) # We got rid of the dash at this stage # We now have to figure out if there is one or two characters for the # county part # First thing to do is to get the first character while current.value < next.value: next_element() # annd we'll try to go a little further # The width of the previous character will be the limit highest = deepcopy(current) while list_characters[0][1] > current.pos and current.value > max(c) / 15: if current.value < next.value: highest = deepcopy(current) next_element() current = highest # Then we try to go down, to find the end while current.value + 270 > next.value: next_element() image, c = save_character(image, c) # 4th character # The trickyest one # For this one, we'll make an average of the other characters values average = 0 total = 0 for character, size in list_characters: total += size average = total / len(list_characters) reset_values() image, c = delete_blank(image, c) current = Point(average) current.value = c[current.pos] next = Point(current.pos) next.value = c[next.pos] while current.value < next.value: next_element() # annd we'll try to go a little further while current.value > next.value: next_element() image, c = save_character(image, c) reset_values() # We remove the second dash while current.value < next.value: next_element() # At this stage we should the highest point on it # We'll try do find where the next character begins while (current.value + margin_error >= next.value): next_element() len_dash = current.pos image, c = delete_blank(image, c) for i in xrange(4): reset_values() while current.value < next.value: next_element() # At this stage, the highest value is current # Now, we'll try to know where this character stops while current.value > max(c) / 10: next_element() image, c = save_character(image, c) reset_values() # We now remove the "blank" between the first and the second character while (current.value < max(c) / 10) or\ (current.value + max(c) / 10) >= next.value: next_element() image, c = delete_blank(image, c) reset_values() return list_characters def vertical_projection(image): vp_image = numpy.array([[255 for a in range(image.shape[1])] for b in range(image.shape[0])], dtype=numpy.uint8) image = image.copy() list_sums = [] for width in xrange(image.shape[1]): sum_column = 0 for height in xrange(image.shape[0]): sum_column += image[height][width] list_sums.append(sum_column) max_i = max(list_sums) average = 0 for a in list_sums: average += a copy = [a for a in list_sums] for width in xrange(vp_image.shape[1]): value = list_sums.pop(0) value = int((float(value) / max_i) * image.shape[0]) vp_image[value:, width] = 0 misc.imsave('images/verticalprojection.jpg', vp_image) return copy def horizontal_projection(image): vp_image = numpy.array([[255 for a in range(image.shape[1] / 4)] for b in range(image.shape[0])], dtype=numpy.uint8) image = image.copy() list_sums = [] for height in xrange(image.shape[0]): sum_column = 0 for width in xrange(image.shape[1]): sum_column += image[height][width] list_sums.append(sum_column) copy = [a for a in list_sums] max_i = max(list_sums) for height in xrange(vp_image.shape[0]): value = list_sums.pop(0) value = int((float(value) / max_i) * (image.shape[1]) / 4) vp_image[height, value:] = 0 misc.imsave('images/horizontalprojection.jpg', vp_image) return copy def grayscale(input_image): return numpy.array([[sum(l) / len(l) for l in row] for row in input_image], dtype=numpy.uint32) def score_template(matrix1, matrix2): mask = numpy.array([[0 for a in xrange(matrix1.shape[1])]\ for b in xrange(matrix1.shape[0])]) diff = matrix2 - matrix1 for a in diff: total = sum(a) return total def template_matching(list_characters, bla=True): path = 'characters' directory = os.listdir(path) characters = [a.lstrip()[0] for a in directory] characters_as_images = {char: grayscale(misc.imread('%s/%s.jpg' %\ (path, char))) for char in characters} # First, the year, so it can just be a digit list_first_digits_save = [] list_images = [] plate = '' count = 1 for plate_character, size in list_characters: scores = {c: sys.maxint for c in characters} for i in range(len(characters_as_images)): if 0 < count <= 2 or 4 < count <= 8: for char in xrange(10): character = characters_as_images[str(char)] template = misc.imresize(character, plate_character.shape) score = score_template(template, plate_character) scores[str(char)] = score else: for char in characters: try: int(char) except: template = misc.imresize(characters_as_images[char], plate_character.shape) score = score_template(template, plate_character) scores[char] = score best = None for key, value in scores.iteritems(): if value == min(scores.values()): best = key plate = '%s%s' % (plate, best) count += 1 return plate class Progression: def __init__(self): self.state = -1 self.number_state = 5 self.operation = ('Initial', 'Grayscale', 'Sobel Filter', 'Plate Localization', 'Character Segmentation', 'Template Matching', '') self.directory = 'images' self.output = '' def start_worker_thread(self, path): width = 200 image = misc.imread(path) self.image_type = path.rsplit('.', 1)[1] misc.imsave('%s/initial.%s' % (self.directory, self.image_type,), image) mini = misc.imresize(image, (image.shape[0] * width / image.shape[1], width)) misc.imsave('%s/initial_min.%s' % (self.directory, self.image_type,), mini) image = grayscale(image) image_segmentation = image.copy() misc.imsave('%s/grayscale.%s' % (self.directory, self.image_type,), image) mini = misc.imresize(image, (image.shape[0] * width / image.shape[1], width)) misc.imsave('%s/grayscale_min.%s' % (self.directory, self.image_type,), mini) self.state += 1 image, bwimage = sobel_filter(image) misc.imsave('%s/sobelfilter.%s' % (self.directory, self.image_type,), image) mini = misc.imresize(image, (image.shape[0] * width / image.shape[1], width)) misc.imsave('%s/sobelfilter_min.%s' %\ (self.directory, self.image_type,), mini) self.state += 1 rec = plate_position(bwimage) image = image_segmentation[rec.tlc[0]:rec.tlc[0] + rec.height, rec.tlc[1]:rec.tlc[1] + rec.width] misc.imsave('%s/platelocalization.%s' %\ (self.directory, self.image_type,), image) mini = misc.imresize(image, (image.shape[0] * width / image.shape[1], width)) misc.imsave('%s/platelocalization_min.%s' %\ (self.directory, self.image_type,), mini) if len(image) < len(mini): misc.imsave('%s/platelocalization.%s' %\ (self.directory, self.image_type,), mini) self.state += 1 # Character segmentation list_characters = character_segmentation(mini) i = 0 for character, size in list_characters: misc.imsave('%s/segmentation_%s.jpg' % (self.directory, i), character) i += 1 self.state += 1 # Template matching self.output = template_matching(list_characters) self.state += 1 self.exit_thread() if __name__ == '__main__': aa = time.clock() image_from = 'images/meath.jpg' image_to = 'b.jpg' image = misc.imread('b.jpg') mini = misc.imresize(image, (39, 200)) characters = character_segmentation(mini) template_matching(characters)