├── README.md └── pythonEdition ├── Main.py ├── Modules ├── Basic.py ├── Basic.pyc ├── __init__.py ├── __init__.pyc ├── cnnModels.py └── cnnModels.pyc └── cnnModels ├── Characters ├── characters-0.001-4conv-basic.model.data-00000-of-00001 ├── characters-0.001-4conv-basic.model.index └── characters-0.001-4conv-basic.model.meta └── Plates ├── plates-0.001-6conv-basic.model.data-00000-of-00001 ├── plates-0.001-6conv-basic.model.index └── plates-0.001-6conv-basic.model.meta /README.md: -------------------------------------------------------------------------------- 1 | # Radar System 2 | 3 | ### This application was created in order to use the city surveillance cameras to identify cars that are wanted by law enforcement. The system detects vehicle license plates in real time using computer vision and artificial neural networks. 4 | 5 | ## System requirements 6 | - **Python v.2.7.0 of higher** 7 | - **OpenCV library v.3.3.0 or higher** 8 | - **Tensorflow library** 9 | - **tflearn library** 10 | - **Numpy** 11 | *** 12 | 13 | ## Package installing: 14 | ```sh 15 | $ pip install --upgrade pip 16 | $ pip install tensorflow tensorflow-tensorboard tflearn numpy 17 | ``` 18 | **Open Computer Vision (OpenCV) installing link:** 19 | https://docs.opencv.org/trunk/d2/de6/tutorial_py_setup_in_ubuntu.html 20 | *** 21 | 22 | ## How to run 23 | *If you use virtual environments (virtualenv) run it first.* **Virtual environments is highly recommended to use in every project!** 24 | *Type '-v' specificator after file name and print path to video file ...* 25 | ```sh 26 | (cv2) $user@user: $ python Main.py -v 'Path to video file/videoFile.mp4' 27 | ``` 28 | *** 29 | -------------------------------------------------------------------------------- /pythonEdition/Main.py: -------------------------------------------------------------------------------- 1 | from Modules.cnnModels import * 2 | from Modules.Basic import * 3 | 4 | 5 | #============================================================================== 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("-v", "--video", required = True, help = "path to the video file") 8 | args = vars(parser.parse_args()) 9 | #============================================================================== 10 | #============================================================================== 11 | cap = cv2.VideoCapture(args['video']) 12 | while (cap.isOpened()): 13 | plateRet, plateFrame = cap.read() 14 | 15 | #========================================================================================= 16 | #plate_Original, plate_morphEx, edge = preprocessOne(plateFrame, (42,10), True) 17 | plate_Original, plate_morphEx, edge = preprocessOne(plateFrame, (34,8), False) 18 | 19 | _,plateCountours,_ = cv2.findContours(plate_morphEx, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 20 | for plateCountour in plateCountours: 21 | aspect_ratio_range, area_range = (2.2, 12), (500, 18000) 22 | if validate_contour(plateCountour, plate_morphEx, aspect_ratio_range, area_range): 23 | rect = cv2.minAreaRect(plateCountour) 24 | box = cv2.boxPoints(rect) 25 | box = np.int0(box) 26 | 27 | cv2.drawContours(plate_Original, [box], 0, (0,255,0), 1) #change position after CNN 28 | Xs, Ys = [i[0] for i in box], [i[1] for i in box] 29 | x1, y1 = min(Xs), min(Ys) 30 | x2, y2 = max(Xs), max(Ys) 31 | 32 | angle = rect[2] 33 | if angle < -45: angle += 90 34 | 35 | W, H = rect[1][0], rect[1][1] 36 | aspect_ratio = float(W)/H if W > H else float(H)/W 37 | 38 | center = ((x1+x2)/2, (y1+y2)/2) 39 | size = (x2-x1, y2-y1) 40 | M = cv2.getRotationMatrix2D((size[0]/2, size[1]/2), angle, 1.0) 41 | tmp = cv2.getRectSubPix(edge, size, center) 42 | TmpW = H if H > W else W 43 | TmpH = H if H < W else W 44 | tmp = cv2.getRectSubPix(tmp, (int(TmpW),int(TmpH)), (size[0]/2, size[1]/2)) 45 | __,tmp = cv2.threshold(tmp,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU) 46 | 47 | white_pixels = 0 48 | for x in range(tmp.shape[0]): 49 | for y in range(tmp.shape[1]): 50 | if tmp[x][y] == 255: 51 | white_pixels += 1 52 | 53 | edge_density = float(white_pixels)/(tmp.shape[0]*tmp.shape[1]) 54 | 55 | tmp = cv2.getRectSubPix(plateFrame, size, center) 56 | tmp = cv2.warpAffine(tmp, M, size) 57 | TmpW = H if H > W else W 58 | TmpH = H if H < W else W 59 | tmp = cv2.getRectSubPix(tmp, (int(TmpW),int(TmpH)), (size[0]/2, size[1]/2)) 60 | cv2.imshow("Tmp", plate_Original) 61 | 62 | #-------------------------------------------------------- 63 | tmp = reshape(tmp, PLATE_IMG_SIZE, "plateBuffer.jpg") 64 | #-------------------------------------------------------- 65 | data = tmp.reshape(PLATE_IMG_SIZE, PLATE_IMG_SIZE, 1) 66 | plate_model_out = plate_model.predict([data])[0] 67 | if not np.argmax(plate_model_out) == 1: 68 | cv2.drawContours(plate_Original, [box], 0, (0,0,255), 2) #change position after CNN 69 | charOrigin = copy.copy(tmp) 70 | charGaussian = cv2.GaussianBlur(tmp, (3,3), 0) 71 | charThresh = cv2.adaptiveThreshold(charGaussian, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 15, 1) 72 | #======================== 73 | x, y, w, h = 0, 0, 0, 0 74 | charsBuffer = [] 75 | #======================== 76 | _,charContours,_ = cv2.findContours(charThresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) 77 | for charContour in charContours: 78 | area = cv2.contourArea(charContour) 79 | if area > 200 and area < 800: [x,y,w,h] = cv2.boundingRect(charContour) 80 | if h > 25 and h < 75 and w > 10 and w < 45: 81 | if not len(charOrigin[y:y+h, x:x+w]) < 10: 82 | Buffer = copy.copy(tmp[y:y+h, x:x+w]) 83 | cv2.rectangle(charOrigin, (x,y), (x+w,y+h), (255,0,0), 1) 84 | charsBuffer.append([x, Buffer]) 85 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 86 | charsBuffer = sorted(charsBuffer, key= lambda x: x[0]) 87 | TrueChars = [] 88 | 89 | for i in range(len(charsBuffer)): 90 | if i == 0: TrueChars.append(charsBuffer[i]) 91 | elif charsBuffer[i][0] != charsBuffer[i-1][0]: TrueChars.append(charsBuffer[i]) 92 | 93 | del (charsBuffer) 94 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 95 | if len(TrueChars) >= 7 and len(TrueChars) <= 8: 96 | cv2.imshow("[Plate] charContours", charOrigin) 97 | #print("----------plate----------") 98 | string_buffer = [] 99 | for i in range(len(TrueChars)): 100 | Buffer = TrueChars[i][1] 101 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 102 | Buffer = reshape(Buffer, CHARS_IMG_SIZE, "charBuffer.jpg") 103 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 104 | data1 = Buffer.reshape(CHARS_IMG_SIZE, CHARS_IMG_SIZE, 1) 105 | chars_model_out = chars_model.predict([data1])[0] 106 | if not np.argmax(chars_model_out) == 36: 107 | string_buffer.append(CodeToChar(chars_model_out)) 108 | pass 109 | pass 110 | if len(string_buffer) >= 7 and len(string_buffer) <= 8: 111 | t = strftime("%Y-%m-%d %H:%M:%S", gmtime()) 112 | print("[{x}] ===> [{y}]".format(x=t, y=string_buffer)) 113 | pass 114 | pass 115 | 116 | # 117 | # 118 | if cv2.waitKey(1) & 0xFF == ord('q'): 119 | break 120 | #========================================================================================= 121 | 122 | cap.release() 123 | cv2.destroyAllWindows() 124 | 125 | os.system("rm charBuffer.jpg") 126 | os.system("rm plateBuffer.jpg") 127 | -------------------------------------------------------------------------------- /pythonEdition/Modules/Basic.py: -------------------------------------------------------------------------------- 1 | from time import gmtime, strftime 2 | import numpy as np 3 | import argparse 4 | import math 5 | import copy 6 | import cv2 7 | import os 8 | 9 | def preprocessOne(plateFrame, se_shape, Show = False): 10 | plateOrigin = copy.copy(plateFrame) 11 | plateGray = enhance(cv2.cvtColor(plateFrame, cv2.COLOR_BGR2GRAY)) 12 | plateGaussian = cv2.GaussianBlur(plateGray, (5,5), 0) 13 | plateSobel = cv2.Sobel(plateGaussian, -1, 1, 0) 14 | h, plateThresh = cv2.threshold(plateSobel, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU) 15 | 16 | se = cv2.getStructuringElement(cv2.MORPH_RECT, se_shape) 17 | plateMorphEx = cv2.morphologyEx(plateThresh, cv2.MORPH_CLOSE, se) 18 | edge = np.copy(plateThresh) 19 | 20 | if Show == True: 21 | cv2.imshow("1. Original frame", plateOrigin) 22 | cv2.imshow("2. Gray frame", plateGray) 23 | cv2.imshow("3. GaussianBlur frame", plateGaussian) 24 | cv2.imshow("4. Sobel frame", plateSobel) 25 | cv2.imshow("5. Threshold frame", plateThresh) 26 | cv2.imshow("6. morphologyEx frame", plateMorphEx) 27 | pass 28 | return plateOrigin, plateMorphEx, edge 29 | 30 | def reshape(Image, Size, Spec = "imgBuffer.jpg"): 31 | old_size = Image.shape[:2] 32 | ratio = float(Size) / max(old_size) 33 | new_size = tuple([int(x*ratio) for x in old_size]) 34 | Image = cv2.resize(Image, (new_size[1], new_size[0])) 35 | 36 | delta_w = Size - new_size[1] 37 | delta_h = Size - new_size[0] 38 | top, bottom = delta_h//2, delta_h-(delta_h//2) 39 | left, right = delta_w//2, delta_w-(delta_w//2) 40 | 41 | color = [0, 0, 0] 42 | Image = cv2.copyMakeBorder(Image, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) 43 | cv2.imwrite(str(Spec), Image) 44 | Image = cv2.imread(str(Spec), 0) 45 | return Image 46 | 47 | def CodeToChar(model_out): 48 | if np.argmax(model_out) == 0: return "A" 49 | elif np.argmax(model_out) == 1: return "B" 50 | elif np.argmax(model_out) == 2: return "C" 51 | elif np.argmax(model_out) == 3: return "D" 52 | elif np.argmax(model_out) == 4: return "E" 53 | elif np.argmax(model_out) == 5: return "F" 54 | elif np.argmax(model_out) == 6: return "G" 55 | elif np.argmax(model_out) == 7: return "H" 56 | elif np.argmax(model_out) == 8: return "I" 57 | elif np.argmax(model_out) == 9: return "G" 58 | elif np.argmax(model_out) == 10: return "K" 59 | elif np.argmax(model_out) == 11: return "L" 60 | elif np.argmax(model_out) == 12: return "M" 61 | elif np.argmax(model_out) == 13: return "N" 62 | elif np.argmax(model_out) == 14: return "O" 63 | elif np.argmax(model_out) == 15: return "P" 64 | elif np.argmax(model_out) == 16: return "Q" 65 | elif np.argmax(model_out) == 17: return "R" 66 | elif np.argmax(model_out) == 18: return "S" 67 | elif np.argmax(model_out) == 19: return "T" 68 | elif np.argmax(model_out) == 20: return "U" 69 | elif np.argmax(model_out) == 21: return "V" 70 | elif np.argmax(model_out) == 22: return "W" 71 | elif np.argmax(model_out) == 23: return "X" 72 | elif np.argmax(model_out) == 24: return "Y" 73 | elif np.argmax(model_out) == 25: return "Z" 74 | 75 | elif np.argmax(model_out) == 26: return "1" 76 | elif np.argmax(model_out) == 27: return "2" 77 | elif np.argmax(model_out) == 28: return "3" 78 | elif np.argmax(model_out) == 29: return "4" 79 | elif np.argmax(model_out) == 30: return "5" 80 | elif np.argmax(model_out) == 31: return "6" 81 | elif np.argmax(model_out) == 32: return "7" 82 | elif np.argmax(model_out) == 33: return "8" 83 | elif np.argmax(model_out) == 34: return "9" 84 | elif np.argmax(model_out) == 35: return "0" 85 | 86 | elif np.argmax(model_out) == 36: return "None" 87 | pass 88 | 89 | def validate_contour(contour, img, aspect_ratio_range, area_range): 90 | rect = cv2.minAreaRect(contour) 91 | img_width, img_height = img.shape[1], img.shape[0] 92 | box = cv2.boxPoints(rect); box = np.int0(box) 93 | 94 | width, height = rect[1][0], rect[1][1] 95 | X, Y = rect[0][0], rect[0][1] 96 | angle = rect[2] 97 | 98 | angle = (angle + 180) if width < height else (angle + 90) 99 | output=False 100 | 101 | if (width > 0 and height > 0) and ((width < img_width/2.0) and (height < img_width/2.0)): 102 | aspect_ratio = float(width)/height if width > height else float(height)/width 103 | if (aspect_ratio >= aspect_ratio_range[0] and aspect_ratio <= aspect_ratio_range[1]): 104 | if((height*width > area_range[0]) and (height*width < area_range[1])): 105 | 106 | box_copy = list(box) 107 | point = box_copy[0] 108 | del(box_copy[0]) 109 | dists = [((p[0]-point[0])**2 + (p[1]-point[1])**2) for p in box_copy] 110 | sorted_dists = sorted(dists) 111 | opposite_point = box_copy[dists.index(sorted_dists[1])] 112 | tmp_angle = 90 113 | 114 | if abs(point[0]-opposite_point[0]) > 0: 115 | tmp_angle = abs(float(point[1]-opposite_point[1]))/abs(point[0]-opposite_point[0]) 116 | tmp_angle = rad_to_deg(math.atan(tmp_angle)) 117 | 118 | if tmp_angle <= 45: output = True 119 | return output 120 | 121 | def deg_to_rad(angle): return angle*np.pi/180.0 122 | def rad_to_deg(angle): return angle*180/np.pi 123 | def enhance(img): 124 | kernel = np.array([[-1,0,1],[-2,0,2],[1,0,1]]) 125 | return cv2.filter2D(img, -1, kernel) 126 | -------------------------------------------------------------------------------- /pythonEdition/Modules/Basic.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/Modules/Basic.pyc -------------------------------------------------------------------------------- /pythonEdition/Modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/Modules/__init__.py -------------------------------------------------------------------------------- /pythonEdition/Modules/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/Modules/__init__.pyc -------------------------------------------------------------------------------- /pythonEdition/Modules/cnnModels.py: -------------------------------------------------------------------------------- 1 | from tflearn.layers.core import input_data, dropout, fully_connected 2 | from tflearn.layers.conv import conv_2d, max_pool_2d 3 | from tflearn.layers.estimator import regression 4 | import tensorflow as tf 5 | import tflearn 6 | 7 | PLATE_MODEL_NAME = 'cnnModels/Plates/plates-0.001-6conv-basic.model' 8 | CHARS_MODEL_NAME = 'cnnModels/Characters/characters-0.001-4conv-basic.model' 9 | 10 | PLATE_IMG_SIZE, CHARS_IMG_SIZE = 200, 50 11 | LR = 1e-3 #0.001 12 | 13 | #~~~Plate model~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 14 | tf.reset_default_graph() 15 | convnet = input_data(shape=[None, PLATE_IMG_SIZE, PLATE_IMG_SIZE, 1], name='input') 16 | 17 | convnet = conv_2d(convnet, 32, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 18 | convnet = conv_2d(convnet, 64, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 19 | convnet = conv_2d(convnet, 128, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 20 | convnet = conv_2d(convnet, 64, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 21 | convnet = conv_2d(convnet, 32, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 22 | 23 | convnet = fully_connected(convnet, 1024, activation='relu'); convnet = dropout(convnet, 0.8) 24 | 25 | convnet = fully_connected(convnet, 2, activation='softmax') 26 | convnet = regression(convnet, optimizer='adam', learning_rate=LR, loss='categorical_crossentropy', name='targets') 27 | 28 | plate_model = tflearn.DNN(convnet, tensorboard_dir='log') 29 | plate_model.load(PLATE_MODEL_NAME) 30 | #~~~Plate model~~~~END~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 31 | 32 | #~~~Characters model~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | tf.reset_default_graph() 34 | convnet = input_data(shape=[None, CHARS_IMG_SIZE, CHARS_IMG_SIZE, 1], name='input') 35 | 36 | convnet = conv_2d(convnet, 32, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 37 | convnet = conv_2d(convnet, 64, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 38 | convnet = conv_2d(convnet, 32, 5, activation='relu'); convnet = max_pool_2d(convnet, 5) 39 | 40 | convnet = fully_connected(convnet, 1024, activation='relu'); convnet = dropout(convnet, 0.8) 41 | 42 | convnet = fully_connected(convnet, 37, activation='softmax') 43 | convnet = regression(convnet, optimizer='adam', learning_rate=LR, loss='categorical_crossentropy', name='targets') 44 | 45 | chars_model = tflearn.DNN(convnet, tensorboard_dir='log') 46 | chars_model.load(CHARS_MODEL_NAME) 47 | #~~~Characters model~~~~END~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 48 | -------------------------------------------------------------------------------- /pythonEdition/Modules/cnnModels.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/Modules/cnnModels.pyc -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.data-00000-of-00001 -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.index -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Characters/characters-0.001-4conv-basic.model.meta -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.data-00000-of-00001 -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.index -------------------------------------------------------------------------------- /pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vladbidyuk/Radar/fa45d1d59749557677abc2c5c845ce6dd996d9f7/pythonEdition/cnnModels/Plates/plates-0.001-6conv-basic.model.meta --------------------------------------------------------------------------------