├── Memoria.docx ├── Memoria.pdf ├── README.md ├── src ├── car.py ├── coches.xml ├── main.py ├── matriculas.xml ├── neural-networks-and-deep-learning.zip ├── neural.py ├── resources │ ├── coches.xml │ ├── matriculas.xml │ └── neural_net ├── run.bat ├── trained_nets │ ├── general_net_V1 │ ├── general_net_V2 │ ├── general_net_V3 │ ├── general_net_V4 │ ├── trained_neural_net │ ├── trained_neural_net_V1 │ ├── trained_neural_net_V2 │ ├── trained_neural_net_V3 │ ├── trained_neural_net_V3_bck │ ├── trained_neural_net_V3_bck2 │ ├── trained_neural_net_V4 │ ├── trained_neural_net_V4_bck │ ├── trained_neural_net_V5 │ ├── trained_neural_net_V6 │ └── trained_neural_net_V7 └── utils.py ├── testing_full_system └── testing_full_system.zip └── training_ocr ├── testing_ocr.zip └── training_ocr.zip /Memoria.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabriel-cf/neuralPlateRecognition/0a6fc7619c4139f39b0210aee4f059b98acdb638/Memoria.docx -------------------------------------------------------------------------------- /Memoria.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabriel-cf/neuralPlateRecognition/0a6fc7619c4139f39b0210aee4f059b98acdb638/Memoria.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # neuralPlateRecognition 2 | This project is developed in Python using OpenCV and the free deep learning library `neural-networks-and-deep-learning`. It is capable of recognizing cars and reading the plate number. 3 | It recognizes 37 different values: all characters from `A-Z`, digits from `0-9` and the european symbol of Spain on the plate. 4 | 5 | Example: 6 | 7 | ![Plate recognition](https://i.imgur.com/lt24uc6.png) 8 | 9 | ## SET UP: 10 | Decompress the files in `testing_full_system/` and `training_ocr/` and the neural network library located in `src/neural-networks-and-deep-learning.zip` 11 | 12 | ## GENERAL DESCRIPTION: 13 | You can test the system executing main.py (See USAGE). The default neural net used is `general_v2` which is the one that gave the best results. 14 | If you want to train a new neural network, you only have to execute neural.py and follow the steps. 15 | You can train it for a few epochs and then change parameters and keep training. 16 | You can even load an existing net and train keep training it if you wish. 17 | 18 | The network `general_v2` was trained with 100 input neurons, 100 first and 100 second level neurons and 37 output neurons. 19 | Used parameters: 20 | ``` 21 | Epochs: 100 22 | Batch-Size: 10 23 | Learning rate: 0.1 24 | Lambda: 5.0 25 | ``` 26 | 27 | ## USAGE: 28 | From `src/`: 29 | * Test a real-world scenario: 30 | `python main.py "../testing_full_system/testing_full_system"` 31 | * Train a new neural network (new or existing): 32 | `python neural.py "../training_ocr/training_ocr/" "../training_ocr/testing_ocr/"` 33 | -------------------------------------------------------------------------------- /src/car.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import print_function 3 | import cv2 4 | import numpy as np 5 | from utils import * 6 | import copy 7 | 8 | class Car(object): 9 | """docstring for Car""" #666 10 | def __init__(self, img, carR, plateCassifier): 11 | self.plateCassifier = plateCassifier 12 | self.img = img #Original image 13 | self.carR = carR #Rectangle delimiting the car on the Image 14 | self.carImg = img[carR.y:carR.y+carR.h, carR.x:carR.x+carR.w] 15 | self.plateImg, self.plateR, self.rs = self.getBestPlate() 16 | self.plateText = '' 17 | 18 | def setPlateText(self, s): 19 | self.plateText = s 20 | 21 | def isPlateEmpty(self): 22 | return self.plateText == '' 23 | 24 | def draw(self): 25 | if(self.carR is not None): 26 | cv2.rectangle(self.img,(self.carR.x,self.carR.y),(self.carR.x+self.carR.w,self.carR.y+self.carR.h),(0,0,255),2) #Print the car rectangle 27 | 28 | if(self.plateR is not None): 29 | cv2.rectangle(self.carImg,(self.plateR.x,self.plateR.y),(self.plateR.x+self.plateR.w,self.plateR.y+self.plateR.h),(255,0,0),2) 30 | for r in self.rs: 31 | cv2.rectangle(self.plateImg,(r.x,r.y),(r.x+r.w,r.y+r.h),(255,0,0),1) 32 | 33 | def getBestPlate(self): 34 | """ Receives the car cropped image 35 | Returns the best plate and a list of rectangles containing the characters 36 | """ 37 | plates = self.plateCassifier.detectMultiScale(self.carImg, 1.3, 2) #Returns an array of rectangles (x,y,w,h) delimiting the plates 38 | l_plates = convertTupleListToRectangleList(plates) 39 | 40 | max_rs = [] 41 | max_plateImg = None 42 | max_plate = None 43 | for plate in l_plates: 44 | plateImg = self.carImg[plate.y:plate.y+plate.h, plate.x:plate.x+plate.w] 45 | plateImgFilter = copy.copy(plateImg) 46 | plateImgFilter = cv2.GaussianBlur(plateImgFilter,(5,5),0) # Helps to detect only large borders 47 | thres = cv2.adaptiveThreshold(plateImgFilter,255,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2) 48 | contours, _ = cv2.findContours(thres,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) 49 | rs = [] 50 | for cnt in contours: 51 | x,y,w,h = cv2.boundingRect(cnt) 52 | r = Rectangle(x,y,w,h) 53 | rs.append(r) 54 | 55 | rs = self.filterBorderRectangles(rs, plate.h) 56 | if(len(rs) > len(max_rs)): 57 | max_rs = rs 58 | max_plateImg = plateImg 59 | max_plate = plate 60 | 61 | return max_plateImg, max_plate, max_rs 62 | 63 | def filterBorderRectangles(self, chunk, h, alfa = 1): 64 | """ Receives a list of Rectangle objects surrounding borders 65 | Alfa parameter can be specified to adjust the strictness of the filter 66 | Returns a filtered list of the eight or less most similar Rectangle objects 67 | """ 68 | def pythagoras(r): 69 | return np.sqrt(r.h*r.h + r.w*r.w) 70 | def inRange(y, r): 71 | """ Determines wether a rectangle is crossed by a horizontal line 'y' or not 72 | """ 73 | ini = r.y 74 | fini = r.y + r.h 75 | return (y > ini and y <= ini + fini) 76 | def getRatio(rs): 77 | return rs.h / rs.w 78 | def diffRatio(r1, r2): 79 | return np.abs(getRatio(r1) - getRatio(r2))/getRatio(r1) 80 | def diffHypotenuse(r1, r2): 81 | py1 = pythagoras(r1) 82 | py2 = pythagoras(r2) 83 | return np.abs(py1 - py2)/py1 84 | def chunkByCriteria(rs, limit, func): 85 | """ 86 | Divides rs in chunks of rectangles based on the criteria of the function func and a given limit 87 | rs --> original list of rectangles 88 | limit --> % of difference for the values of two consecutive rectangles 89 | func --> name of the function to be used for calculating the difference %. Must need 2 rectangles (r1, r2) 90 | """ 91 | chunk = [] 92 | best_chunk = [] 93 | for i in xrange(0,len(rs)): 94 | chunk.append(rs[i]) #We append and cut this chunk if the next rectangle is different 95 | if(i < len(rs) - 1): 96 | percentage = func(rs[i], rs[i + 1]) 97 | if percentage > limit: 98 | if (len(best_chunk) < len(chunk)): 99 | best_chunk = chunk 100 | chunk = [] 101 | else: 102 | if (len(best_chunk) < len(chunk)): 103 | best_chunk = chunk 104 | return best_chunk 105 | 106 | MID = h // 2 107 | MIN_H = h // 3 #Minimum height for a rectangle containing a digit 108 | 109 | while len(chunk) > 8: 110 | #ratioLimit = 1.2 * alfa 111 | #hypotenuseLimit = 0.25 #* alfa 112 | ratioLimit = 2.0 * alfa 113 | hypotenuseLimit = 0.5 * alfa 114 | 115 | # I: Filter values that aren't crossed by the middle line or are too small (< MIN_H) 116 | chunk = filter(lambda x: inRange(MID, x) and (x.h > MIN_H), chunk) 117 | 118 | # II: Sort rectangles by ratio and divide in chunks of similar ratios 119 | chunk.sort(key=lambda x: getRatio(x)) 120 | chunk = chunkByCriteria(chunk, ratioLimit, diffRatio) 121 | 122 | # III: Sort chunks by hypotenuse (size) and filter 123 | chunk.sort(key=lambda x: pythagoras(x)) 124 | chunk = chunkByCriteria(chunk, hypotenuseLimit, diffHypotenuse) 125 | 126 | alfa = alfa - 0.05 127 | 128 | chunk.sort(key=lambda r: r.x) 129 | return chunk -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | from __future__ import division 2 | from __future__ import print_function 3 | import sys 4 | sys.path.append('neural-networks-and-deep-learning/src') 5 | from sys import argv, stderr 6 | from os import listdir 7 | from os.path import isdir, isfile, join 8 | import cv2 9 | import numpy as np 10 | import network2 11 | import copy 12 | from matplotlib import pyplot as plt 13 | from utils import * 14 | from car import Car 15 | 16 | 17 | ###################################### 18 | # FUNCTIONS # 19 | ###################################### 20 | 21 | 22 | def getCarsFromImage(img, carClassifier): 23 | """ Receives the original image 24 | Returns a list of Rectangle objects containing the detected cars 25 | """ 26 | cars = carClassifier.detectMultiScale(img, 1.3, 2) 27 | return convertTupleListToRectangleList(cars) 28 | 29 | def processImageForNeuralNet(arg1, image=False): 30 | """ 31 | Receives as parameter arg1 the path of the image to be converted or the image already captured with 32 | cv2 (in that case, pass image=True as a parameter). The return of this function (x) should be passed as 33 | input to a Network object by network.feedforward(x) 34 | """ 35 | SIDE_SIZE = 10 36 | TOTAL_SIZE = 100 37 | img = arg1 38 | if(not image): 39 | img = cv2.imread(arg1,0) 40 | img = cv2.resize(img,(SIDE_SIZE,SIDE_SIZE)) 41 | img = cv2.adaptiveThreshold(img,1,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY,11,2) 42 | 43 | img = np.reshape(img, (TOTAL_SIZE, 1)) 44 | return np.array(img, dtype='f') 45 | 46 | def translateNeuralOutput(value): 47 | if(value < 10): 48 | return chr(value + 48) 49 | if(value == 36): 50 | return 'ESP' 51 | return chr(value + 55) 52 | 53 | def processPlateText(car, net): 54 | s = "" 55 | if(len(car.rs) > 0): 56 | for r in car.rs: #First, we process the characters 57 | crop_img = car.plateImg[r.y:r.y+r.h, r.x:r.x+r.w] 58 | #showImage(crop_img) 59 | p_img = processImageForNeuralNet(crop_img, image=True) 60 | s = s + "{} ".format(translateNeuralOutput(np.argmax(net.feedforward(p_img)))) 61 | #print(s) 62 | return s 63 | 64 | ###################################### 65 | # MAIN # 66 | ###################################### 67 | 68 | ####### Param specification ####### 69 | USE = "Use: