├── checkpoint ├── license_plate ├── readme.txt ├── Main.pyc ├── DetectChars.pyc ├── Preprocess.pyc ├── DetectPlates.pyc ├── PossibleChar.pyc ├── PossiblePlate.pyc ├── PossiblePlate.py ├── PossibleChar.py ├── Preprocess.py ├── Main.py ├── DetectPlates.py └── DetectChars.py ├── test_images └── image1.jpg ├── Output └── 2018-04-04 (3).png ├── color1.py ├── mscoco_label_map.pbtxt ├── visualization_utils_test.py ├── object_detection1.ipynb └── visualization_utils.py /checkpoint: -------------------------------------------------------------------------------- 1 | model_checkpoint_path: "model.ckpt" 2 | all_model_checkpoint_paths: "model.ckpt" 3 | -------------------------------------------------------------------------------- /license_plate/readme.txt: -------------------------------------------------------------------------------- 1 | The video pretty much explains it all: 2 | https://www.youtube.com/watch?v=fJcl6Gw1D8k 3 | -------------------------------------------------------------------------------- /license_plate/Main.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/Main.pyc -------------------------------------------------------------------------------- /test_images/image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/test_images/image1.jpg -------------------------------------------------------------------------------- /Output/2018-04-04 (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/Output/2018-04-04 (3).png -------------------------------------------------------------------------------- /license_plate/DetectChars.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/DetectChars.pyc -------------------------------------------------------------------------------- /license_plate/Preprocess.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/Preprocess.pyc -------------------------------------------------------------------------------- /license_plate/DetectPlates.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/DetectPlates.pyc -------------------------------------------------------------------------------- /license_plate/PossibleChar.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/PossibleChar.pyc -------------------------------------------------------------------------------- /license_plate/PossiblePlate.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prateek327/Car-detection-with-Color-and-Number-Plate-/HEAD/license_plate/PossiblePlate.pyc -------------------------------------------------------------------------------- /license_plate/PossiblePlate.py: -------------------------------------------------------------------------------- 1 | # PossiblePlate.py 2 | import os 3 | import cv2 4 | import numpy as np 5 | 6 | ################################################################################################### 7 | class PossiblePlate: 8 | 9 | # constructor ################################################################################# 10 | def __init__(self): 11 | self.imgPlate = None 12 | self.imgGrayscale = None 13 | self.imgThresh = None 14 | 15 | self.rrLocationOfPlateInScene = None 16 | 17 | self.strChars = "" 18 | # end constructor 19 | 20 | # end class 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /license_plate/PossibleChar.py: -------------------------------------------------------------------------------- 1 | # PossibleChar.py 2 | import os 3 | import cv2 4 | import numpy as np 5 | import math 6 | 7 | ################################################################################################### 8 | class PossibleChar: 9 | 10 | # constructor ################################################################################# 11 | def __init__(self, _contour): 12 | self.contour = _contour 13 | 14 | self.boundingRect = cv2.boundingRect(self.contour) 15 | 16 | [intX, intY, intWidth, intHeight] = self.boundingRect 17 | 18 | self.intBoundingRectX = intX 19 | self.intBoundingRectY = intY 20 | self.intBoundingRectWidth = intWidth 21 | self.intBoundingRectHeight = intHeight 22 | 23 | self.intBoundingRectArea = self.intBoundingRectWidth * self.intBoundingRectHeight 24 | 25 | self.intCenterX = (self.intBoundingRectX + self.intBoundingRectX + self.intBoundingRectWidth) / 2 26 | self.intCenterY = (self.intBoundingRectY + self.intBoundingRectY + self.intBoundingRectHeight) / 2 27 | 28 | self.fltDiagonalSize = math.sqrt((self.intBoundingRectWidth ** 2) + (self.intBoundingRectHeight ** 2)) 29 | 30 | self.fltAspectRatio = float(self.intBoundingRectWidth) / float(self.intBoundingRectHeight) 31 | # end constructor 32 | 33 | # end class 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /license_plate/Preprocess.py: -------------------------------------------------------------------------------- 1 | # Preprocess.py 2 | 3 | import os 4 | import cv2 5 | import numpy as np 6 | import math 7 | 8 | # module level variables ########################################################################## 9 | GAUSSIAN_SMOOTH_FILTER_SIZE = (5, 5) 10 | ADAPTIVE_THRESH_BLOCK_SIZE = 19 11 | ADAPTIVE_THRESH_WEIGHT = 9 12 | 13 | ################################################################################################### 14 | def preprocess(imgOriginal): 15 | imgGrayscale = extractValue(imgOriginal) 16 | 17 | imgMaxContrastGrayscale = maximizeContrast(imgGrayscale) 18 | 19 | height, width = imgGrayscale.shape 20 | 21 | imgBlurred = np.zeros((height, width, 1), np.uint8) 22 | 23 | imgBlurred = cv2.GaussianBlur(imgMaxContrastGrayscale, GAUSSIAN_SMOOTH_FILTER_SIZE, 0) 24 | 25 | imgThresh = cv2.adaptiveThreshold(imgBlurred, 255.0, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, ADAPTIVE_THRESH_BLOCK_SIZE, ADAPTIVE_THRESH_WEIGHT) 26 | 27 | return imgGrayscale, imgThresh 28 | # end function 29 | 30 | ################################################################################################### 31 | def extractValue(imgOriginal): 32 | height, width, numChannels = imgOriginal.shape 33 | 34 | imgHSV = np.zeros((height, width, 3), np.uint8) 35 | 36 | imgHSV = cv2.cvtColor(imgOriginal, cv2.COLOR_BGR2HSV) 37 | 38 | imgHue, imgSaturation, imgValue = cv2.split(imgHSV) 39 | 40 | return imgValue 41 | # end function 42 | 43 | ################################################################################################### 44 | def maximizeContrast(imgGrayscale): 45 | 46 | height, width = imgGrayscale.shape 47 | 48 | imgTopHat = np.zeros((height, width, 1), np.uint8) 49 | imgBlackHat = np.zeros((height, width, 1), np.uint8) 50 | 51 | structuringElement = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) 52 | 53 | imgTopHat = cv2.morphologyEx(imgGrayscale, cv2.MORPH_TOPHAT, structuringElement) 54 | imgBlackHat = cv2.morphologyEx(imgGrayscale, cv2.MORPH_BLACKHAT, structuringElement) 55 | 56 | imgGrayscalePlusTopHat = cv2.add(imgGrayscale, imgTopHat) 57 | imgGrayscalePlusTopHatMinusBlackHat = cv2.subtract(imgGrayscalePlusTopHat, imgBlackHat) 58 | 59 | return imgGrayscalePlusTopHatMinusBlackHat 60 | # end function 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /color1.py: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env python 3 | import cv2 4 | from colorsys import hsv_to_rgb 5 | from math import sqrt, ceil 6 | import matplotlib.colors 7 | from matplotlib.patches import Rectangle 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | from optparse import OptionParser 11 | import os 12 | from scipy.cluster.vq import kmeans, whiten 13 | import sys 14 | import struct 15 | from PIL import Image 16 | import scipy 17 | import scipy.misc 18 | import scipy.cluster 19 | from sklearn.cluster import KMeans 20 | 21 | 22 | def crop(image_obj, coords, saved_location): 23 | cropped_image = image_obj.crop(coords) 24 | cropped_image.save(saved_location) 25 | 26 | 27 | def find_histogram(clt): 28 | """ 29 | create a histogram with k clusters 30 | :param: clt 31 | :return:hist 32 | """ 33 | numLabels = np.arange(0, len(np.unique(clt.labels_)) + 1) 34 | (hist, _) = np.histogram(clt.labels_, bins=numLabels) 35 | 36 | hist = hist.astype("float") 37 | hist /= hist.sum() 38 | 39 | return hist 40 | """ 41 | def plot_colors(hist, cent): 42 | start = 0 43 | end = 0 44 | myRect = np.zeros((50, 300, 3), dtype="uint8") 45 | tmp = hist[0] 46 | tmpC = cent[0] 47 | for (percent, color) in zip(hist, cent): 48 | if(percent > tmp): 49 | tmp = percent 50 | tmpC = color 51 | end = start + (tmp * 300) # try to fit my rectangle 50*300 shape 52 | cv2.rectangle(myRect, (int(start), 0), (int(end), 50), 53 | tmpC.astype("uint8").tolist(), -1) 54 | start = end 55 | #rest will be black. Convert to black 56 | for (percent,color) in zip(hist, cent): 57 | end = start + (percent * 300) # try to fit my rectangle 50*300 shape 58 | if(percent != tmp): 59 | color = [0, 0, 0] 60 | cv2.rectangle(myRect, (int(start), 0), (int(end), 50), 61 | color, -1) #draw in a rectangle 62 | start = end 63 | return myRect 64 | """ 65 | def plot_colors2(hist, centroids): 66 | bar = np.zeros((50, 300, 3), dtype="uint8") 67 | startX = 0 68 | 69 | for (percent, color) in zip(hist, centroids): 70 | # plot the relative percentage of each cluster 71 | endX = startX + (percent * 300) 72 | cv2.rectangle(bar, (int(startX), 0), (int(endX), 50), 73 | color.astype("uint8").tolist(), -1) 74 | startX = endX 75 | 76 | # return the bar chart 77 | return bar 78 | 79 | 80 | def find_color(image,coords,saved_location): 81 | 82 | crop(image, ( 48.13239723443985,41.31784084439278,751.2476563453674,404.91936445236206 ), saved_location) 83 | img = cv2.imread(saved_location) 84 | img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) 85 | img = img.reshape((img.shape[0] * img.shape[1],3)) #represent as row*column,channel number 86 | clt = KMeans(n_clusters=3) #cluster number 87 | clt.fit(img) 88 | hist = find_histogram(clt) 89 | print(clt.cluster_centers_) 90 | bar = plot_colors2(hist, clt.cluster_centers_) 91 | plt.axis("off") 92 | plt.imshow(bar) 93 | plt.show() 94 | -------------------------------------------------------------------------------- /mscoco_label_map.pbtxt: -------------------------------------------------------------------------------- 1 | item { 2 | name: "/m/01g317" 3 | id: 1 4 | display_name: "person" 5 | } 6 | item { 7 | name: "/m/0199g" 8 | id: 2 9 | display_name: "bicycle" 10 | } 11 | item { 12 | name: "/m/0k4j" 13 | id: 3 14 | display_name: "car" 15 | } 16 | item { 17 | name: "/m/04_sv" 18 | id: 4 19 | display_name: "motorcycle" 20 | } 21 | item { 22 | name: "/m/05czz6l" 23 | id: 5 24 | display_name: "airplane" 25 | } 26 | item { 27 | name: "/m/01bjv" 28 | id: 6 29 | display_name: "bus" 30 | } 31 | item { 32 | name: "/m/07jdr" 33 | id: 7 34 | display_name: "train" 35 | } 36 | item { 37 | name: "/m/07r04" 38 | id: 8 39 | display_name: "truck" 40 | } 41 | item { 42 | name: "/m/019jd" 43 | id: 9 44 | display_name: "boat" 45 | } 46 | item { 47 | name: "/m/015qff" 48 | id: 10 49 | display_name: "traffic light" 50 | } 51 | item { 52 | name: "/m/01pns0" 53 | id: 11 54 | display_name: "fire hydrant" 55 | } 56 | item { 57 | name: "/m/02pv19" 58 | id: 13 59 | display_name: "stop sign" 60 | } 61 | item { 62 | name: "/m/015qbp" 63 | id: 14 64 | display_name: "parking meter" 65 | } 66 | item { 67 | name: "/m/0cvnqh" 68 | id: 15 69 | display_name: "bench" 70 | } 71 | item { 72 | name: "/m/015p6" 73 | id: 16 74 | display_name: "bird" 75 | } 76 | item { 77 | name: "/m/01yrx" 78 | id: 17 79 | display_name: "cat" 80 | } 81 | item { 82 | name: "/m/0bt9lr" 83 | id: 18 84 | display_name: "dog" 85 | } 86 | item { 87 | name: "/m/03k3r" 88 | id: 19 89 | display_name: "horse" 90 | } 91 | item { 92 | name: "/m/07bgp" 93 | id: 20 94 | display_name: "sheep" 95 | } 96 | item { 97 | name: "/m/01xq0k1" 98 | id: 21 99 | display_name: "cow" 100 | } 101 | item { 102 | name: "/m/0bwd_0j" 103 | id: 22 104 | display_name: "elephant" 105 | } 106 | item { 107 | name: "/m/01dws" 108 | id: 23 109 | display_name: "bear" 110 | } 111 | item { 112 | name: "/m/0898b" 113 | id: 24 114 | display_name: "zebra" 115 | } 116 | item { 117 | name: "/m/03bk1" 118 | id: 25 119 | display_name: "giraffe" 120 | } 121 | item { 122 | name: "/m/01940j" 123 | id: 27 124 | display_name: "backpack" 125 | } 126 | item { 127 | name: "/m/0hnnb" 128 | id: 28 129 | display_name: "umbrella" 130 | } 131 | item { 132 | name: "/m/080hkjn" 133 | id: 31 134 | display_name: "handbag" 135 | } 136 | item { 137 | name: "/m/01rkbr" 138 | id: 32 139 | display_name: "tie" 140 | } 141 | item { 142 | name: "/m/01s55n" 143 | id: 33 144 | display_name: "suitcase" 145 | } 146 | item { 147 | name: "/m/02wmf" 148 | id: 34 149 | display_name: "frisbee" 150 | } 151 | item { 152 | name: "/m/071p9" 153 | id: 35 154 | display_name: "skis" 155 | } 156 | item { 157 | name: "/m/06__v" 158 | id: 36 159 | display_name: "snowboard" 160 | } 161 | item { 162 | name: "/m/018xm" 163 | id: 37 164 | display_name: "sports ball" 165 | } 166 | item { 167 | name: "/m/02zt3" 168 | id: 38 169 | display_name: "kite" 170 | } 171 | item { 172 | name: "/m/03g8mr" 173 | id: 39 174 | display_name: "baseball bat" 175 | } 176 | item { 177 | name: "/m/03grzl" 178 | id: 40 179 | display_name: "baseball glove" 180 | } 181 | item { 182 | name: "/m/06_fw" 183 | id: 41 184 | display_name: "skateboard" 185 | } 186 | item { 187 | name: "/m/019w40" 188 | id: 42 189 | display_name: "surfboard" 190 | } 191 | item { 192 | name: "/m/0dv9c" 193 | id: 43 194 | display_name: "tennis racket" 195 | } 196 | item { 197 | name: "/m/04dr76w" 198 | id: 44 199 | display_name: "bottle" 200 | } 201 | item { 202 | name: "/m/09tvcd" 203 | id: 46 204 | display_name: "wine glass" 205 | } 206 | item { 207 | name: "/m/08gqpm" 208 | id: 47 209 | display_name: "cup" 210 | } 211 | item { 212 | name: "/m/0dt3t" 213 | id: 48 214 | display_name: "fork" 215 | } 216 | item { 217 | name: "/m/04ctx" 218 | id: 49 219 | display_name: "knife" 220 | } 221 | item { 222 | name: "/m/0cmx8" 223 | id: 50 224 | display_name: "spoon" 225 | } 226 | item { 227 | name: "/m/04kkgm" 228 | id: 51 229 | display_name: "bowl" 230 | } 231 | item { 232 | name: "/m/09qck" 233 | id: 52 234 | display_name: "banana" 235 | } 236 | item { 237 | name: "/m/014j1m" 238 | id: 53 239 | display_name: "apple" 240 | } 241 | item { 242 | name: "/m/0l515" 243 | id: 54 244 | display_name: "sandwich" 245 | } 246 | item { 247 | name: "/m/0cyhj_" 248 | id: 55 249 | display_name: "orange" 250 | } 251 | item { 252 | name: "/m/0hkxq" 253 | id: 56 254 | display_name: "broccoli" 255 | } 256 | item { 257 | name: "/m/0fj52s" 258 | id: 57 259 | display_name: "carrot" 260 | } 261 | item { 262 | name: "/m/01b9xk" 263 | id: 58 264 | display_name: "hot dog" 265 | } 266 | item { 267 | name: "/m/0663v" 268 | id: 59 269 | display_name: "pizza" 270 | } 271 | item { 272 | name: "/m/0jy4k" 273 | id: 60 274 | display_name: "donut" 275 | } 276 | item { 277 | name: "/m/0fszt" 278 | id: 61 279 | display_name: "cake" 280 | } 281 | item { 282 | name: "/m/01mzpv" 283 | id: 62 284 | display_name: "chair" 285 | } 286 | item { 287 | name: "/m/02crq1" 288 | id: 63 289 | display_name: "couch" 290 | } 291 | item { 292 | name: "/m/03fp41" 293 | id: 64 294 | display_name: "potted plant" 295 | } 296 | item { 297 | name: "/m/03ssj5" 298 | id: 65 299 | display_name: "bed" 300 | } 301 | item { 302 | name: "/m/04bcr3" 303 | id: 67 304 | display_name: "dining table" 305 | } 306 | item { 307 | name: "/m/09g1w" 308 | id: 70 309 | display_name: "toilet" 310 | } 311 | item { 312 | name: "/m/07c52" 313 | id: 72 314 | display_name: "tv" 315 | } 316 | item { 317 | name: "/m/01c648" 318 | id: 73 319 | display_name: "laptop" 320 | } 321 | item { 322 | name: "/m/020lf" 323 | id: 74 324 | display_name: "mouse" 325 | } 326 | item { 327 | name: "/m/0qjjc" 328 | id: 75 329 | display_name: "remote" 330 | } 331 | item { 332 | name: "/m/01m2v" 333 | id: 76 334 | display_name: "keyboard" 335 | } 336 | item { 337 | name: "/m/050k8" 338 | id: 77 339 | display_name: "cell phone" 340 | } 341 | item { 342 | name: "/m/0fx9l" 343 | id: 78 344 | display_name: "microwave" 345 | } 346 | item { 347 | name: "/m/029bxz" 348 | id: 79 349 | display_name: "oven" 350 | } 351 | item { 352 | name: "/m/01k6s3" 353 | id: 80 354 | display_name: "toaster" 355 | } 356 | item { 357 | name: "/m/0130jx" 358 | id: 81 359 | display_name: "sink" 360 | } 361 | item { 362 | name: "/m/040b_t" 363 | id: 82 364 | display_name: "refrigerator" 365 | } 366 | item { 367 | name: "/m/0bt_c3" 368 | id: 84 369 | display_name: "book" 370 | } 371 | item { 372 | name: "/m/01x3z" 373 | id: 85 374 | display_name: "clock" 375 | } 376 | item { 377 | name: "/m/02s195" 378 | id: 86 379 | display_name: "vase" 380 | } 381 | item { 382 | name: "/m/01lsmm" 383 | id: 87 384 | display_name: "scissors" 385 | } 386 | item { 387 | name: "/m/0kmg4" 388 | id: 88 389 | display_name: "teddy bear" 390 | } 391 | item { 392 | name: "/m/03wvsk" 393 | id: 89 394 | display_name: "hair drier" 395 | } 396 | item { 397 | name: "/m/012xff" 398 | id: 90 399 | display_name: "toothbrush" 400 | } 401 | -------------------------------------------------------------------------------- /license_plate/Main.py: -------------------------------------------------------------------------------- 1 | # Main.py 2 | 3 | import cv2 4 | import numpy as np 5 | import os 6 | 7 | import DetectChars 8 | import DetectPlates 9 | import PossiblePlate 10 | 11 | # module level variables ########################################################################## 12 | SCALAR_BLACK = (0.0, 0.0, 0.0) 13 | SCALAR_WHITE = (255.0, 255.0, 255.0) 14 | SCALAR_YELLOW = (0.0, 255.0, 255.0) 15 | SCALAR_GREEN = (0.0, 255.0, 0.0) 16 | SCALAR_RED = (0.0, 0.0, 255.0) 17 | 18 | showSteps = False 19 | 20 | ################################################################################################### 21 | def main(saved_location): 22 | 23 | blnKNNTrainingSuccessful = DetectChars.loadKNNDataAndTrainKNN() # attempt KNN training 24 | 25 | if blnKNNTrainingSuccessful == False: # if KNN training was not successful 26 | print ("\nerror: KNN traning was not successful\n") # show error message 27 | return # and exit program 28 | # end if 29 | 30 | imgOriginalScene = cv2.imread(saved_location) # open image 31 | 32 | if imgOriginalScene is None: # if image was not read successfully 33 | print ("\nerror: image not read from file \n\n" ) # print error message to std out 34 | os.system("pause") # pause so user can see error message 35 | return # and exit program 36 | # end if 37 | 38 | listOfPossiblePlates = DetectPlates.detectPlatesInScene(imgOriginalScene) # detect plates 39 | 40 | listOfPossiblePlates = DetectChars.detectCharsInPlates(listOfPossiblePlates) # detect chars in plates 41 | 42 | cv2.imshow("imgOriginalScene", imgOriginalScene) # show scene image 43 | 44 | if len(listOfPossiblePlates) == 0: # if no plates were found 45 | print ("\nno license plates were detected\n" ) # inform user no plates were found 46 | else: # else 47 | # if we get in here list of possible plates has at leat one plate 48 | 49 | # sort the list of possible plates in DESCENDING order (most number of chars to least number of chars) 50 | listOfPossiblePlates.sort(key = lambda possiblePlate: len(possiblePlate.strChars), reverse = True) 51 | 52 | # suppose the plate with the most recognized chars (the first plate in sorted by string length descending order) is the actual plate 53 | licPlate = listOfPossiblePlates[0] 54 | 55 | cv2.imshow("imgPlate", licPlate.imgPlate) # show crop of plate and threshold of plate 56 | cv2.imshow("imgThresh", licPlate.imgThresh) 57 | 58 | if len(licPlate.strChars) == 0: # if no chars were found in the plate 59 | print ("\nno characters were detected\n\n" ) # show message 60 | return # and exit program 61 | # end if 62 | 63 | drawRedRectangleAroundPlate(imgOriginalScene, licPlate) # draw red rectangle around plate 64 | 65 | print ("\nlicense plate read from image = " + licPlate.strChars + "\n" ) # write license plate text to std out 66 | print("----------------------------------------") 67 | 68 | writeLicensePlateCharsOnImage(imgOriginalScene, licPlate) # write license plate text on the image 69 | 70 | cv2.imshow("imgOriginalScene", imgOriginalScene) # re-show scene image 71 | 72 | cv2.imwrite("imgOriginalScene.png", imgOriginalScene) # write image out to file 73 | 74 | # end if else 75 | 76 | cv2.waitKey(0) # hold windows open until user presses a key 77 | 78 | return 79 | # end main 80 | 81 | ################################################################################################### 82 | def drawRedRectangleAroundPlate(imgOriginalScene, licPlate): 83 | 84 | p2fRectPoints = cv2.boxPoints(licPlate.rrLocationOfPlateInScene) # get 4 vertices of rotated rect 85 | 86 | cv2.line(imgOriginalScene, tuple(p2fRectPoints[0]), tuple(p2fRectPoints[1]), SCALAR_RED, 2) # draw 4 red lines 87 | cv2.line(imgOriginalScene, tuple(p2fRectPoints[1]), tuple(p2fRectPoints[2]), SCALAR_RED, 2) 88 | cv2.line(imgOriginalScene, tuple(p2fRectPoints[2]), tuple(p2fRectPoints[3]), SCALAR_RED, 2) 89 | cv2.line(imgOriginalScene, tuple(p2fRectPoints[3]), tuple(p2fRectPoints[0]), SCALAR_RED, 2) 90 | # end function 91 | 92 | ################################################################################################### 93 | def writeLicensePlateCharsOnImage(imgOriginalScene, licPlate): 94 | ptCenterOfTextAreaX = 0 # this will be the center of the area the text will be written to 95 | ptCenterOfTextAreaY = 0 96 | 97 | ptLowerLeftTextOriginX = 0 # this will be the bottom left of the area that the text will be written to 98 | ptLowerLeftTextOriginY = 0 99 | 100 | sceneHeight, sceneWidth, sceneNumChannels = imgOriginalScene.shape 101 | plateHeight, plateWidth, plateNumChannels = licPlate.imgPlate.shape 102 | 103 | intFontFace = cv2.FONT_HERSHEY_SIMPLEX # choose a plain jane font 104 | fltFontScale = float(plateHeight) / 30.0 # base font scale on height of plate area 105 | intFontThickness = int(round(fltFontScale * 1.5)) # base font thickness on font scale 106 | 107 | textSize, baseline = cv2.getTextSize(licPlate.strChars, intFontFace, fltFontScale, intFontThickness) # call getTextSize 108 | 109 | # unpack roatated rect into center point, width and height, and angle 110 | ( (intPlateCenterX, intPlateCenterY), (intPlateWidth, intPlateHeight), fltCorrectionAngleInDeg ) = licPlate.rrLocationOfPlateInScene 111 | 112 | intPlateCenterX = int(intPlateCenterX) # make sure center is an integer 113 | intPlateCenterY = int(intPlateCenterY) 114 | 115 | ptCenterOfTextAreaX = int(intPlateCenterX) # the horizontal location of the text area is the same as the plate 116 | 117 | if intPlateCenterY < (sceneHeight * 0.75): # if the license plate is in the upper 3/4 of the image 118 | ptCenterOfTextAreaY = int(round(intPlateCenterY)) + int(round(plateHeight * 1.6)) # write the chars in below the plate 119 | else: # else if the license plate is in the lower 1/4 of the image 120 | ptCenterOfTextAreaY = int(round(intPlateCenterY)) - int(round(plateHeight * 1.6)) # write the chars in above the plate 121 | # end if 122 | 123 | textSizeWidth, textSizeHeight = textSize # unpack text size width and height 124 | 125 | ptLowerLeftTextOriginX = int(ptCenterOfTextAreaX - (textSizeWidth / 2)) # calculate the lower left origin of the text area 126 | ptLowerLeftTextOriginY = int(ptCenterOfTextAreaY + (textSizeHeight / 2)) # based on the text area center, width, and height 127 | 128 | # write the text on the image 129 | cv2.putText(imgOriginalScene, licPlate.strChars, (ptLowerLeftTextOriginX, ptLowerLeftTextOriginY), intFontFace, fltFontScale, SCALAR_YELLOW, intFontThickness) 130 | # end function 131 | 132 | ################################################################################################### 133 | 134 | if __name__ == "__main__": 135 | main() 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /visualization_utils_test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Tests for image.understanding.object_detection.core.visualization_utils. 17 | 18 | Testing with visualization in the following colab: 19 | https://drive.google.com/a/google.com/file/d/0B5HnKS_hMsNARERpU3MtU3I5RFE/view?usp=sharing 20 | 21 | """ 22 | 23 | import os 24 | 25 | import numpy as np 26 | import PIL.Image as Image 27 | import tensorflow as tf 28 | 29 | from object_detection.utils import visualization_utils 30 | 31 | _TESTDATA_PATH = 'object_detection/test_images' 32 | 33 | 34 | class VisualizationUtilsTest(tf.test.TestCase): 35 | 36 | def create_colorful_test_image(self): 37 | """This function creates an image that can be used to test vis functions. 38 | 39 | It makes an image composed of four colored rectangles. 40 | 41 | Returns: 42 | colorful test numpy array image. 43 | """ 44 | ch255 = np.full([100, 200, 1], 255, dtype=np.uint8) 45 | ch128 = np.full([100, 200, 1], 128, dtype=np.uint8) 46 | ch0 = np.full([100, 200, 1], 0, dtype=np.uint8) 47 | imr = np.concatenate((ch255, ch128, ch128), axis=2) 48 | img = np.concatenate((ch255, ch255, ch0), axis=2) 49 | imb = np.concatenate((ch255, ch0, ch255), axis=2) 50 | imw = np.concatenate((ch128, ch128, ch128), axis=2) 51 | imu = np.concatenate((imr, img), axis=1) 52 | imd = np.concatenate((imb, imw), axis=1) 53 | image = np.concatenate((imu, imd), axis=0) 54 | return image 55 | 56 | def test_draw_bounding_box_on_image(self): 57 | test_image = self.create_colorful_test_image() 58 | test_image = Image.fromarray(test_image) 59 | width_original, height_original = test_image.size 60 | ymin = 0.25 61 | ymax = 0.75 62 | xmin = 0.4 63 | xmax = 0.6 64 | 65 | visualization_utils.draw_bounding_box_on_image(test_image, ymin, xmin, ymax, 66 | xmax) 67 | width_final, height_final = test_image.size 68 | 69 | self.assertEqual(width_original, width_final) 70 | self.assertEqual(height_original, height_final) 71 | 72 | def test_draw_bounding_box_on_image_array(self): 73 | test_image = self.create_colorful_test_image() 74 | width_original = test_image.shape[0] 75 | height_original = test_image.shape[1] 76 | ymin = 0.25 77 | ymax = 0.75 78 | xmin = 0.4 79 | xmax = 0.6 80 | 81 | visualization_utils.draw_bounding_box_on_image_array( 82 | test_image, ymin, xmin, ymax, xmax) 83 | width_final = test_image.shape[0] 84 | height_final = test_image.shape[1] 85 | 86 | self.assertEqual(width_original, width_final) 87 | self.assertEqual(height_original, height_final) 88 | 89 | def test_draw_bounding_boxes_on_image(self): 90 | test_image = self.create_colorful_test_image() 91 | test_image = Image.fromarray(test_image) 92 | width_original, height_original = test_image.size 93 | boxes = np.array([[0.25, 0.75, 0.4, 0.6], 94 | [0.1, 0.1, 0.9, 0.9]]) 95 | 96 | visualization_utils.draw_bounding_boxes_on_image(test_image, boxes) 97 | width_final, height_final = test_image.size 98 | 99 | self.assertEqual(width_original, width_final) 100 | self.assertEqual(height_original, height_final) 101 | 102 | def test_draw_bounding_boxes_on_image_array(self): 103 | test_image = self.create_colorful_test_image() 104 | width_original = test_image.shape[0] 105 | height_original = test_image.shape[1] 106 | boxes = np.array([[0.25, 0.75, 0.4, 0.6], 107 | [0.1, 0.1, 0.9, 0.9]]) 108 | 109 | visualization_utils.draw_bounding_boxes_on_image_array(test_image, boxes) 110 | width_final = test_image.shape[0] 111 | height_final = test_image.shape[1] 112 | 113 | self.assertEqual(width_original, width_final) 114 | self.assertEqual(height_original, height_final) 115 | 116 | def test_draw_bounding_boxes_on_image_tensors(self): 117 | """Tests that bounding box utility produces reasonable results.""" 118 | category_index = {1: {'id': 1, 'name': 'dog'}, 2: {'id': 2, 'name': 'cat'}} 119 | 120 | fname = os.path.join(_TESTDATA_PATH, 'image1.jpg') 121 | image_np = np.array(Image.open(fname)) 122 | images_np = np.stack((image_np, image_np), axis=0) 123 | 124 | with tf.Graph().as_default(): 125 | images_tensor = tf.constant(value=images_np, dtype=tf.uint8) 126 | boxes = tf.constant([[[0.4, 0.25, 0.75, 0.75], [0.5, 0.3, 0.6, 0.9]], 127 | [[0.25, 0.25, 0.75, 0.75], [0.1, 0.3, 0.6, 1.0]]]) 128 | classes = tf.constant([[1, 1], [1, 2]], dtype=tf.int64) 129 | scores = tf.constant([[0.8, 0.1], [0.6, 0.5]]) 130 | images_with_boxes = ( 131 | visualization_utils.draw_bounding_boxes_on_image_tensors( 132 | images_tensor, 133 | boxes, 134 | classes, 135 | scores, 136 | category_index, 137 | min_score_thresh=0.2)) 138 | 139 | with self.test_session() as sess: 140 | sess.run(tf.global_variables_initializer()) 141 | 142 | # Write output images for visualization. 143 | images_with_boxes_np = sess.run(images_with_boxes) 144 | self.assertEqual(images_np.shape, images_with_boxes_np.shape) 145 | for i in range(images_with_boxes_np.shape[0]): 146 | img_name = 'image_' + str(i) + '.png' 147 | output_file = os.path.join(self.get_temp_dir(), img_name) 148 | print 'Writing output image %d to %s' % (i, output_file) 149 | image_pil = Image.fromarray(images_with_boxes_np[i, ...]) 150 | image_pil.save(output_file) 151 | 152 | def test_draw_keypoints_on_image(self): 153 | test_image = self.create_colorful_test_image() 154 | test_image = Image.fromarray(test_image) 155 | width_original, height_original = test_image.size 156 | keypoints = [[0.25, 0.75], [0.4, 0.6], [0.1, 0.1], [0.9, 0.9]] 157 | 158 | visualization_utils.draw_keypoints_on_image(test_image, keypoints) 159 | width_final, height_final = test_image.size 160 | 161 | self.assertEqual(width_original, width_final) 162 | self.assertEqual(height_original, height_final) 163 | 164 | def test_draw_keypoints_on_image_array(self): 165 | test_image = self.create_colorful_test_image() 166 | width_original = test_image.shape[0] 167 | height_original = test_image.shape[1] 168 | keypoints = [[0.25, 0.75], [0.4, 0.6], [0.1, 0.1], [0.9, 0.9]] 169 | 170 | visualization_utils.draw_keypoints_on_image_array(test_image, keypoints) 171 | width_final = test_image.shape[0] 172 | height_final = test_image.shape[1] 173 | 174 | self.assertEqual(width_original, width_final) 175 | self.assertEqual(height_original, height_final) 176 | 177 | def test_draw_mask_on_image_array(self): 178 | test_image = np.asarray([[[0, 0, 0], [0, 0, 0]], 179 | [[0, 0, 0], [0, 0, 0]]], dtype=np.uint8) 180 | mask = np.asarray([[0, 1], 181 | [1, 1]], dtype=np.uint8) 182 | expected_result = np.asarray([[[0, 0, 0], [0, 0, 127]], 183 | [[0, 0, 127], [0, 0, 127]]], dtype=np.uint8) 184 | visualization_utils.draw_mask_on_image_array(test_image, mask, 185 | color='Blue', alpha=.5) 186 | self.assertAllEqual(test_image, expected_result) 187 | 188 | def test_add_cdf_image_summary(self): 189 | values = [0.1, 0.2, 0.3, 0.4, 0.42, 0.44, 0.46, 0.48, 0.50] 190 | visualization_utils.add_cdf_image_summary(values, 'PositiveAnchorLoss') 191 | cdf_image_summary = tf.get_collection(key=tf.GraphKeys.SUMMARIES)[0] 192 | with self.test_session(): 193 | cdf_image_summary.eval() 194 | 195 | 196 | if __name__ == '__main__': 197 | tf.test.main() 198 | -------------------------------------------------------------------------------- /license_plate/DetectPlates.py: -------------------------------------------------------------------------------- 1 | # DetectPlates.py 2 | import os 3 | import cv2 4 | import numpy as np 5 | import math 6 | import Main 7 | import random 8 | import Preprocess 9 | import DetectChars 10 | import PossiblePlate 11 | import PossibleChar 12 | 13 | # module level variables ########################################################################## 14 | PLATE_WIDTH_PADDING_FACTOR = 1.3 15 | PLATE_HEIGHT_PADDING_FACTOR = 1.5 16 | 17 | ################################################################################################### 18 | def detectPlatesInScene(imgOriginalScene): 19 | listOfPossiblePlates = [] # this will be the return value 20 | 21 | height, width, numChannels = imgOriginalScene.shape 22 | 23 | imgGrayscaleScene = np.zeros((height, width, 1), np.uint8) 24 | imgThreshScene = np.zeros((height, width, 1), np.uint8) 25 | imgContours = np.zeros((height, width, 3), np.uint8) 26 | 27 | cv2.destroyAllWindows() 28 | 29 | if Main.showSteps == True: # show steps ####################################################### 30 | cv2.imshow("0", imgOriginalScene) 31 | # end if # show steps ######################################################################### 32 | 33 | imgGrayscaleScene, imgThreshScene = Preprocess.preprocess(imgOriginalScene) # preprocess to get grayscale and threshold images 34 | 35 | if Main.showSteps == True: # show steps ####################################################### 36 | cv2.imshow("1a", imgGrayscaleScene) 37 | cv2.imshow("1b", imgThreshScene) 38 | # end if # show steps ######################################################################### 39 | 40 | # find all possible chars in the scene, 41 | # this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet) 42 | listOfPossibleCharsInScene = findPossibleCharsInScene(imgThreshScene) 43 | 44 | if Main.showSteps == True: # show steps ####################################################### 45 | print ("step 2 - len(listOfPossibleCharsInScene) = " + str(len(listOfPossibleCharsInScene)) ) # 131 with MCLRNF1 image 46 | 47 | imgContours = np.zeros((height, width, 3), np.uint8) 48 | 49 | contours = [] 50 | 51 | for possibleChar in listOfPossibleCharsInScene: 52 | contours.append(possibleChar.contour) 53 | # end for 54 | 55 | cv2.drawContours(imgContours, contours, -1, Main.SCALAR_WHITE) 56 | cv2.imshow("2b", imgContours) 57 | # end if # show steps ######################################################################### 58 | 59 | # given a list of all possible chars, find groups of matching chars 60 | # in the next steps each group of matching chars will attempt to be recognized as a plate 61 | listOfListsOfMatchingCharsInScene = DetectChars.findListOfListsOfMatchingChars(listOfPossibleCharsInScene) 62 | 63 | if Main.showSteps == True: # show steps ####################################################### 64 | print ("step 3 - listOfListsOfMatchingCharsInScene.Count = " + str(len(listOfListsOfMatchingCharsInScene)) ) # 13 with MCLRNF1 image 65 | 66 | imgContours = np.zeros((height, width, 3), np.uint8) 67 | 68 | for listOfMatchingChars in listOfListsOfMatchingCharsInScene: 69 | intRandomBlue = random.randint(0, 255) 70 | intRandomGreen = random.randint(0, 255) 71 | intRandomRed = random.randint(0, 255) 72 | 73 | contours = [] 74 | 75 | for matchingChar in listOfMatchingChars: 76 | contours.append(matchingChar.contour) 77 | # end for 78 | 79 | cv2.drawContours(imgContours, contours, -1, (intRandomBlue, intRandomGreen, intRandomRed)) 80 | # end for 81 | 82 | cv2.imshow("3", imgContours) 83 | # end if # show steps ######################################################################### 84 | 85 | for listOfMatchingChars in listOfListsOfMatchingCharsInScene: # for each group of matching chars 86 | possiblePlate = extractPlate(imgOriginalScene, listOfMatchingChars) # attempt to extract plate 87 | 88 | if possiblePlate.imgPlate is not None: # if plate was found 89 | listOfPossiblePlates.append(possiblePlate) # add to list of possible plates 90 | # end if 91 | # end for 92 | 93 | print ("\n" + str(len(listOfPossiblePlates)) + " possible plates found" ) # 13 with MCLRNF1 image 94 | 95 | if Main.showSteps == True: # show steps ####################################################### 96 | print("\n") 97 | cv2.imshow("4a", imgContours) 98 | 99 | for i in range(0, len(listOfPossiblePlates)): 100 | p2fRectPoints = cv2.boxPoints(listOfPossiblePlates[i].rrLocationOfPlateInScene) 101 | 102 | cv2.line(imgContours, tuple(p2fRectPoints[0]), tuple(p2fRectPoints[1]), Main.SCALAR_RED, 2) 103 | cv2.line(imgContours, tuple(p2fRectPoints[1]), tuple(p2fRectPoints[2]), Main.SCALAR_RED, 2) 104 | cv2.line(imgContours, tuple(p2fRectPoints[2]), tuple(p2fRectPoints[3]), Main.SCALAR_RED, 2) 105 | cv2.line(imgContours, tuple(p2fRectPoints[3]), tuple(p2fRectPoints[0]), Main.SCALAR_RED, 2) 106 | 107 | cv2.imshow("4a", imgContours) 108 | 109 | print ("possible plate " + str(i) + ", click on any image and press a key to continue . . .") 110 | 111 | cv2.imshow("4b", listOfPossiblePlates[i].imgPlate) 112 | cv2.waitKey(0) 113 | # end for 114 | 115 | print ("\nplate detection complete, click on any image and press a key to begin char recognition . . .\n") 116 | cv2.waitKey(0) 117 | # end if # show steps ######################################################################### 118 | 119 | return listOfPossiblePlates 120 | # end function 121 | 122 | ################################################################################################### 123 | def findPossibleCharsInScene(imgThresh): 124 | listOfPossibleChars = [] # this will be the return value 125 | 126 | intCountOfPossibleChars = 0 127 | 128 | imgThreshCopy = imgThresh.copy() 129 | 130 | imgContours, contours, npaHierarchy = cv2.findContours(imgThreshCopy, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # find all contours 131 | 132 | height, width = imgThresh.shape 133 | imgContours = np.zeros((height, width, 3), np.uint8) 134 | 135 | for i in range(0, len(contours)): # for each contour 136 | 137 | if Main.showSteps == True: # show steps ################################################### 138 | cv2.drawContours(imgContours, contours, i, Main.SCALAR_WHITE) 139 | # end if # show steps ##################################################################### 140 | 141 | possibleChar = PossibleChar.PossibleChar(contours[i]) 142 | 143 | if DetectChars.checkIfPossibleChar(possibleChar): # if contour is a possible char, note this does not compare to other chars (yet) . . . 144 | intCountOfPossibleChars = intCountOfPossibleChars + 1 # increment count of possible chars 145 | listOfPossibleChars.append(possibleChar) # and add to list of possible chars 146 | # end if 147 | # end for 148 | 149 | if Main.showSteps == True: # show steps ####################################################### 150 | print ("\nstep 2 - len(contours) = " + str(len(contours))) # 2362 with MCLRNF1 image 151 | print ("step 2 - intCountOfPossibleChars = " + str(intCountOfPossibleChars)) # 131 with MCLRNF1 image 152 | cv2.imshow("2a", imgContours) 153 | # end if # show steps ######################################################################### 154 | 155 | return listOfPossibleChars 156 | # end function 157 | 158 | 159 | ################################################################################################### 160 | def extractPlate(imgOriginal, listOfMatchingChars): 161 | possiblePlate = PossiblePlate.PossiblePlate() # this will be the return value 162 | 163 | listOfMatchingChars.sort(key = lambda matchingChar: matchingChar.intCenterX) # sort chars from left to right based on x position 164 | 165 | # calculate the center point of the plate 166 | fltPlateCenterX = (listOfMatchingChars[0].intCenterX + listOfMatchingChars[len(listOfMatchingChars) - 1].intCenterX) / 2.0 167 | fltPlateCenterY = (listOfMatchingChars[0].intCenterY + listOfMatchingChars[len(listOfMatchingChars) - 1].intCenterY) / 2.0 168 | 169 | ptPlateCenter = fltPlateCenterX, fltPlateCenterY 170 | 171 | # calculate plate width and height 172 | intPlateWidth = int((listOfMatchingChars[len(listOfMatchingChars) - 1].intBoundingRectX + listOfMatchingChars[len(listOfMatchingChars) - 1].intBoundingRectWidth - listOfMatchingChars[0].intBoundingRectX) * PLATE_WIDTH_PADDING_FACTOR) 173 | 174 | intTotalOfCharHeights = 0 175 | 176 | for matchingChar in listOfMatchingChars: 177 | intTotalOfCharHeights = intTotalOfCharHeights + matchingChar.intBoundingRectHeight 178 | # end for 179 | 180 | fltAverageCharHeight = intTotalOfCharHeights / len(listOfMatchingChars) 181 | 182 | intPlateHeight = int(fltAverageCharHeight * PLATE_HEIGHT_PADDING_FACTOR) 183 | 184 | # calculate correction angle of plate region 185 | fltOpposite = listOfMatchingChars[len(listOfMatchingChars) - 1].intCenterY - listOfMatchingChars[0].intCenterY 186 | fltHypotenuse = DetectChars.distanceBetweenChars(listOfMatchingChars[0], listOfMatchingChars[len(listOfMatchingChars) - 1]) 187 | fltCorrectionAngleInRad = math.asin(fltOpposite / fltHypotenuse) 188 | fltCorrectionAngleInDeg = fltCorrectionAngleInRad * (180.0 / math.pi) 189 | 190 | # pack plate region center point, width and height, and correction angle into rotated rect member variable of plate 191 | possiblePlate.rrLocationOfPlateInScene = ( tuple(ptPlateCenter), (intPlateWidth, intPlateHeight), fltCorrectionAngleInDeg ) 192 | 193 | # final steps are to perform the actual rotation 194 | 195 | # get the rotation matrix for our calculated correction angle 196 | rotationMatrix = cv2.getRotationMatrix2D(tuple(ptPlateCenter), fltCorrectionAngleInDeg, 1.0) 197 | 198 | height, width, numChannels = imgOriginal.shape # unpack original image width and height 199 | 200 | imgRotated = cv2.warpAffine(imgOriginal, rotationMatrix, (width, height)) # rotate the entire image 201 | 202 | imgCropped = cv2.getRectSubPix(imgRotated, (intPlateWidth, intPlateHeight), tuple(ptPlateCenter)) 203 | 204 | possiblePlate.imgPlate = imgCropped # copy the cropped plate image into the applicable member variable of the possible plate 205 | 206 | return possiblePlate 207 | # end function 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | -------------------------------------------------------------------------------- /object_detection1.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Object Detection 15BCE0014\n" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "# Imports" 15 | ] 16 | }, 17 | { 18 | "cell_type": "code", 19 | "execution_count": 1, 20 | "metadata": { 21 | "scrolled": true 22 | }, 23 | "outputs": [ 24 | { 25 | "name": "stdout", 26 | "output_type": "stream", 27 | "text": [ 28 | "Khandelwal Prateek Ratan Kumar 15BCE0014\n" 29 | ] 30 | } 31 | ], 32 | "source": [ 33 | "import numpy as np\n", 34 | "import os\n", 35 | "import six.moves.urllib as urllib\n", 36 | "import sys\n", 37 | "import tarfile\n", 38 | "import tensorflow as tf\n", 39 | "import zipfile\n", 40 | "import struct\n", 41 | "from PIL import Image\n", 42 | "import scipy\n", 43 | "import scipy.misc\n", 44 | "import scipy.cluster\n", 45 | "from collections import defaultdict\n", 46 | "from io import StringIO\n", 47 | "from matplotlib import pyplot as plt\n", 48 | "from PIL import Image\n", 49 | "from object_detection.utils import ops as utils_ops\n", 50 | "import color1\n", 51 | "import sys\n", 52 | "sys.path.append(r\"C:\\Users\\user\\models\\research\\object_detection\\license_plate\")\n", 53 | "import Main\n", 54 | "\n", 55 | "if tf.__version__ < '1.4.0':\n", 56 | " raise ImportError('Please upgrade your tensorflow installation to v1.4.* or later!')\n", 57 | "\n", 58 | "print('Khandelwal Prateek Ratan Kumar 15BCE0014')\n" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "## Env setup" 66 | ] 67 | }, 68 | { 69 | "cell_type": "code", 70 | "execution_count": 2, 71 | "metadata": {}, 72 | "outputs": [], 73 | "source": [ 74 | "# This is needed to display the images.\n", 75 | "%matplotlib inline\n", 76 | "\n", 77 | "# This is needed since the notebook is stored in the object_detection folder.\n", 78 | "sys.path.append(\"..\")" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "## Object detection imports\n", 86 | "Here are the imports from the object detection module." 87 | ] 88 | }, 89 | { 90 | "cell_type": "code", 91 | "execution_count": 3, 92 | "metadata": {}, 93 | "outputs": [ 94 | { 95 | "name": "stderr", 96 | "output_type": "stream", 97 | "text": [ 98 | "C:\\Users\\user\\models\\research\\object_detection\\utils\\visualization_utils.py:25: UserWarning: \n", 99 | "This call to matplotlib.use() has no effect because the backend has already\n", 100 | "been chosen; matplotlib.use() must be called *before* pylab, matplotlib.pyplot,\n", 101 | "or matplotlib.backends is imported for the first time.\n", 102 | "\n", 103 | "The backend was *originally* set to 'module://ipykernel.pylab.backend_inline' by the following code:\n", 104 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\runpy.py\", line 193, in _run_module_as_main\n", 105 | " \"__main__\", mod_spec)\n", 106 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\runpy.py\", line 85, in _run_code\n", 107 | " exec(code, run_globals)\n", 108 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel_launcher.py\", line 16, in \n", 109 | " app.launch_new_instance()\n", 110 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\traitlets\\config\\application.py\", line 658, in launch_instance\n", 111 | " app.start()\n", 112 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\kernelapp.py\", line 486, in start\n", 113 | " self.io_loop.start()\n", 114 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\tornado\\ioloop.py\", line 832, in start\n", 115 | " self._run_callback(self._callbacks.popleft())\n", 116 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\tornado\\ioloop.py\", line 605, in _run_callback\n", 117 | " ret = callback()\n", 118 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\tornado\\stack_context.py\", line 277, in null_wrapper\n", 119 | " return fn(*args, **kwargs)\n", 120 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\zmq\\eventloop\\zmqstream.py\", line 536, in \n", 121 | " self.io_loop.add_callback(lambda : self._handle_events(self.socket, 0))\n", 122 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\zmq\\eventloop\\zmqstream.py\", line 450, in _handle_events\n", 123 | " self._handle_recv()\n", 124 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\zmq\\eventloop\\zmqstream.py\", line 480, in _handle_recv\n", 125 | " self._run_callback(callback, msg)\n", 126 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\zmq\\eventloop\\zmqstream.py\", line 432, in _run_callback\n", 127 | " callback(*args, **kwargs)\n", 128 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\tornado\\stack_context.py\", line 277, in null_wrapper\n", 129 | " return fn(*args, **kwargs)\n", 130 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 283, in dispatcher\n", 131 | " return self.dispatch_shell(stream, msg)\n", 132 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 233, in dispatch_shell\n", 133 | " handler(stream, idents, msg)\n", 134 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\kernelbase.py\", line 399, in execute_request\n", 135 | " user_expressions, allow_stdin)\n", 136 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\ipkernel.py\", line 208, in do_execute\n", 137 | " res = shell.run_cell(code, store_history=store_history, silent=silent)\n", 138 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\ipykernel\\zmqshell.py\", line 537, in run_cell\n", 139 | " return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs)\n", 140 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2728, in run_cell\n", 141 | " interactivity=interactivity, compiler=compiler, result=result)\n", 142 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2850, in run_ast_nodes\n", 143 | " if self.run_code(code, result):\n", 144 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2910, in run_code\n", 145 | " exec(code_obj, self.user_global_ns, self.user_ns)\n", 146 | " File \"\", line 2, in \n", 147 | " get_ipython().run_line_magic('matplotlib', 'inline')\n", 148 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2095, in run_line_magic\n", 149 | " result = fn(*args,**kwargs)\n", 150 | " File \"\", line 2, in matplotlib\n", 151 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\magic.py\", line 187, in \n", 152 | " call = lambda f, *a, **k: f(*a, **k)\n", 153 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\magics\\pylab.py\", line 99, in matplotlib\n", 154 | " gui, backend = self.shell.enable_matplotlib(args.gui)\n", 155 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\interactiveshell.py\", line 2978, in enable_matplotlib\n", 156 | " pt.activate_matplotlib(backend)\n", 157 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\IPython\\core\\pylabtools.py\", line 308, in activate_matplotlib\n", 158 | " matplotlib.pyplot.switch_backend(backend)\n", 159 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\matplotlib\\pyplot.py\", line 231, in switch_backend\n", 160 | " matplotlib.use(newbackend, warn=False, force=True)\n", 161 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\matplotlib\\__init__.py\", line 1400, in use\n", 162 | " reload(sys.modules['matplotlib.backends'])\n", 163 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\importlib\\__init__.py\", line 166, in reload\n", 164 | " _bootstrap._exec(spec, module)\n", 165 | " File \"c:\\users\\user\\appdata\\local\\programs\\python\\python36\\lib\\site-packages\\matplotlib\\backends\\__init__.py\", line 16, in \n", 166 | " line for line in traceback.format_stack()\n", 167 | "\n", 168 | "\n", 169 | " import matplotlib; matplotlib.use('Agg') # pylint: disable=multiple-statements\n" 170 | ] 171 | } 172 | ], 173 | "source": [ 174 | "from utils import label_map_util\n", 175 | "\n", 176 | "from utils import visualization_utils as vis_util" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": {}, 182 | "source": [ 183 | "# Model preparation " 184 | ] 185 | }, 186 | { 187 | "cell_type": "markdown", 188 | "metadata": {}, 189 | "source": [ 190 | "## Variables\n", 191 | "\n", 192 | "Any model exported using the `export_inference_graph.py` tool can be loaded here simply by changing `PATH_TO_CKPT` to point to a new .pb file. \n", 193 | "\n", 194 | "By default we use an \"SSD with Mobilenet\" model here. See the [detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md) for a list of other models that can be run out-of-the-box with varying speeds and accuracies." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 4, 200 | "metadata": {}, 201 | "outputs": [], 202 | "source": [ 203 | "\n", 204 | "\n", 205 | "# Path to frozen detection graph. This is the actual model that is used for the object detection.\n", 206 | "\n", 207 | "MODEL_NAME = 'faster_rcnn_resnet101_coco_2018_01_28'\n", 208 | "\n", 209 | "PATH_TO_CKPT = MODEL_NAME + '/frozen_inference_graph.pb'\n", 210 | "\n", 211 | "# List of the strings that is used to add correct label for each box.\n", 212 | "PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt')\n", 213 | "\n", 214 | "NUM_CLASSES = 90" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": {}, 220 | "source": [ 221 | "## Download Model" 222 | ] 223 | }, 224 | { 225 | "cell_type": "code", 226 | "execution_count": null, 227 | "metadata": {}, 228 | "outputs": [], 229 | "source": [] 230 | }, 231 | { 232 | "cell_type": "markdown", 233 | "metadata": {}, 234 | "source": [ 235 | "## Load a (frozen) Tensorflow model into memory." 236 | ] 237 | }, 238 | { 239 | "cell_type": "code", 240 | "execution_count": 5, 241 | "metadata": {}, 242 | "outputs": [], 243 | "source": [ 244 | "detection_graph = tf.Graph()\n", 245 | "with detection_graph.as_default():\n", 246 | " od_graph_def = tf.GraphDef()\n", 247 | " with tf.gfile.GFile(PATH_TO_CKPT, 'rb') as fid:\n", 248 | " serialized_graph = fid.read()\n", 249 | " od_graph_def.ParseFromString(serialized_graph)\n", 250 | " tf.import_graph_def(od_graph_def, name='')" 251 | ] 252 | }, 253 | { 254 | "cell_type": "markdown", 255 | "metadata": {}, 256 | "source": [ 257 | "## Loading label map\n", 258 | "Label maps map indices to category names, so that when our convolution network predicts `5`, we know that this corresponds to `airplane`. Here we use internal utility functions, but anything that returns a dictionary mapping integers to appropriate string labels would be fine" 259 | ] 260 | }, 261 | { 262 | "cell_type": "code", 263 | "execution_count": 6, 264 | "metadata": {}, 265 | "outputs": [], 266 | "source": [ 267 | "label_map = label_map_util.load_labelmap(PATH_TO_LABELS)\n", 268 | "categories = label_map_util.convert_label_map_to_categories(label_map, max_num_classes=NUM_CLASSES, use_display_name=True)\n", 269 | "category_index = label_map_util.create_category_index(categories)" 270 | ] 271 | }, 272 | { 273 | "cell_type": "markdown", 274 | "metadata": {}, 275 | "source": [ 276 | "## Helper code" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": 7, 282 | "metadata": {}, 283 | "outputs": [], 284 | "source": [ 285 | "def load_image_into_numpy_array(image):\n", 286 | " (im_width, im_height) = image.size\n", 287 | " return np.array(image.getdata()).reshape(\n", 288 | " (im_height, im_width, 3)).astype(np.uint8)" 289 | ] 290 | }, 291 | { 292 | "cell_type": "markdown", 293 | "metadata": {}, 294 | "source": [ 295 | "# Detection" 296 | ] 297 | }, 298 | { 299 | "cell_type": "code", 300 | "execution_count": 8, 301 | "metadata": {}, 302 | "outputs": [], 303 | "source": [ 304 | "# For the sake of simplicity we will use only 2 images:\n", 305 | "# image1.jpg\n", 306 | "# image2.jpg\n", 307 | "# If you want to test the code with your images, just add path to the images to the TEST_IMAGE_PATHS.\n", 308 | "PATH_TO_TEST_IMAGES_DIR = 'test_images'\n", 309 | "TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1, 2) ]\n", 310 | "\n", 311 | "# Size, in inches, of the output images.\n", 312 | "IMAGE_SIZE = (12, 8)" 313 | ] 314 | }, 315 | { 316 | "cell_type": "code", 317 | "execution_count": 9, 318 | "metadata": {}, 319 | "outputs": [], 320 | "source": [ 321 | " \n", 322 | "def run_inference_for_single_image(image, graph):\n", 323 | " with graph.as_default():\n", 324 | " with tf.Session() as sess:\n", 325 | " # Get handles to input and output tensors\n", 326 | " ops = tf.get_default_graph().get_operations()\n", 327 | " all_tensor_names = {output.name for op in ops for output in op.outputs}\n", 328 | " tensor_dict = {}\n", 329 | " for key in [\n", 330 | " 'num_detections', 'detection_boxes', 'detection_scores',\n", 331 | " 'detection_classes', 'detection_masks'\n", 332 | " ]:\n", 333 | " tensor_name = key + ':0'\n", 334 | " if tensor_name in all_tensor_names:\n", 335 | " tensor_dict[key] = tf.get_default_graph().get_tensor_by_name(\n", 336 | " tensor_name)\n", 337 | " if 'detection_masks' in tensor_dict:\n", 338 | " # The following processing is only for single image\n", 339 | " detection_boxes = tf.squeeze(tensor_dict['detection_boxes'], [0]) \n", 340 | " detection_masks = tf.squeeze(tensor_dict['detection_masks'], [0])\n", 341 | " # Reframe is required to translate mask from box coordinates to image coordinates and fit the image size.\n", 342 | " real_num_detection = tf.cast(tensor_dict['num_detections'][0], tf.int32)\n", 343 | " detection_boxes = tf.slice(detection_boxes, [0, 0], [real_num_detection, -1])\n", 344 | " detection_masks = tf.slice(detection_masks, [0, 0, 0], [real_num_detection, -1, -1])\n", 345 | " detection_masks_reframed = utils_ops.reframe_box_masks_to_image_masks(\n", 346 | " detection_masks, detection_boxes, image.shape[0], image.shape[1])\n", 347 | " detection_masks_reframed = tf.cast(\n", 348 | " tf.greater(detection_masks_reframed, 0.5), tf.uint8)\n", 349 | " # Follow the convention by adding back the batch dimension\n", 350 | " tensor_dict['detection_masks'] = tf.expand_dims(\n", 351 | " detection_masks_reframed, 0)\n", 352 | " image_tensor = tf.get_default_graph().get_tensor_by_name('image_tensor:0')\n", 353 | "\n", 354 | " # Run inference\n", 355 | " output_dict = sess.run(tensor_dict,\n", 356 | " feed_dict={image_tensor: np.expand_dims(image, 0)})\n", 357 | " \n", 358 | " # all outputs are float32 numpy arrays, so convert types as appropriate\n", 359 | " output_dict['num_detections'] = int(output_dict['num_detections'][0])\n", 360 | " output_dict['detection_classes'] = output_dict[\n", 361 | " 'detection_classes'][0].astype(np.uint8)\n", 362 | " output_dict['detection_boxes'] = output_dict['detection_boxes'][0]\n", 363 | " \n", 364 | " output_dict['detection_scores'] = output_dict['detection_scores'][0]\n", 365 | " if 'detection_masks' in output_dict:\n", 366 | " output_dict['detection_masks'] = output_dict['detection_masks'][0]\n", 367 | " return output_dict" 368 | ] 369 | }, 370 | { 371 | "cell_type": "code", 372 | "execution_count": null, 373 | "metadata": {}, 374 | "outputs": [], 375 | "source": [ 376 | "\n" 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": null, 382 | "metadata": { 383 | "scrolled": true 384 | }, 385 | "outputs": [ 386 | { 387 | "name": "stdout", 388 | "output_type": "stream", 389 | "text": [ 390 | "[[ 66.711177 86.56160127 114.97072415]\n", 391 | " [184.12347661 186.60314618 191.73667379]\n", 392 | " [ 17.17046362 20.45366968 27.62133751]]\n" 393 | ] 394 | }, 395 | { 396 | "data": { 397 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAABaCAYAAACosq2hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADx0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4wcmMxLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvjNbHMQAAAYlJREFUeJzt27EJQjEARVG/uJm1tSiWbiA4sZ3gCnGFD4rByzl1IK+6pMkyxtgA8P+2swcA8B2CDhAh6AARgg4QIegAEYIOECHoABGCDhAh6AARu19etj/efEvlY/frYfYEVjidL7MnZLyej2XNOS90gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gAhBB4gQdIAIQQeIEHSACEEHiBB0gIhljDF7AwBf4IUOECHoABGCDhAh6AARgg4QIegAEYIOECHoABGCDhAh6AARgg4QIegAEYIOECHoABGCDhAh6AARgg4QIegAEYIOECHoABGCDhAh6AARgg4Q8QbSUA2vAlCd3gAAAABJRU5ErkJggg==\n", 398 | "text/plain": [ 399 | "
" 400 | ] 401 | }, 402 | "metadata": {}, 403 | "output_type": "display_data" 404 | }, 405 | { 406 | "name": "stdout", 407 | "output_type": "stream", 408 | "text": [ 409 | "\n", 410 | "1 possible plates found\n", 411 | "\n", 412 | "license plate read from image = A02NJX808\n", 413 | "\n", 414 | "----------------------------------------\n" 415 | ] 416 | } 417 | ], 418 | "source": [ 419 | "for image_path in TEST_IMAGE_PATHS:\n", 420 | " image = Image.open(image_path)\n", 421 | " # the array based representation of the image will be used later in order to prepare the\n", 422 | " # result image with boxes and labels on it.\n", 423 | " image_np = load_image_into_numpy_array(image)\n", 424 | " # Expand dimensions since the model expects images to have shape: [1, None, None, 3]\n", 425 | " image_np_expanded = np.expand_dims(image_np, axis=0)\n", 426 | " # Actual detection.\n", 427 | " \n", 428 | " output_dict = run_inference_for_single_image(image_np, detection_graph)\n", 429 | " im_width, im_height = image.size\n", 430 | " ymin=int(output_dict['detection_boxes'][0][0]*im_height)\n", 431 | " xmin=int(output_dict['detection_boxes'][0][1]*im_width)\n", 432 | " ymax=int(output_dict['detection_boxes'][0][2]*im_height)\n", 433 | " xmax=int(output_dict['detection_boxes'][0][3]*im_width)\n", 434 | " coords = (xmin,ymin,xmax,ymax)\n", 435 | " color1.find_color(image,coords,'cropped_images/cropped.jpg')\n", 436 | " Main.main('cropped_images/cropped.jpg')\n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " # Visualization of the results of a detection.\n", 443 | " vis_util.visualize_boxes_and_labels_on_image_array(\n", 444 | " image_np,\n", 445 | " output_dict['detection_boxes'],\n", 446 | " output_dict['detection_classes'],\n", 447 | " output_dict['detection_scores'],\n", 448 | " category_index,\n", 449 | " instance_masks=output_dict.get('detection_masks'),\n", 450 | " use_normalized_coordinates=True,max_boxes_to_draw=1,\n", 451 | " min_score_thresh=.7,\n", 452 | " line_thickness=8)\n", 453 | " plt.figure(figsize=IMAGE_SIZE)\n", 454 | " plt.imshow(image_np)" 455 | ] 456 | } 457 | ], 458 | "metadata": { 459 | "colab": { 460 | "version": "0.3.2" 461 | }, 462 | "kernelspec": { 463 | "display_name": "Python 3", 464 | "language": "python", 465 | "name": "python3" 466 | }, 467 | "language_info": { 468 | "codemirror_mode": { 469 | "name": "ipython", 470 | "version": 3 471 | }, 472 | "file_extension": ".py", 473 | "mimetype": "text/x-python", 474 | "name": "python", 475 | "nbconvert_exporter": "python", 476 | "pygments_lexer": "ipython3", 477 | "version": "3.6.0" 478 | } 479 | }, 480 | "nbformat": 4, 481 | "nbformat_minor": 2 482 | } 483 | -------------------------------------------------------------------------------- /license_plate/DetectChars.py: -------------------------------------------------------------------------------- 1 | # DetectChars.py 2 | import os 3 | import cv2 4 | import numpy as np 5 | import math 6 | import random 7 | import Main 8 | import Preprocess 9 | import PossibleChar 10 | 11 | # module level variables ########################################################################## 12 | 13 | kNearest = cv2.ml.KNearest_create() 14 | 15 | # constants for checkIfPossibleChar, this checks one possible char only (does not compare to another char) 16 | MIN_PIXEL_WIDTH = 2 17 | MIN_PIXEL_HEIGHT = 8 18 | 19 | MIN_ASPECT_RATIO = 0.25 20 | MAX_ASPECT_RATIO = 1.0 21 | 22 | MIN_PIXEL_AREA = 80 23 | 24 | # constants for comparing two chars 25 | MIN_DIAG_SIZE_MULTIPLE_AWAY = 0.3 26 | MAX_DIAG_SIZE_MULTIPLE_AWAY = 5.0 27 | 28 | MAX_CHANGE_IN_AREA = 0.5 29 | 30 | MAX_CHANGE_IN_WIDTH = 0.8 31 | MAX_CHANGE_IN_HEIGHT = 0.2 32 | 33 | MAX_ANGLE_BETWEEN_CHARS = 12.0 34 | 35 | # other constants 36 | MIN_NUMBER_OF_MATCHING_CHARS = 3 37 | 38 | RESIZED_CHAR_IMAGE_WIDTH = 20 39 | RESIZED_CHAR_IMAGE_HEIGHT = 30 40 | 41 | MIN_CONTOUR_AREA = 100 42 | 43 | ################################################################################################### 44 | def loadKNNDataAndTrainKNN(): 45 | allContoursWithData = [] # declare empty lists, 46 | validContoursWithData = [] # we will fill these shortly 47 | 48 | try: 49 | 50 | npaClassifications = np.loadtxt("classifications.txt", np.float32) # read in training classifications 51 | except: # if file could not be opened 52 | print ("error, unable to open classifications.txt, exiting program\n") # show error message 53 | os.system("pause") 54 | return False # and return False 55 | # end try 56 | 57 | try: 58 | npaFlattenedImages = np.loadtxt("flattened_images.txt", np.float32) # read in training images 59 | except: # if file could not be opened 60 | print ("error, unable to open flattened_images.txt, exiting program\n") # show error message 61 | os.system("pause") 62 | return False # and return False 63 | # end try 64 | 65 | npaClassifications = npaClassifications.reshape((npaClassifications.size, 1)) # reshape numpy array to 1d, necessary to pass to call to train 66 | 67 | kNearest.setDefaultK(1) # set default K to 1 68 | 69 | kNearest.train(npaFlattenedImages, cv2.ml.ROW_SAMPLE, npaClassifications) # train KNN object 70 | 71 | return True # if we got here training was successful so return true 72 | # end function 73 | 74 | ################################################################################################### 75 | def detectCharsInPlates(listOfPossiblePlates): 76 | intPlateCounter = 0 77 | imgContours = None 78 | contours = [] 79 | 80 | if len(listOfPossiblePlates) == 0: # if list of possible plates is empty 81 | return listOfPossiblePlates # return 82 | # end if 83 | 84 | # at this point we can be sure the list of possible plates has at least one plate 85 | 86 | for possiblePlate in listOfPossiblePlates: # for each possible plate, this is a big for loop that takes up most of the function 87 | 88 | possiblePlate.imgGrayscale, possiblePlate.imgThresh = Preprocess.preprocess(possiblePlate.imgPlate) # preprocess to get grayscale and threshold images 89 | 90 | if Main.showSteps == True: # show steps ################################################### 91 | cv2.imshow("5a", possiblePlate.imgPlate) 92 | cv2.imshow("5b", possiblePlate.imgGrayscale) 93 | cv2.imshow("5c", possiblePlate.imgThresh) 94 | # end if # show steps ##################################################################### 95 | 96 | # increase size of plate image for easier viewing and char detection 97 | possiblePlate.imgThresh = cv2.resize(possiblePlate.imgThresh, (0, 0), fx = 1.6, fy = 1.6) 98 | 99 | # threshold again to eliminate any gray areas 100 | thresholdValue, possiblePlate.imgThresh = cv2.threshold(possiblePlate.imgThresh, 0.0, 255.0, cv2.THRESH_BINARY | cv2.THRESH_OTSU) 101 | 102 | if Main.showSteps == True: # show steps ################################################### 103 | cv2.imshow("5d", possiblePlate.imgThresh) 104 | # end if # show steps ##################################################################### 105 | 106 | # find all possible chars in the plate, 107 | # this function first finds all contours, then only includes contours that could be chars (without comparison to other chars yet) 108 | listOfPossibleCharsInPlate = findPossibleCharsInPlate(possiblePlate.imgGrayscale, possiblePlate.imgThresh) 109 | 110 | if Main.showSteps == True: # show steps ################################################### 111 | height, width, numChannels = possiblePlate.imgPlate.shape 112 | imgContours = np.zeros((height, width, 3), np.uint8) 113 | del contours[:] # clear the contours list 114 | 115 | for possibleChar in listOfPossibleCharsInPlate: 116 | contours.append(possibleChar.contour) 117 | # end for 118 | 119 | cv2.drawContours(imgContours, contours, -1, Main.SCALAR_WHITE) 120 | 121 | cv2.imshow("6", imgContours) 122 | # end if # show steps ##################################################################### 123 | 124 | # given a list of all possible chars, find groups of matching chars within the plate 125 | listOfListsOfMatchingCharsInPlate = findListOfListsOfMatchingChars(listOfPossibleCharsInPlate) 126 | 127 | if Main.showSteps == True: # show steps ################################################### 128 | imgContours = np.zeros((height, width, 3), np.uint8) 129 | del contours[:] 130 | 131 | for listOfMatchingChars in listOfListsOfMatchingCharsInPlate: 132 | intRandomBlue = random.randint(0, 255) 133 | intRandomGreen = random.randint(0, 255) 134 | intRandomRed = random.randint(0, 255) 135 | 136 | for matchingChar in listOfMatchingChars: 137 | contours.append(matchingChar.contour) 138 | # end for 139 | cv2.drawContours(imgContours, contours, -1, (intRandomBlue, intRandomGreen, intRandomRed)) 140 | # end for 141 | cv2.imshow("7", imgContours) 142 | # end if # show steps ##################################################################### 143 | 144 | if (len(listOfListsOfMatchingCharsInPlate) == 0): # if no groups of matching chars were found in the plate 145 | 146 | if Main.showSteps == True: # show steps ############################################### 147 | print ("chars found in plate number " + str(intPlateCounter) + " = (none), click on any image and press a key to continue . . .") 148 | intPlateCounter = intPlateCounter + 1 149 | cv2.destroyWindow("8") 150 | cv2.destroyWindow("9") 151 | cv2.destroyWindow("10") 152 | cv2.waitKey(0) 153 | # end if # show steps ################################################################# 154 | 155 | possiblePlate.strChars = "" 156 | continue # go back to top of for loop 157 | # end if 158 | 159 | for i in range(0, len(listOfListsOfMatchingCharsInPlate)): # within each list of matching chars 160 | listOfListsOfMatchingCharsInPlate[i].sort(key = lambda matchingChar: matchingChar.intCenterX) # sort chars from left to right 161 | listOfListsOfMatchingCharsInPlate[i] = removeInnerOverlappingChars(listOfListsOfMatchingCharsInPlate[i]) # and remove inner overlapping chars 162 | # end for 163 | 164 | if Main.showSteps == True: # show steps ################################################### 165 | imgContours = np.zeros((height, width, 3), np.uint8) 166 | 167 | for listOfMatchingChars in listOfListsOfMatchingCharsInPlate: 168 | intRandomBlue = random.randint(0, 255) 169 | intRandomGreen = random.randint(0, 255) 170 | intRandomRed = random.randint(0, 255) 171 | 172 | del contours[:] 173 | 174 | for matchingChar in listOfMatchingChars: 175 | contours.append(matchingChar.contour) 176 | # end for 177 | 178 | cv2.drawContours(imgContours, contours, -1, (intRandomBlue, intRandomGreen, intRandomRed)) 179 | # end for 180 | cv2.imshow("8", imgContours) 181 | # end if # show steps ##################################################################### 182 | 183 | # within each possible plate, suppose the longest list of potential matching chars is the actual list of chars 184 | intLenOfLongestListOfChars = 0 185 | intIndexOfLongestListOfChars = 0 186 | 187 | # loop through all the vectors of matching chars, get the index of the one with the most chars 188 | for i in range(0, len(listOfListsOfMatchingCharsInPlate)): 189 | if len(listOfListsOfMatchingCharsInPlate[i]) > intLenOfLongestListOfChars: 190 | intLenOfLongestListOfChars = len(listOfListsOfMatchingCharsInPlate[i]) 191 | intIndexOfLongestListOfChars = i 192 | # end if 193 | # end for 194 | 195 | # suppose that the longest list of matching chars within the plate is the actual list of chars 196 | longestListOfMatchingCharsInPlate = listOfListsOfMatchingCharsInPlate[intIndexOfLongestListOfChars] 197 | 198 | if Main.showSteps == True: # show steps ################################################### 199 | imgContours = np.zeros((height, width, 3), np.uint8) 200 | del contours[:] 201 | 202 | for matchingChar in longestListOfMatchingCharsInPlate: 203 | contours.append(matchingChar.contour) 204 | # end for 205 | 206 | cv2.drawContours(imgContours, contours, -1, Main.SCALAR_WHITE) 207 | 208 | cv2.imshow("9", imgContours) 209 | # end if # show steps ##################################################################### 210 | 211 | possiblePlate.strChars = recognizeCharsInPlate(possiblePlate.imgThresh, longestListOfMatchingCharsInPlate) 212 | 213 | if Main.showSteps == True: # show steps ################################################### 214 | print ("chars found in plate number " + str(intPlateCounter) + " = " + possiblePlate.strChars + ", click on any image and press a key to continue . . .") 215 | intPlateCounter = intPlateCounter + 1 216 | cv2.waitKey(0) 217 | # end if # show steps ##################################################################### 218 | 219 | # end of big for loop that takes up most of the function 220 | 221 | if Main.showSteps == True: 222 | print ("\nchar detection complete, click on any image and press a key to continue . . .\n") 223 | cv2.waitKey(0) 224 | # end if 225 | 226 | return listOfPossiblePlates 227 | # end function 228 | 229 | ################################################################################################### 230 | def findPossibleCharsInPlate(imgGrayscale, imgThresh): 231 | listOfPossibleChars = [] # this will be the return value 232 | contours = [] 233 | imgThreshCopy = imgThresh.copy() 234 | 235 | # find all contours in plate 236 | imgContours, contours, npaHierarchy = cv2.findContours(imgThreshCopy, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) 237 | 238 | for contour in contours: # for each contour 239 | possibleChar = PossibleChar.PossibleChar(contour) 240 | 241 | if checkIfPossibleChar(possibleChar): # if contour is a possible char, note this does not compare to other chars (yet) . . . 242 | listOfPossibleChars.append(possibleChar) # add to list of possible chars 243 | # end if 244 | # end if 245 | 246 | return listOfPossibleChars 247 | # end function 248 | 249 | ################################################################################################### 250 | def checkIfPossibleChar(possibleChar): 251 | # this function is a 'first pass' that does a rough check on a contour to see if it could be a char, 252 | # note that we are not (yet) comparing the char to other chars to look for a group 253 | if (possibleChar.intBoundingRectArea > MIN_PIXEL_AREA and 254 | possibleChar.intBoundingRectWidth > MIN_PIXEL_WIDTH and possibleChar.intBoundingRectHeight > MIN_PIXEL_HEIGHT and 255 | MIN_ASPECT_RATIO < possibleChar.fltAspectRatio and possibleChar.fltAspectRatio < MAX_ASPECT_RATIO): 256 | return True 257 | else: 258 | return False 259 | # end if 260 | # end function 261 | 262 | ################################################################################################### 263 | def findListOfListsOfMatchingChars(listOfPossibleChars): 264 | # with this function, we start off with all the possible chars in one big list 265 | # the purpose of this function is to re-arrange the one big list of chars into a list of lists of matching chars, 266 | # note that chars that are not found to be in a group of matches do not need to be considered further 267 | listOfListsOfMatchingChars = [] # this will be the return value 268 | 269 | for possibleChar in listOfPossibleChars: # for each possible char in the one big list of chars 270 | listOfMatchingChars = findListOfMatchingChars(possibleChar, listOfPossibleChars) # find all chars in the big list that match the current char 271 | 272 | listOfMatchingChars.append(possibleChar) # also add the current char to current possible list of matching chars 273 | 274 | if len(listOfMatchingChars) < MIN_NUMBER_OF_MATCHING_CHARS: # if current possible list of matching chars is not long enough to constitute a possible plate 275 | continue # jump back to the top of the for loop and try again with next char, note that it's not necessary 276 | # to save the list in any way since it did not have enough chars to be a possible plate 277 | # end if 278 | 279 | # if we get here, the current list passed test as a "group" or "cluster" of matching chars 280 | listOfListsOfMatchingChars.append(listOfMatchingChars) # so add to our list of lists of matching chars 281 | 282 | listOfPossibleCharsWithCurrentMatchesRemoved = [] 283 | 284 | # remove the current list of matching chars from the big list so we don't use those same chars twice, 285 | # make sure to make a new big list for this since we don't want to change the original big list 286 | listOfPossibleCharsWithCurrentMatchesRemoved = list(set(listOfPossibleChars) - set(listOfMatchingChars)) 287 | 288 | recursiveListOfListsOfMatchingChars = findListOfListsOfMatchingChars(listOfPossibleCharsWithCurrentMatchesRemoved) # recursive call 289 | 290 | for recursiveListOfMatchingChars in recursiveListOfListsOfMatchingChars: # for each list of matching chars found by recursive call 291 | listOfListsOfMatchingChars.append(recursiveListOfMatchingChars) # add to our original list of lists of matching chars 292 | # end for 293 | 294 | break # exit for 295 | 296 | # end for 297 | 298 | return listOfListsOfMatchingChars 299 | # end function 300 | 301 | ################################################################################################### 302 | def findListOfMatchingChars(possibleChar, listOfChars): 303 | # the purpose of this function is, given a possible char and a big list of possible chars, 304 | # find all chars in the big list that are a match for the single possible char, and return those matching chars as a list 305 | listOfMatchingChars = [] # this will be the return value 306 | 307 | for possibleMatchingChar in listOfChars: # for each char in big list 308 | if possibleMatchingChar == possibleChar: # 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 309 | # then we should not include it in the list of matches b/c that would end up double including the current char 310 | continue # so do not add to list of matches and jump back to top of for loop 311 | # end if 312 | # compute stuff to see if chars are a match 313 | fltDistanceBetweenChars = distanceBetweenChars(possibleChar, possibleMatchingChar) 314 | 315 | fltAngleBetweenChars = angleBetweenChars(possibleChar, possibleMatchingChar) 316 | 317 | fltChangeInArea = float(abs(possibleMatchingChar.intBoundingRectArea - possibleChar.intBoundingRectArea)) / float(possibleChar.intBoundingRectArea) 318 | 319 | fltChangeInWidth = float(abs(possibleMatchingChar.intBoundingRectWidth - possibleChar.intBoundingRectWidth)) / float(possibleChar.intBoundingRectWidth) 320 | fltChangeInHeight = float(abs(possibleMatchingChar.intBoundingRectHeight - possibleChar.intBoundingRectHeight)) / float(possibleChar.intBoundingRectHeight) 321 | 322 | # check if chars match 323 | if (fltDistanceBetweenChars < (possibleChar.fltDiagonalSize * MAX_DIAG_SIZE_MULTIPLE_AWAY) and 324 | fltAngleBetweenChars < MAX_ANGLE_BETWEEN_CHARS and 325 | fltChangeInArea < MAX_CHANGE_IN_AREA and 326 | fltChangeInWidth < MAX_CHANGE_IN_WIDTH and 327 | fltChangeInHeight < MAX_CHANGE_IN_HEIGHT): 328 | 329 | listOfMatchingChars.append(possibleMatchingChar) # if the chars are a match, add the current char to list of matching chars 330 | # end if 331 | # end for 332 | 333 | return listOfMatchingChars # return result 334 | # end function 335 | 336 | ################################################################################################### 337 | # use Pythagorean theorem to calculate distance between two chars 338 | def distanceBetweenChars(firstChar, secondChar): 339 | intX = abs(firstChar.intCenterX - secondChar.intCenterX) 340 | intY = abs(firstChar.intCenterY - secondChar.intCenterY) 341 | 342 | return math.sqrt((intX ** 2) + (intY ** 2)) 343 | # end function 344 | 345 | ################################################################################################### 346 | # use basic trigonometry (SOH CAH TOA) to calculate angle between chars 347 | def angleBetweenChars(firstChar, secondChar): 348 | fltAdj = float(abs(firstChar.intCenterX - secondChar.intCenterX)) 349 | fltOpp = float(abs(firstChar.intCenterY - secondChar.intCenterY)) 350 | 351 | if fltAdj != 0.0: # check to make sure we do not divide by zero if the center X positions are equal, float division by zero will cause a crash in Python 352 | fltAngleInRad = math.atan(fltOpp / fltAdj) # if adjacent is not zero, calculate angle 353 | else: 354 | fltAngleInRad = 1.5708 # if adjacent is zero, use this as the angle, this is to be consistent with the C++ version of this program 355 | # end if 356 | 357 | fltAngleInDeg = fltAngleInRad * (180.0 / math.pi) # calculate angle in degrees 358 | 359 | return fltAngleInDeg 360 | # end function 361 | 362 | ################################################################################################### 363 | # if we have two chars overlapping or to close to each other to possibly be separate chars, remove the inner (smaller) char, 364 | # this is to prevent including the same char twice if two contours are found for the same char, 365 | # for example for the letter 'O' both the inner ring and the outer ring may be found as contours, but we should only include the char once 366 | def removeInnerOverlappingChars(listOfMatchingChars): 367 | listOfMatchingCharsWithInnerCharRemoved = list(listOfMatchingChars) # this will be the return value 368 | 369 | for currentChar in listOfMatchingChars: 370 | for otherChar in listOfMatchingChars: 371 | if currentChar != otherChar: # if current char and other char are not the same char . . . 372 | # if current char and other char have center points at almost the same location . . . 373 | if distanceBetweenChars(currentChar, otherChar) < (currentChar.fltDiagonalSize * MIN_DIAG_SIZE_MULTIPLE_AWAY): 374 | # if we get in here we have found overlapping chars 375 | # next we identify which char is smaller, then if that char was not already removed on a previous pass, remove it 376 | if currentChar.intBoundingRectArea < otherChar.intBoundingRectArea: # if current char is smaller than other char 377 | if currentChar in listOfMatchingCharsWithInnerCharRemoved: # if current char was not already removed on a previous pass . . . 378 | listOfMatchingCharsWithInnerCharRemoved.remove(currentChar) # then remove current char 379 | # end if 380 | else: # else if other char is smaller than current char 381 | if otherChar in listOfMatchingCharsWithInnerCharRemoved: # if other char was not already removed on a previous pass . . . 382 | listOfMatchingCharsWithInnerCharRemoved.remove(otherChar) # then remove other char 383 | # end if 384 | # end if 385 | # end if 386 | # end if 387 | # end for 388 | # end for 389 | 390 | return listOfMatchingCharsWithInnerCharRemoved 391 | # end function 392 | 393 | ################################################################################################### 394 | # this is where we apply the actual char recognition 395 | def recognizeCharsInPlate(imgThresh, listOfMatchingChars): 396 | strChars = "" # this will be the return value, the chars in the lic plate 397 | 398 | height, width = imgThresh.shape 399 | 400 | imgThreshColor = np.zeros((height, width, 3), np.uint8) 401 | 402 | listOfMatchingChars.sort(key = lambda matchingChar: matchingChar.intCenterX) # sort chars from left to right 403 | 404 | cv2.cvtColor(imgThresh, cv2.COLOR_GRAY2BGR, imgThreshColor) # make color version of threshold image so we can draw contours in color on it 405 | 406 | for currentChar in listOfMatchingChars: # for each char in plate 407 | pt1 = (currentChar.intBoundingRectX, currentChar.intBoundingRectY) 408 | pt2 = ((currentChar.intBoundingRectX + currentChar.intBoundingRectWidth), (currentChar.intBoundingRectY + currentChar.intBoundingRectHeight)) 409 | 410 | cv2.rectangle(imgThreshColor, pt1, pt2, Main.SCALAR_GREEN, 2) # draw green box around the char 411 | 412 | # crop char out of threshold image 413 | imgROI = imgThresh[currentChar.intBoundingRectY : currentChar.intBoundingRectY + currentChar.intBoundingRectHeight, 414 | currentChar.intBoundingRectX : currentChar.intBoundingRectX + currentChar.intBoundingRectWidth] 415 | 416 | imgROIResized = cv2.resize(imgROI, (RESIZED_CHAR_IMAGE_WIDTH, RESIZED_CHAR_IMAGE_HEIGHT)) # resize image, this is necessary for char recognition 417 | 418 | npaROIResized = imgROIResized.reshape((1, RESIZED_CHAR_IMAGE_WIDTH * RESIZED_CHAR_IMAGE_HEIGHT)) # flatten image into 1d numpy array 419 | 420 | npaROIResized = np.float32(npaROIResized) # convert from 1d numpy array of ints to 1d numpy array of floats 421 | 422 | retval, npaResults, neigh_resp, dists = kNearest.findNearest(npaROIResized, k = 1) # finally we can call findNearest !!! 423 | 424 | strCurrentChar = str(chr(int(npaResults[0][0]))) # get character from results 425 | 426 | strChars = strChars + strCurrentChar # append current char to full string 427 | 428 | # end for 429 | 430 | if Main.showSteps == True: # show steps ####################################################### 431 | cv2.imshow("10", imgThreshColor) 432 | # end if # show steps ######################################################################### 433 | 434 | return strChars 435 | # end function 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | -------------------------------------------------------------------------------- /visualization_utils.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """A set of functions that are used for visualization. 17 | 18 | These functions often receive an image, perform some visualization on the image. 19 | The functions do not return a value, instead they modify the image itself. 20 | 21 | """ 22 | import collections 23 | import functools 24 | # Set headless-friendly backend. 25 | import matplotlib; matplotlib.use('Agg') # pylint: disable=multiple-statements 26 | import matplotlib.pyplot as plt # pylint: disable=g-import-not-at-top 27 | import numpy as np 28 | import PIL.Image as Image 29 | import PIL.ImageColor as ImageColor 30 | import PIL.ImageDraw as ImageDraw 31 | import PIL.ImageFont as ImageFont 32 | import six 33 | import tensorflow as tf 34 | 35 | from object_detection.core import standard_fields as fields 36 | 37 | 38 | _TITLE_LEFT_MARGIN = 10 39 | _TITLE_TOP_MARGIN = 10 40 | STANDARD_COLORS = [ 41 | 'AliceBlue', 'Chartreuse', 'Aqua', 'Aquamarine', 'Azure', 'Beige', 'Bisque', 42 | 'BlanchedAlmond', 'BlueViolet', 'BurlyWood', 'CadetBlue', 'AntiqueWhite', 43 | 'Chocolate', 'Coral', 'CornflowerBlue', 'Cornsilk', 'Crimson', 'Cyan', 44 | 'DarkCyan', 'DarkGoldenRod', 'DarkGrey', 'DarkKhaki', 'DarkOrange', 45 | 'DarkOrchid', 'DarkSalmon', 'DarkSeaGreen', 'DarkTurquoise', 'DarkViolet', 46 | 'DeepPink', 'DeepSkyBlue', 'DodgerBlue', 'FireBrick', 'FloralWhite', 47 | 'ForestGreen', 'Fuchsia', 'Gainsboro', 'GhostWhite', 'Gold', 'GoldenRod', 48 | 'Salmon', 'Tan', 'HoneyDew', 'HotPink', 'IndianRed', 'Ivory', 'Khaki', 49 | 'Lavender', 'LavenderBlush', 'LawnGreen', 'LemonChiffon', 'LightBlue', 50 | 'LightCoral', 'LightCyan', 'LightGoldenRodYellow', 'LightGray', 'LightGrey', 51 | 'LightGreen', 'LightPink', 'LightSalmon', 'LightSeaGreen', 'LightSkyBlue', 52 | 'LightSlateGray', 'LightSlateGrey', 'LightSteelBlue', 'LightYellow', 'Lime', 53 | 'LimeGreen', 'Linen', 'Magenta', 'MediumAquaMarine', 'MediumOrchid', 54 | 'MediumPurple', 'MediumSeaGreen', 'MediumSlateBlue', 'MediumSpringGreen', 55 | 'MediumTurquoise', 'MediumVioletRed', 'MintCream', 'MistyRose', 'Moccasin', 56 | 'NavajoWhite', 'OldLace', 'Olive', 'OliveDrab', 'Orange', 'OrangeRed', 57 | 'Orchid', 'PaleGoldenRod', 'PaleGreen', 'PaleTurquoise', 'PaleVioletRed', 58 | 'PapayaWhip', 'PeachPuff', 'Peru', 'Pink', 'Plum', 'PowderBlue', 'Purple', 59 | 'Red', 'RosyBrown', 'RoyalBlue', 'SaddleBrown', 'Green', 'SandyBrown', 60 | 'SeaGreen', 'SeaShell', 'Sienna', 'Silver', 'SkyBlue', 'SlateBlue', 61 | 'SlateGray', 'SlateGrey', 'Snow', 'SpringGreen', 'SteelBlue', 'GreenYellow', 62 | 'Teal', 'Thistle', 'Tomato', 'Turquoise', 'Violet', 'Wheat', 'White', 63 | 'WhiteSmoke', 'Yellow', 'YellowGreen' 64 | ] 65 | 66 | 67 | def save_image_array_as_png(image, output_path): 68 | """Saves an image (represented as a numpy array) to PNG. 69 | 70 | Args: 71 | image: a numpy array with shape [height, width, 3]. 72 | output_path: path to which image should be written. 73 | """ 74 | image_pil = Image.fromarray(np.uint8(image)).convert('RGB') 75 | with tf.gfile.Open(output_path, 'w') as fid: 76 | image_pil.save(fid, 'PNG') 77 | 78 | 79 | def encode_image_array_as_png_str(image): 80 | """Encodes a numpy array into a PNG string. 81 | 82 | Args: 83 | image: a numpy array with shape [height, width, 3]. 84 | 85 | Returns: 86 | PNG encoded image string. 87 | """ 88 | image_pil = Image.fromarray(np.uint8(image)) 89 | output = six.BytesIO() 90 | image_pil.save(output, format='PNG') 91 | png_string = output.getvalue() 92 | output.close() 93 | return png_string 94 | 95 | 96 | def draw_bounding_box_on_image_array(image, 97 | ymin, 98 | xmin, 99 | ymax, 100 | xmax, 101 | color='red', 102 | thickness=4, 103 | display_str_list=(), 104 | use_normalized_coordinates=True): 105 | """Adds a bounding box to an image (numpy array). 106 | 107 | Bounding box coordinates can be specified in either absolute (pixel) or 108 | normalized coordinates by setting the use_normalized_coordinates argument. 109 | 110 | Args: 111 | image: a numpy array with shape [height, width, 3]. 112 | ymin: ymin of bounding box. 113 | xmin: xmin of bounding box. 114 | ymax: ymax of bounding box. 115 | xmax: xmax of bounding box. 116 | color: color to draw bounding box. Default is red. 117 | thickness: line thickness. Default value is 4. 118 | display_str_list: list of strings to display in box 119 | (each to be shown on its own line). 120 | use_normalized_coordinates: If True (default), treat coordinates 121 | ymin, xmin, ymax, xmax as relative to the image. Otherwise treat 122 | coordinates as absolute. 123 | """ 124 | image_pil = Image.fromarray(np.uint8(image)).convert('RGB') 125 | draw_bounding_box_on_image(image_pil, ymin, xmin, ymax, xmax, color, 126 | thickness, display_str_list, 127 | use_normalized_coordinates) 128 | np.copyto(image, np.array(image_pil)) 129 | 130 | 131 | def draw_bounding_box_on_image(image, 132 | ymin, 133 | xmin, 134 | ymax, 135 | xmax, 136 | color='red', 137 | thickness=4, 138 | display_str_list=(), 139 | use_normalized_coordinates=True): 140 | """Adds a bounding box to an image. 141 | 142 | Bounding box coordinates can be specified in either absolute (pixel) or 143 | normalized coordinates by setting the use_normalized_coordinates argument. 144 | 145 | Each string in display_str_list is displayed on a separate line above the 146 | bounding box in black text on a rectangle filled with the input 'color'. 147 | If the top of the bounding box extends to the edge of the image, the strings 148 | are displayed below the bounding box. 149 | 150 | Args: 151 | image: a PIL.Image object. 152 | ymin: ymin of bounding box. 153 | xmin: xmin of bounding box. 154 | ymax: ymax of bounding box. 155 | xmax: xmax of bounding box. 156 | color: color to draw bounding box. Default is red. 157 | thickness: line thickness. Default value is 4. 158 | display_str_list: list of strings to display in box 159 | (each to be shown on its own line). 160 | use_normalized_coordinates: If True (default), treat coordinates 161 | ymin, xmin, ymax, xmax as relative to the image. Otherwise treat 162 | coordinates as absolute. 163 | """ 164 | draw = ImageDraw.Draw(image) 165 | im_width, im_height = image.size 166 | if use_normalized_coordinates: 167 | (left, right, top, bottom) = (xmin * im_width, xmax * im_width, 168 | ymin * im_height, ymax * im_height) 169 | else: 170 | (left, right, top, bottom) = (xmin, xmax, ymin, ymax) 171 | draw.line([(left, top), (left, bottom), (right, bottom), 172 | (right, top), (left, top)], width=thickness, fill=color) 173 | try: 174 | font = ImageFont.truetype('arial.ttf', 24) 175 | except IOError: 176 | font = ImageFont.load_default() 177 | 178 | # If the total height of the display strings added to the top of the bounding 179 | # box exceeds the top of the image, stack the strings below the bounding box 180 | # instead of above. 181 | display_str_heights = [font.getsize(ds)[1] for ds in display_str_list] 182 | # Each display_str has a top and bottom margin of 0.05x. 183 | total_display_str_height = (1 + 2 * 0.05) * sum(display_str_heights) 184 | 185 | if top > total_display_str_height: 186 | text_bottom = top 187 | else: 188 | text_bottom = bottom + total_display_str_height 189 | # Reverse list and print from bottom to top. 190 | for display_str in display_str_list[::-1]: 191 | text_width, text_height = font.getsize(display_str) 192 | margin = np.ceil(0.05 * text_height) 193 | draw.rectangle( 194 | [(left, text_bottom - text_height - 2 * margin), (left + text_width, 195 | text_bottom)], 196 | fill=color) 197 | draw.text( 198 | (left + margin, text_bottom - text_height - margin), 199 | display_str, 200 | fill='black', 201 | font=font) 202 | text_bottom -= text_height - 2 * margin 203 | 204 | 205 | def draw_bounding_boxes_on_image_array(image, 206 | boxes, 207 | color='red', 208 | thickness=4, 209 | display_str_list_list=()): 210 | """Draws bounding boxes on image (numpy array). 211 | 212 | Args: 213 | image: a numpy array object. 214 | boxes: a 2 dimensional numpy array of [N, 4]: (ymin, xmin, ymax, xmax). 215 | The coordinates are in normalized format between [0, 1]. 216 | color: color to draw bounding box. Default is red. 217 | thickness: line thickness. Default value is 4. 218 | display_str_list_list: list of list of strings. 219 | a list of strings for each bounding box. 220 | The reason to pass a list of strings for a 221 | bounding box is that it might contain 222 | multiple labels. 223 | 224 | Raises: 225 | ValueError: if boxes is not a [N, 4] array 226 | """ 227 | image_pil = Image.fromarray(image) 228 | draw_bounding_boxes_on_image(image_pil, boxes, color, thickness, 229 | display_str_list_list) 230 | np.copyto(image, np.array(image_pil)) 231 | 232 | 233 | def draw_bounding_boxes_on_image(image, 234 | boxes, 235 | color='red', 236 | thickness=4, 237 | display_str_list_list=()): 238 | """Draws bounding boxes on image. 239 | 240 | Args: 241 | image: a PIL.Image object. 242 | boxes: a 2 dimensional numpy array of [N, 4]: (ymin, xmin, ymax, xmax). 243 | The coordinates are in normalized format between [0, 1]. 244 | color: color to draw bounding box. Default is red. 245 | thickness: line thickness. Default value is 4. 246 | display_str_list_list: list of list of strings. 247 | a list of strings for each bounding box. 248 | The reason to pass a list of strings for a 249 | bounding box is that it might contain 250 | multiple labels. 251 | 252 | Raises: 253 | ValueError: if boxes is not a [N, 4] array 254 | """ 255 | boxes_shape = boxes.shape 256 | if not boxes_shape: 257 | return 258 | if len(boxes_shape) != 2 or boxes_shape[1] != 4: 259 | raise ValueError('Input must be of size [N, 4]') 260 | for i in range(boxes_shape[0]): 261 | display_str_list = () 262 | if display_str_list_list: 263 | display_str_list = display_str_list_list[i] 264 | draw_bounding_box_on_image(image, boxes[i, 0], boxes[i, 1], boxes[i, 2], 265 | boxes[i, 3], color, thickness, display_str_list) 266 | 267 | 268 | def _visualize_boxes(image, boxes, classes, scores, category_index, **kwargs): 269 | return visualize_boxes_and_labels_on_image_array( 270 | image, boxes, classes, scores, category_index=category_index, **kwargs) 271 | 272 | 273 | def _visualize_boxes_and_masks(image, boxes, classes, scores, masks, 274 | category_index, **kwargs): 275 | return visualize_boxes_and_labels_on_image_array( 276 | image, 277 | boxes, 278 | classes, 279 | scores, 280 | category_index=category_index, 281 | instance_masks=masks, 282 | **kwargs) 283 | 284 | 285 | def _visualize_boxes_and_keypoints(image, boxes, classes, scores, keypoints, 286 | category_index, **kwargs): 287 | return visualize_boxes_and_labels_on_image_array( 288 | image, 289 | boxes, 290 | classes, 291 | scores, 292 | category_index=category_index, 293 | keypoints=keypoints, 294 | **kwargs) 295 | 296 | 297 | def _visualize_boxes_and_masks_and_keypoints( 298 | image, boxes, classes, scores, masks, keypoints, category_index, **kwargs): 299 | return visualize_boxes_and_labels_on_image_array( 300 | image, 301 | boxes, 302 | classes, 303 | scores, 304 | category_index=category_index, 305 | instance_masks=masks, 306 | keypoints=keypoints, 307 | **kwargs) 308 | 309 | 310 | def draw_bounding_boxes_on_image_tensors(images, 311 | boxes, 312 | classes, 313 | scores, 314 | category_index, 315 | instance_masks=None, 316 | keypoints=None, 317 | max_boxes_to_draw=20, 318 | min_score_thresh=0.2): 319 | """Draws bounding boxes, masks, and keypoints on batch of image tensors. 320 | 321 | Args: 322 | images: A 4D uint8 image tensor of shape [N, H, W, C]. 323 | boxes: [N, max_detections, 4] float32 tensor of detection boxes. 324 | classes: [N, max_detections] int tensor of detection classes. Note that 325 | classes are 1-indexed. 326 | scores: [N, max_detections] float32 tensor of detection scores. 327 | category_index: a dict that maps integer ids to category dicts. e.g. 328 | {1: {1: 'dog'}, 2: {2: 'cat'}, ...} 329 | instance_masks: A 4D uint8 tensor of shape [N, max_detection, H, W] with 330 | instance masks. 331 | keypoints: A 4D float32 tensor of shape [N, max_detection, num_keypoints, 2] 332 | with keypoints. 333 | max_boxes_to_draw: Maximum number of boxes to draw on an image. Default 20. 334 | min_score_thresh: Minimum score threshold for visualization. Default 0.2. 335 | 336 | Returns: 337 | 4D image tensor of type uint8, with boxes drawn on top. 338 | """ 339 | visualization_keyword_args = { 340 | 'use_normalized_coordinates': True, 341 | 'max_boxes_to_draw': max_boxes_to_draw, 342 | 'min_score_thresh': min_score_thresh, 343 | 'agnostic_mode': False, 344 | 'line_thickness': 4 345 | } 346 | 347 | if instance_masks is not None and keypoints is None: 348 | visualize_boxes_fn = functools.partial( 349 | _visualize_boxes_and_masks, 350 | category_index=category_index, 351 | **visualization_keyword_args) 352 | elems = [images, boxes, classes, scores, instance_masks] 353 | elif instance_masks is None and keypoints is not None: 354 | visualize_boxes_fn = functools.partial( 355 | _visualize_boxes_and_keypoints, 356 | category_index=category_index, 357 | **visualization_keyword_args) 358 | elems = [images, boxes, classes, scores, keypoints] 359 | elif instance_masks is not None and keypoints is not None: 360 | visualize_boxes_fn = functools.partial( 361 | _visualize_boxes_and_masks_and_keypoints, 362 | category_index=category_index, 363 | **visualization_keyword_args) 364 | elems = [images, boxes, classes, scores, instance_masks, keypoints] 365 | else: 366 | visualize_boxes_fn = functools.partial( 367 | _visualize_boxes, 368 | category_index=category_index, 369 | **visualization_keyword_args) 370 | elems = [images, boxes, classes, scores] 371 | 372 | def draw_boxes(image_and_detections): 373 | """Draws boxes on image.""" 374 | image_with_boxes = tf.py_func(visualize_boxes_fn, image_and_detections, 375 | tf.uint8) 376 | return image_with_boxes 377 | 378 | images = tf.map_fn(draw_boxes, elems, dtype=tf.uint8, back_prop=False) 379 | return images 380 | 381 | 382 | def draw_side_by_side_evaluation_image(eval_dict, 383 | category_index, 384 | max_boxes_to_draw=20, 385 | min_score_thresh=0.2): 386 | """Creates a side-by-side image with detections and groundtruth. 387 | 388 | Bounding boxes (and instance masks, if available) are visualized on both 389 | subimages. 390 | 391 | Args: 392 | eval_dict: The evaluation dictionary returned by 393 | eval_util.result_dict_for_single_example(). 394 | category_index: A category index (dictionary) produced from a labelmap. 395 | max_boxes_to_draw: The maximum number of boxes to draw for detections. 396 | min_score_thresh: The minimum score threshold for showing detections. 397 | 398 | Returns: 399 | A [1, H, 2 * W, C] uint8 tensor. The subimage on the left corresponds to 400 | detections, while the subimage on the right corresponds to groundtruth. 401 | """ 402 | detection_fields = fields.DetectionResultFields() 403 | input_data_fields = fields.InputDataFields() 404 | instance_masks = None 405 | if detection_fields.detection_masks in eval_dict: 406 | instance_masks = tf.cast( 407 | tf.expand_dims(eval_dict[detection_fields.detection_masks], axis=0), 408 | tf.uint8) 409 | keypoints = None 410 | if detection_fields.detection_keypoints in eval_dict: 411 | keypoints = tf.expand_dims( 412 | eval_dict[detection_fields.detection_keypoints], axis=0) 413 | groundtruth_instance_masks = None 414 | if input_data_fields.groundtruth_instance_masks in eval_dict: 415 | groundtruth_instance_masks = tf.cast( 416 | tf.expand_dims( 417 | eval_dict[input_data_fields.groundtruth_instance_masks], axis=0), 418 | tf.uint8) 419 | images_with_detections = draw_bounding_boxes_on_image_tensors( 420 | eval_dict[input_data_fields.original_image], 421 | tf.expand_dims(eval_dict[detection_fields.detection_boxes], axis=0), 422 | tf.expand_dims(eval_dict[detection_fields.detection_classes], axis=0), 423 | tf.expand_dims(eval_dict[detection_fields.detection_scores], axis=0), 424 | category_index, 425 | instance_masks=instance_masks, 426 | keypoints=keypoints, 427 | max_boxes_to_draw=max_boxes_to_draw, 428 | min_score_thresh=min_score_thresh) 429 | images_with_groundtruth = draw_bounding_boxes_on_image_tensors( 430 | eval_dict[input_data_fields.original_image], 431 | tf.expand_dims(eval_dict[input_data_fields.groundtruth_boxes], axis=0), 432 | tf.expand_dims(eval_dict[input_data_fields.groundtruth_classes], axis=0), 433 | tf.expand_dims( 434 | tf.ones_like( 435 | eval_dict[input_data_fields.groundtruth_classes], 436 | dtype=tf.float32), 437 | axis=0), 438 | category_index, 439 | instance_masks=groundtruth_instance_masks, 440 | keypoints=None, 441 | max_boxes_to_draw=None, 442 | min_score_thresh=0.0) 443 | return tf.concat([images_with_detections, images_with_groundtruth], axis=2) 444 | 445 | 446 | def draw_keypoints_on_image_array(image, 447 | keypoints, 448 | color='red', 449 | radius=2, 450 | use_normalized_coordinates=True): 451 | """Draws keypoints on an image (numpy array). 452 | 453 | Args: 454 | image: a numpy array with shape [height, width, 3]. 455 | keypoints: a numpy array with shape [num_keypoints, 2]. 456 | color: color to draw the keypoints with. Default is red. 457 | radius: keypoint radius. Default value is 2. 458 | use_normalized_coordinates: if True (default), treat keypoint values as 459 | relative to the image. Otherwise treat them as absolute. 460 | """ 461 | image_pil = Image.fromarray(np.uint8(image)).convert('RGB') 462 | draw_keypoints_on_image(image_pil, keypoints, color, radius, 463 | use_normalized_coordinates) 464 | np.copyto(image, np.array(image_pil)) 465 | 466 | 467 | def draw_keypoints_on_image(image, 468 | keypoints, 469 | color='red', 470 | radius=2, 471 | use_normalized_coordinates=True): 472 | """Draws keypoints on an image. 473 | 474 | Args: 475 | image: a PIL.Image object. 476 | keypoints: a numpy array with shape [num_keypoints, 2]. 477 | color: color to draw the keypoints with. Default is red. 478 | radius: keypoint radius. Default value is 2. 479 | use_normalized_coordinates: if True (default), treat keypoint values as 480 | relative to the image. Otherwise treat them as absolute. 481 | """ 482 | draw = ImageDraw.Draw(image) 483 | im_width, im_height = image.size 484 | keypoints_x = [k[1] for k in keypoints] 485 | keypoints_y = [k[0] for k in keypoints] 486 | if use_normalized_coordinates: 487 | keypoints_x = tuple([im_width * x for x in keypoints_x]) 488 | keypoints_y = tuple([im_height * y for y in keypoints_y]) 489 | for keypoint_x, keypoint_y in zip(keypoints_x, keypoints_y): 490 | draw.ellipse([(keypoint_x - radius, keypoint_y - radius), 491 | (keypoint_x + radius, keypoint_y + radius)], 492 | outline=color, fill=color) 493 | 494 | 495 | def draw_mask_on_image_array(image, mask, color='red', alpha=0.4): 496 | """Draws mask on an image. 497 | 498 | Args: 499 | image: uint8 numpy array with shape (img_height, img_height, 3) 500 | mask: a uint8 numpy array of shape (img_height, img_height) with 501 | values between either 0 or 1. 502 | color: color to draw the keypoints with. Default is red. 503 | alpha: transparency value between 0 and 1. (default: 0.4) 504 | 505 | Raises: 506 | ValueError: On incorrect data type for image or masks. 507 | """ 508 | if image.dtype != np.uint8: 509 | raise ValueError('`image` not of type np.uint8') 510 | if mask.dtype != np.uint8: 511 | raise ValueError('`mask` not of type np.uint8') 512 | if np.any(np.logical_and(mask != 1, mask != 0)): 513 | raise ValueError('`mask` elements should be in [0, 1]') 514 | if image.shape[:2] != mask.shape: 515 | raise ValueError('The image has spatial dimensions %s but the mask has ' 516 | 'dimensions %s' % (image.shape[:2], mask.shape)) 517 | rgb = ImageColor.getrgb(color) 518 | pil_image = Image.fromarray(image) 519 | 520 | solid_color = np.expand_dims( 521 | np.ones_like(mask), axis=2) * np.reshape(list(rgb), [1, 1, 3]) 522 | pil_solid_color = Image.fromarray(np.uint8(solid_color)).convert('RGBA') 523 | pil_mask = Image.fromarray(np.uint8(255.0*alpha*mask)).convert('L') 524 | pil_image = Image.composite(pil_solid_color, pil_image, pil_mask) 525 | np.copyto(image, np.array(pil_image.convert('RGB'))) 526 | 527 | 528 | def visualize_boxes_and_labels_on_image_array( 529 | image, 530 | boxes, 531 | classes, 532 | scores, 533 | category_index, 534 | instance_masks=None, 535 | instance_boundaries=None, 536 | keypoints=None, 537 | use_normalized_coordinates=False, 538 | max_boxes_to_draw=20, 539 | min_score_thresh=.5, 540 | agnostic_mode=False, 541 | line_thickness=4, 542 | groundtruth_box_visualization_color='black', 543 | skip_scores=False, 544 | skip_labels=False): 545 | """Overlay labeled boxes on an image with formatted scores and label names. 546 | 547 | This function groups boxes that correspond to the same location 548 | and creates a display string for each detection and overlays these 549 | on the image. Note that this function modifies the image in place, and returns 550 | that same image. 551 | 552 | Args: 553 | image: uint8 numpy array with shape (img_height, img_width, 3) 554 | boxes: a numpy array of shape [N, 4] 555 | classes: a numpy array of shape [N]. Note that class indices are 1-based, 556 | and match the keys in the label map. 557 | scores: a numpy array of shape [N] or None. If scores=None, then 558 | this function assumes that the boxes to be plotted are groundtruth 559 | boxes and plot all boxes as black with no classes or scores. 560 | category_index: a dict containing category dictionaries (each holding 561 | category index `id` and category name `name`) keyed by category indices. 562 | instance_masks: a numpy array of shape [N, image_height, image_width] with 563 | values ranging between 0 and 1, can be None. 564 | instance_boundaries: a numpy array of shape [N, image_height, image_width] 565 | with values ranging between 0 and 1, can be None. 566 | keypoints: a numpy array of shape [N, num_keypoints, 2], can 567 | be None 568 | use_normalized_coordinates: whether boxes is to be interpreted as 569 | normalized coordinates or not. 570 | max_boxes_to_draw: maximum number of boxes to visualize. If None, draw 571 | all boxes. 572 | min_score_thresh: minimum score threshold for a box to be visualized 573 | agnostic_mode: boolean (default: False) controlling whether to evaluate in 574 | class-agnostic mode or not. This mode will display scores but ignore 575 | classes. 576 | line_thickness: integer (default: 4) controlling line width of the boxes. 577 | groundtruth_box_visualization_color: box color for visualizing groundtruth 578 | boxes 579 | skip_scores: whether to skip score when drawing a single detection 580 | skip_labels: whether to skip label when drawing a single detection 581 | 582 | Returns: 583 | uint8 numpy array with shape (img_height, img_width, 3) with overlaid boxes. 584 | """ 585 | # Create a display string (and color) for every box location, group any boxes 586 | # that correspond to the same location. 587 | box_to_display_str_map = collections.defaultdict(list) 588 | box_to_color_map = collections.defaultdict(str) 589 | box_to_instance_masks_map = {} 590 | box_to_instance_boundaries_map = {} 591 | box_to_keypoints_map = collections.defaultdict(list) 592 | if not max_boxes_to_draw: 593 | max_boxes_to_draw = boxes.shape[0] 594 | for i in range(min(max_boxes_to_draw, boxes.shape[0])): 595 | if scores is None or scores[i] > min_score_thresh: 596 | box = tuple(boxes[i].tolist()) 597 | if instance_masks is not None: 598 | box_to_instance_masks_map[box] = instance_masks[i] 599 | if instance_boundaries is not None: 600 | box_to_instance_boundaries_map[box] = instance_boundaries[i] 601 | if keypoints is not None: 602 | box_to_keypoints_map[box].extend(keypoints[i]) 603 | if scores is None: 604 | box_to_color_map[box] = groundtruth_box_visualization_color 605 | else: 606 | display_str = '' 607 | if not skip_labels: 608 | if not agnostic_mode: 609 | if classes[i] in category_index.keys(): 610 | class_name = category_index[classes[i]]['name'] 611 | else: 612 | class_name = 'N/A' 613 | display_str = str(class_name) 614 | if not skip_scores: 615 | if not display_str: 616 | display_str = '{}%'.format(int(100*scores[i])) 617 | else: 618 | display_str = '{}: {}%'.format(display_str, int(100*scores[i])) 619 | box_to_display_str_map[box].append(display_str) 620 | if agnostic_mode: 621 | box_to_color_map[box] = 'DarkOrange' 622 | else: 623 | box_to_color_map[box] = STANDARD_COLORS[ 624 | classes[i] % len(STANDARD_COLORS)] 625 | 626 | # Draw all boxes onto image. 627 | for box, color in box_to_color_map.items(): 628 | ymin, xmin, ymax, xmax = box 629 | if instance_masks is not None: 630 | draw_mask_on_image_array( 631 | image, 632 | box_to_instance_masks_map[box], 633 | color=color 634 | ) 635 | if instance_boundaries is not None: 636 | draw_mask_on_image_array( 637 | image, 638 | box_to_instance_boundaries_map[box], 639 | color='red', 640 | alpha=1.0 641 | ) 642 | draw_bounding_box_on_image_array( 643 | image, 644 | ymin, 645 | xmin, 646 | ymax, 647 | xmax, 648 | color=color, 649 | thickness=line_thickness, 650 | display_str_list=box_to_display_str_map[box], 651 | use_normalized_coordinates=use_normalized_coordinates) 652 | if keypoints is not None: 653 | draw_keypoints_on_image_array( 654 | image, 655 | box_to_keypoints_map[box], 656 | color=color, 657 | radius=line_thickness / 2, 658 | use_normalized_coordinates=use_normalized_coordinates) 659 | 660 | return image 661 | 662 | 663 | def add_cdf_image_summary(values, name): 664 | """Adds a tf.summary.image for a CDF plot of the values. 665 | 666 | Normalizes `values` such that they sum to 1, plots the cumulative distribution 667 | function and creates a tf image summary. 668 | 669 | Args: 670 | values: a 1-D float32 tensor containing the values. 671 | name: name for the image summary. 672 | """ 673 | def cdf_plot(values): 674 | """Numpy function to plot CDF.""" 675 | normalized_values = values / np.sum(values) 676 | sorted_values = np.sort(normalized_values) 677 | cumulative_values = np.cumsum(sorted_values) 678 | fraction_of_examples = (np.arange(cumulative_values.size, dtype=np.float32) 679 | / cumulative_values.size) 680 | fig = plt.figure(frameon=False) 681 | ax = fig.add_subplot('111') 682 | ax.plot(fraction_of_examples, cumulative_values) 683 | ax.set_ylabel('cumulative normalized values') 684 | ax.set_xlabel('fraction of examples') 685 | fig.canvas.draw() 686 | width, height = fig.get_size_inches() * fig.get_dpi() 687 | image = np.fromstring(fig.canvas.tostring_rgb(), dtype='uint8').reshape( 688 | 1, int(height), int(width), 3) 689 | return image 690 | cdf_plot = tf.py_func(cdf_plot, [values], tf.uint8) 691 | tf.summary.image(name, cdf_plot) 692 | --------------------------------------------------------------------------------