├── ComparisonWithDOUBANGO.xls ├── FSRCNN_x4.pb ├── GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py ├── GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR_V1.py ├── LicensePlateYolov8Train.py ├── README.md ├── Test.zip ├── TestWilburImage.py ├── Traffic IP Camera video.mp4 ├── VIDEOGetNumberInternationalLicensePlate_RoboflowModel_Filters_PaddleOCR_Demonstration.py ├── VIDEOGetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py ├── WilburImage.jpg ├── best.pt ├── demonstration1.mp4 ├── demonstration2.mp4 ├── licence_data.yaml ├── roboflow.zip ├── test6Training.zip └── yolov8s.pt /ComparisonWithDOUBANGO.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/ComparisonWithDOUBANGO.xls -------------------------------------------------------------------------------- /FSRCNN_x4.pb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/FSRCNN_x4.pb -------------------------------------------------------------------------------- /GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Apr 25 20:1 7:29 2022 4 | 5 | @author: Alfonso Blanco 6 | """ 7 | ###################################################################### 8 | # PARAMETERS 9 | ##################################################################### 10 | dir="" 11 | 12 | dirname= "test6Training\\images" 13 | #dirname= "archiveLabeled" 14 | #dirname= "C:\\Malos\\images" 15 | #dirname= "roboflow\\test\\images" 16 | 17 | dirnameYolo="runs\\detect\\train9\\weights\\best.pt" 18 | # https://docs.ultralytics.com/python/ 19 | from ultralytics import YOLO 20 | model = YOLO(dirnameYolo) 21 | class_list = model.model.names 22 | #print(class_list) 23 | 24 | 25 | ###################################################################### 26 | from paddleocr import PaddleOCR 27 | # Paddleocr supports Chinese, English, French, German, Korean and Japanese. 28 | # You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan` 29 | # to switch the language model in order. 30 | # https://pypi.org/project/paddleocr/ 31 | # 32 | # supress anoysing logging messages parameter show_log = False 33 | # https://github.com/PaddlePaddle/PaddleOCR/issues/2348 34 | ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log = False) # need to run only once to download and load model into memory 35 | 36 | import numpy as np 37 | 38 | import cv2 39 | 40 | X_resize=220 41 | Y_resize=70 42 | 43 | import os 44 | import re 45 | 46 | import imutils 47 | 48 | TabTotHitsFilter=[] 49 | TabTotFailuresFilter=[] 50 | 51 | for j in range(60): 52 | TabTotHitsFilter.append(0) 53 | TabTotFailuresFilter.append(0) 54 | ##################################################################### 55 | """ 56 | Copied from https://gist.github.com/endolith/334196bac1cac45a4893# 57 | 58 | other source: 59 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 60 | """ 61 | 62 | from skimage.transform import radon 63 | 64 | import numpy 65 | from numpy import mean, array, blackman, sqrt, square 66 | from numpy.fft import rfft 67 | 68 | try: 69 | # More accurate peak finding from 70 | # https://gist.github.com/endolith/255291#file-parabolic-py 71 | from parabolic import parabolic 72 | 73 | def argmax(x): 74 | return parabolic(x, numpy.argmax(x))[0] 75 | except ImportError: 76 | from numpy import argmax 77 | 78 | 79 | def GetRotationImage(image): 80 | 81 | 82 | I=image 83 | I = I - mean(I) # Demean; make the brightness extend above and below zero 84 | 85 | 86 | # Do the radon transform and display the result 87 | sinogram = radon(I) 88 | 89 | 90 | # Find the RMS value of each row and find "busiest" rotation, 91 | # where the transform is lined up perfectly with the alternating dark 92 | # text and white lines 93 | 94 | # rms_flat does no exist in recent versions 95 | #r = array([mlab.rms_flat(line) for line in sinogram.transpose()]) 96 | r = array([sqrt(mean(square(line))) for line in sinogram.transpose()]) 97 | rotation = argmax(r) 98 | #print('Rotation: {:.2f} degrees'.format(90 - rotation)) 99 | #plt.axhline(rotation, color='r') 100 | 101 | # Plot the busy row 102 | row = sinogram[:, rotation] 103 | N = len(row) 104 | 105 | # Take spectrum of busy row and find line spacing 106 | window = blackman(N) 107 | spectrum = rfft(row * window) 108 | 109 | frequency = argmax(abs(spectrum)) 110 | 111 | return rotation, spectrum, frequency 112 | 113 | ##################################################################### 114 | def ThresholdStable(image): 115 | # -*- coding: utf-8 -*- 116 | """ 117 | Created on Fri Aug 12 21:04:48 2022 118 | Author: Alfonso Blanco García 119 | 120 | Looks for the threshold whose variations keep the image STABLE 121 | (there are only small variations with the image of the previous 122 | threshold). 123 | Similar to the method followed in cv2.MSER 124 | https://datasmarts.net/es/como-usar-el-detector-de-puntos-clave-mser-en-opencv/https://felipemeganha.medium.com/detecting-handwriting-regions-with-opencv-and-python-ff0b1050aa4e 125 | """ 126 | 127 | thresholds=[] 128 | Repes=[] 129 | Difes=[] 130 | 131 | gray=image 132 | grayAnt=gray 133 | 134 | ContRepe=0 135 | threshold=0 136 | for i in range (255): 137 | 138 | ret, gray1=cv2.threshold(gray,i,255, cv2.THRESH_BINARY) 139 | Dife1 = grayAnt - gray1 140 | Dife2=np.sum(Dife1) 141 | if Dife2 < 0: Dife2=Dife2*-1 142 | Difes.append(Dife2) 143 | if Dife2<22000: # Case only image of license plate 144 | #if Dife2<60000: 145 | ContRepe=ContRepe+1 146 | 147 | threshold=i 148 | grayAnt=gray1 149 | continue 150 | if ContRepe > 0: 151 | 152 | thresholds.append(threshold) 153 | Repes.append(ContRepe) 154 | ContRepe=0 155 | grayAnt=gray1 156 | thresholdMax=0 157 | RepesMax=0 158 | for i in range(len(thresholds)): 159 | #print ("Threshold = " + str(thresholds[i])+ " Repeticiones = " +str(Repes[i])) 160 | if Repes[i] > RepesMax: 161 | RepesMax=Repes[i] 162 | thresholdMax=thresholds[i] 163 | 164 | #print(min(Difes)) 165 | #print ("Threshold Resultado= " + str(thresholdMax)+ " Repeticiones = " +str(RepesMax)) 166 | return thresholdMax 167 | 168 | 169 | 170 | # Copied from https://learnopencv.com/otsu-thresholding-with-opencv/ 171 | def OTSU_Threshold(image): 172 | # Set total number of bins in the histogram 173 | 174 | bins_num = 256 175 | 176 | # Get the image histogram 177 | 178 | hist, bin_edges = np.histogram(image, bins=bins_num) 179 | 180 | # Get normalized histogram if it is required 181 | 182 | #if is_normalized: 183 | 184 | hist = np.divide(hist.ravel(), hist.max()) 185 | 186 | 187 | 188 | # Calculate centers of bins 189 | 190 | bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2. 191 | 192 | 193 | # Iterate over all thresholds (indices) and get the probabilities w1(t), w2(t) 194 | 195 | weight1 = np.cumsum(hist) 196 | 197 | weight2 = np.cumsum(hist[::-1])[::-1] 198 | 199 | # Get the class means mu0(t) 200 | 201 | mean1 = np.cumsum(hist * bin_mids) / weight1 202 | 203 | # Get the class means mu1(t) 204 | 205 | mean2 = (np.cumsum((hist * bin_mids)[::-1]) / weight2[::-1])[::-1] 206 | 207 | inter_class_variance = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2 208 | 209 | # Maximize the inter_class_variance function val 210 | 211 | index_of_max_val = np.argmax(inter_class_variance) 212 | 213 | threshold = bin_mids[:-1][index_of_max_val] 214 | 215 | #print("Otsu's algorithm implementation thresholding result: ", threshold) 216 | return threshold 217 | 218 | ######################################################################### 219 | def ApplyCLAHE(gray): 220 | #https://towardsdatascience.com/image-enhancement-techniques-using-opencv-and-python-9191d5c30d45 221 | 222 | gray_img_eqhist=cv2.equalizeHist(gray) 223 | hist=cv2.calcHist(gray_img_eqhist,[0],None,[256],[0,256]) 224 | clahe=cv2.createCLAHE(clipLimit=200,tileGridSize=(3,3)) 225 | gray_img_clahe=clahe.apply(gray_img_eqhist) 226 | return gray_img_clahe 227 | 228 | 229 | def GetPaddleOcr(img): 230 | 231 | """ 232 | Created on Tue Mar 7 10:31:09 2023 233 | 234 | @author: https://pypi.org/project/paddleocr/ (adapted from) 235 | """ 236 | 237 | cv2.imwrite("gray.jpg",img) 238 | img_path = 'gray.jpg' 239 | result = ocr.ocr(img_path, cls=True) 240 | for idx in range(len(result)): 241 | res = result[idx] 242 | #for line in res: 243 | #print(line) 244 | 245 | # draw result 246 | from PIL import Image 247 | result = result[0] 248 | image = Image.open(img_path).convert('RGB') 249 | boxes = [line[0] for line in result] 250 | 251 | txts = [line[1][0] for line in result] 252 | scores = [line[1][1] for line in result] 253 | 254 | licensePlate= "" 255 | accuracy=0.0 256 | #print("RESULTADO "+ str(txts)) 257 | #print("confiabilidad "+ str(scores)) 258 | if len(txts) > 0: 259 | licensePlate= txts[0] 260 | accuracy=float(scores[0]) 261 | #print(licensePlate) 262 | #print(accuracy) 263 | 264 | return licensePlate, accuracy 265 | ######################################################################### 266 | def FindLicenseNumber (gray, x_offset, y_offset, License, x_resize, y_resize, \ 267 | Resize_xfactor, Resize_yfactor, BilateralOption): 268 | ######################################################################### 269 | 270 | 271 | gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) 272 | 273 | TotHits=0 274 | 275 | X_resize=x_resize 276 | Y_resize=y_resize 277 | 278 | 279 | gray=cv2.resize(gray,None,fx=Resize_xfactor,fy=Resize_yfactor,interpolation=cv2.INTER_CUBIC) 280 | 281 | gray = cv2.resize(gray, (X_resize,Y_resize), interpolation = cv2.INTER_AREA) 282 | 283 | rotation, spectrum, frquency =GetRotationImage(gray) 284 | rotation=90 - rotation 285 | #print("Car" + str(NumberImageOrder) + " Brillo : " +str(SumBrightnessLic) + 286 | # " Desviacion : " + str(DesvLic)) 287 | if (rotation > 0 and rotation < 30) or (rotation < 0 and rotation > -30): 288 | print(License + " rotate "+ str(rotation)) 289 | gray=imutils.rotate(gray,angle=rotation) 290 | 291 | 292 | 293 | 294 | TabLicensesFounded=[] 295 | ContLicensesFounded=[] 296 | 297 | 298 | X_resize=x_resize 299 | Y_resize=y_resize 300 | print("gray.shape " + str(gray.shape)) 301 | Resize_xfactor=1.5 302 | Resize_yfactor=1.5 303 | 304 | 305 | TabLicensesFounded=[] 306 | ContLicensesFounded=[] 307 | 308 | TotHits=0 309 | 310 | # https://medium.com/practical-data-science-and-engineering/image-kernels-88162cb6585d 311 | 312 | kernel = np.array([[0, -1, 0], 313 | [-1,10, -1], 314 | [0, -1, 0]]) 315 | dst = cv2.filter2D(gray, -1, kernel) 316 | img_concat = cv2.hconcat([gray, dst]) 317 | text, Accuraccy = GetPaddleOcr(img_concat) 318 | text = ''.join(char for char in text if char.isalnum()) 319 | text=ProcessText(text) 320 | if ProcessText(text) != "": 321 | 322 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 323 | if text==License: 324 | print(text + " Hit with image concat ") 325 | TotHits=TotHits+1 326 | else: 327 | print(License + " detected with Filter image concat "+ text) 328 | 329 | 330 | 331 | kernel = np.ones((3,3),np.float32)/90 332 | gray1 = cv2.filter2D(gray,-1,kernel) 333 | #gray_clahe = cv2.GaussianBlur(gray, (5, 5), 0) 334 | gray_img_clahe=ApplyCLAHE(gray1) 335 | 336 | th=OTSU_Threshold(gray_img_clahe) 337 | max_val=255 338 | 339 | ret, o3 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO) 340 | text, Accuraccy = GetPaddleOcr(o3) 341 | 342 | text = ''.join(char for char in text if char.isalnum()) 343 | text=ProcessText(text) 344 | if ProcessText(text) != "": 345 | 346 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 347 | if text==License: 348 | print(text + " Hit with CLAHE and THRESH_TOZERO" ) 349 | #TotHits=TotHits+1 350 | else: 351 | print(License + " detected with CLAHE and THRESH_TOZERO as "+ text) 352 | 353 | 354 | 355 | for z in range(5,6): 356 | 357 | kernel = np.array([[0,-1,0], [-1,z,-1], [0,-1,0]]) 358 | gray1 = cv2.filter2D(gray, -1, kernel) 359 | 360 | text, Accuraccy = GetPaddleOcr(gray1) 361 | 362 | text = ''.join(char for char in text if char.isalnum()) 363 | text=ProcessText(text) 364 | if ProcessText(text) != "": 365 | 366 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 367 | if text==License: 368 | print(text + " Hit with Sharpen filter z= " +str(z)) 369 | TotHits=TotHits+1 370 | else: 371 | print(License + " detected with Sharpen filter z= " +str(z) + " as "+ text) 372 | 373 | 374 | gray_img_clahe=ApplyCLAHE(gray) 375 | 376 | th=OTSU_Threshold(gray_img_clahe) 377 | max_val=255 378 | 379 | # Otsu's thresholding 380 | ret2,gray1 = cv2.threshold(gray,0,255,cv2.THRESH_TRUNC+cv2.THRESH_OTSU) 381 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 382 | text, Accuraccy = GetPaddleOcr(gray1) 383 | 384 | text = ''.join(char for char in text if char.isalnum()) 385 | 386 | text=ProcessText(text) 387 | if ProcessText(text) != "": 388 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 389 | if text==Licenses[i]: 390 | print(text + " Hit with Otsu's thresholding of cv2 and THRESH_TRUNC" ) 391 | TotHits=TotHits+1 392 | else: 393 | print(Licenses[i] + " detected with Otsu's thresholding of cv2 and THRESH_TRUNC as "+ text) 394 | 395 | 396 | threshold=ThresholdStable(gray) 397 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TRUNC) 398 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 399 | text, Accuraccy = GetPaddleOcr(gray1) 400 | 401 | text = ''.join(char for char in text if char.isalnum()) 402 | text=ProcessText(text) 403 | if ProcessText(text) != "": 404 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 405 | if text==Licenses[i]: 406 | print(text + " Hit with Stable and THRESH_TRUNC" ) 407 | TotHits=TotHits+1 408 | else: 409 | print(Licenses[i] + " detected with Stable and THRESH_TRUNC as "+ text) 410 | 411 | 412 | #################################################### 413 | # experimental formula based on the brightness 414 | # of the whole image 415 | #################################################### 416 | 417 | SumBrightness=np.sum(gray) 418 | threshold=(SumBrightness/177600.00) 419 | 420 | ##################################################### 421 | 422 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TOZERO) 423 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 424 | text, Accuraccy = GetPaddleOcr(gray1) 425 | 426 | text = ''.join(char for char in text if char.isalnum()) 427 | 428 | text=ProcessText(text) 429 | if ProcessText(text) != "": 430 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 431 | if text==Licenses[i]: 432 | print(text + " Hit with Brightness and THRESH_TOZERO" ) 433 | TotHits=TotHits+1 434 | else: 435 | print(Licenses[i] + " detected with Brightness and THRESH_TOZERO as "+ text) 436 | 437 | 438 | ################################################################ 439 | return TabLicensesFounded, ContLicensesFounded 440 | 441 | ######################################################################## 442 | def loadimagesRoboflow (dirname): 443 | ######################################################################### 444 | # adapted from: 445 | # https://www.aprendemachinelearning.com/clasificacion-de-imagenes-en-python/ 446 | # by Alfonso Blanco García 447 | ######################################################################## 448 | imgpath = dirname + "\\" 449 | 450 | images = [] 451 | Licenses=[] 452 | 453 | print("Reading imagenes from ",imgpath) 454 | NumImage=-2 455 | 456 | Cont=0 457 | for root, dirnames, filenames in os.walk(imgpath): 458 | 459 | NumImage=NumImage+1 460 | 461 | for filename in filenames: 462 | 463 | if re.search("\.(jpg|jpeg|png|bmp|tiff)$", filename): 464 | 465 | 466 | filepath = os.path.join(root, filename) 467 | License=filename[:len(filename)-4] 468 | 469 | image = cv2.imread(filepath) 470 | # Roboflow images are (416,416) 471 | #image=cv2.resize(image,(416,416)) 472 | # kaggle images 473 | #image=cv2.resize(image, (640,640)) 474 | 475 | images.append(image) 476 | Licenses.append(License) 477 | 478 | Cont+=1 479 | 480 | return images, Licenses 481 | 482 | def Detect_International_LicensePlate(Text): 483 | if len(Text) < 3 : return -1 484 | for i in range(len(Text)): 485 | if (Text[i] >= "0" and Text[i] <= "9" ) or (Text[i] >= "A" and Text[i] <= "Z" ): 486 | continue 487 | else: 488 | return -1 489 | 490 | return 1 491 | 492 | def ProcessText(text): 493 | 494 | if len(text) > 10: 495 | text=text[len(text)-10] 496 | if len(text) > 9: 497 | text=text[len(text)-9] 498 | else: 499 | if len(text) > 8: 500 | text=text[len(text)-8] 501 | else: 502 | 503 | if len(text) > 7: 504 | text=text[len(text)-7:] 505 | if Detect_International_LicensePlate(text)== -1: 506 | return "" 507 | else: 508 | return text 509 | 510 | def ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text): 511 | 512 | SwFounded=0 513 | for i in range( len(TabLicensesFounded)): 514 | if text==TabLicensesFounded[i]: 515 | ContLicensesFounded[i]=ContLicensesFounded[i]+1 516 | SwFounded=1 517 | break 518 | if SwFounded==0: 519 | TabLicensesFounded.append(text) 520 | ContLicensesFounded.append(1) 521 | return TabLicensesFounded, ContLicensesFounded 522 | 523 | 524 | # ttps://medium.chom/@chanon.krittapholchai/build-object-detection-gui-with-yolov8-and-pysimplegui-76d5f5464d6c 525 | def DetectLicenseWithYolov8 (img): 526 | 527 | TabcropLicense=[] 528 | results = model.predict(img) 529 | 530 | result=results[0] 531 | 532 | xyxy= result.boxes.xyxy.numpy() 533 | confidence= result.boxes.conf.numpy() 534 | class_id= result.boxes.cls.numpy().astype(int) 535 | # Get Class name 536 | class_name = [class_list[x] for x in class_id] 537 | # Pack together for easy use 538 | sum_output = list(zip(class_name, confidence,xyxy)) 539 | # Copy image, in case that we need original image for something 540 | out_image = img.copy() 541 | for run_output in sum_output : 542 | # Unpack 543 | #print(class_name) 544 | label, con, box = run_output 545 | if label == "vehicle":continue 546 | cropLicense=out_image[int(box[1]):int(box[3]),int(box[0]):int(box[2])] 547 | #cv2.imshow("Crop", cropLicense) 548 | #cv2.waitKey(0) 549 | TabcropLicense.append(cropLicense) 550 | return TabcropLicense 551 | 552 | 553 | ########################################################### 554 | # MAIN 555 | ########################################################## 556 | 557 | imagesComplete, Licenses=loadimagesRoboflow(dirname) 558 | 559 | print("Number of imagenes : " + str(len(imagesComplete))) 560 | 561 | print("Number of licenses : " + str(len(Licenses))) 562 | 563 | ContDetected=0 564 | ContNoDetected=0 565 | TotHits=0 566 | TotFailures=0 567 | with open( "LicenseResults.txt" ,"w") as w: 568 | for i in range (len(imagesComplete)): 569 | 570 | gray=imagesComplete[i] 571 | 572 | License=Licenses[i] 573 | 574 | TabImgSelect =DetectLicenseWithYolov8(gray) 575 | if TabImgSelect==[]: 576 | print(License + " NON DETECTED") 577 | ContNoDetected=ContNoDetected+1 578 | continue 579 | else: 580 | ContDetected=ContDetected+1 581 | print(License + " DETECTED ") 582 | 583 | gray=TabImgSelect[0] 584 | 585 | x_off=3 586 | y_off=2 587 | 588 | x_resize=220 589 | y_resize=70 590 | 591 | Resize_xfactor=1.78 592 | Resize_yfactor=1.78 593 | 594 | ContLoop=0 595 | 596 | SwFounded=0 597 | 598 | BilateralOption=0 599 | 600 | TabLicensesFounded, ContLicensesFounded= FindLicenseNumber (gray, x_off, y_off, License, x_resize, y_resize, \ 601 | Resize_xfactor, Resize_yfactor, BilateralOption) 602 | 603 | 604 | print(TabLicensesFounded) 605 | print(ContLicensesFounded) 606 | 607 | ymax=-1 608 | contmax=0 609 | licensemax="" 610 | 611 | for y in range(len(TabLicensesFounded)): 612 | if ContLicensesFounded[y] > contmax: 613 | contmax=ContLicensesFounded[y] 614 | licensemax=TabLicensesFounded[y] 615 | 616 | if licensemax == License: 617 | print(License + " correctly recognized") 618 | TotHits+=1 619 | else: 620 | print(License + " Detected but not correctly recognized") 621 | TotFailures +=1 622 | print ("") 623 | lineaw=[] 624 | lineaw.append(License) 625 | lineaw.append(licensemax) 626 | lineaWrite =','.join(lineaw) 627 | lineaWrite=lineaWrite + "\n" 628 | w.write(lineaWrite) 629 | 630 | print("") 631 | print("Total Hits = " + str(TotHits ) + " from " + str(len(imagesComplete)) + " images readed") 632 | 633 | print("") 634 | -------------------------------------------------------------------------------- /GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR_V1.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Apr 25 20:1 7:29 2022 4 | 5 | @author: Alfonso Blanco 6 | """ 7 | ####################################################################### 8 | # PARAMETERS 9 | ###################################################################### 10 | dir="" 11 | 12 | #dirname= "test6Training\\images" 13 | #dirname= "archiveLabeled" 14 | #dirname= "C:\\Malos\\images" 15 | dirname= "Test" 16 | #dirname= "roboflow\\test\\images" 17 | #https://github.com/mrzaizai2k/VIETNAMESE_LICENSE_PLATE/tree/master/data/image 18 | #dirname="C:\\PruebasGithub\\License-Plate-Recognition-YOLOv7-and-CNN-main\\License-Plate-Recognition-YOLOv7-and-CNN-main\\data\\test\\images" 19 | #dirname="C:\\PruebasGithub\\LicensePlateDetector-master\\LicensePlateDetector-master\\output" 20 | #dirname="C:\\PruebasGithub\\detectron2-licenseplates-master\\detectron2-licenseplates-master\\datasets\\licenseplates\\images" 21 | 22 | 23 | import cv2 24 | 25 | # suggested by Wilbur 26 | # https://github.com/Saafke/FSRCNN_Tensorflow/tree/master/models 27 | # https://learnopencv.com/super-resolution-in-opencv/#sec5 28 | # https://learnopencv.com/super-resolution-in-opencv/ 29 | ocv_model = cv2.dnn_superres.DnnSuperResImpl_create() 30 | ocv_weight = 'FSRCNN_x4.pb' 31 | ocv_model.readModel(ocv_weight) 32 | ocv_model.setModel('fsrcnn', 4) 33 | 34 | 35 | import time 36 | Ini=time.time() 37 | 38 | 39 | dirnameYolo="runs\\detect\\train9\\weights\\best.pt" 40 | # https://docs.ultralytics.com/python/ 41 | from ultralytics import YOLO 42 | model = YOLO(dirnameYolo) 43 | class_list = model.model.names 44 | #print(class_list) 45 | 46 | 47 | ###################################################################### 48 | from paddleocr import PaddleOCR 49 | # Paddleocr supports Chinese, English, French, German, Korean and Japanese. 50 | # You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan` 51 | # to switch the language model in order. 52 | # https://pypi.org/project/paddleocr/ 53 | # 54 | # supress anoysing logging messages parameter show_log = False 55 | # https://github.com/PaddlePaddle/PaddleOCR/issues/2348 56 | ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log = False) # need to run only once to download and load model into memory 57 | 58 | import numpy as np 59 | 60 | X_resize=220 61 | Y_resize=70 62 | 63 | import os 64 | import re 65 | 66 | import imutils 67 | 68 | from scipy.signal import convolve2d 69 | 70 | # Control filters accuracy 71 | TabTotHitsFilter=[] 72 | TabTotFailuresFilter=[] 73 | 74 | for j in range(60): 75 | TabTotHitsFilter.append(0) 76 | TabTotFailuresFilter.append(0) 77 | ##################################################################### 78 | """ 79 | Copied from https://gist.github.com/endolith/334196bac1cac45a4893# 80 | 81 | other source: 82 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 83 | """ 84 | 85 | from skimage.transform import radon 86 | 87 | import numpy 88 | from numpy import mean, array, blackman, sqrt, square 89 | from numpy.fft import rfft 90 | 91 | 92 | # https://gist.github.com/fubel/ad01878c5a08a57be9b8b80605ad1247 93 | # comment from nafe93 modified by Alfonso Blanco 94 | def Otra_discrete_radon_transform(img): 95 | #steps= img.shape[1] 96 | steps= 90 97 | import skimage 98 | # shape 99 | h, w = img.shape 100 | zero = np.zeros((h, steps), dtype='float64') 101 | # sum and roatate 102 | for s in range(steps): 103 | #for s in range(300): 104 | #if s > img.shape[0] -1: break 105 | #rotation = skimage.transform.rotate(img, s, reshape=False).astype('float64') 106 | rotation = skimage.transform.rotate(img, s).astype('float64') 107 | # sum 108 | #zero[:, s] = np.sum(rotation, axis=0) 109 | zero[:, s] = np.sum(rotation) 110 | # rotate image 111 | #zero = skimage.transform.rotate(zero, 180).astype('float64') 112 | print(zero.shape) 113 | return zero 114 | 115 | 116 | 117 | try: 118 | # More accurate peak finding from 119 | # https://gist.github.com/endolith/255291#file-parabolic-py 120 | from parabolic import parabolic 121 | 122 | def argmax(x): 123 | return parabolic(x, numpy.argmax(x))[0] 124 | except ImportError: 125 | from numpy import argmax 126 | 127 | 128 | def GetRotationImage(image): 129 | 130 | 131 | I=image 132 | I = I - mean(I) # Demean; make the brightness extend above and below zero 133 | 134 | 135 | # Do the radon transform and display the result 136 | sinogram = radon(I) 137 | # a radon from scratch less efficient than radom 138 | #sinogram=Otra_discrete_radon_transform(I) 139 | 140 | 141 | # Find the RMS value of each row and find "busiest" rotation, 142 | # where the transform is lined up perfectly with the alternating dark 143 | # text and white lines 144 | 145 | # rms_flat does no exist in recent versions 146 | #r = array([mlab.rms_flat(line) for line in sinogram.transpose()]) 147 | r = array([sqrt(mean(square(line))) for line in sinogram.transpose()]) 148 | rotation = argmax(r) 149 | #print('Rotation: {:.2f} degrees'.format(90 - rotation)) 150 | #plt.axhline(rotation, color='r') 151 | 152 | # Plot the busy row 153 | row = sinogram[:, rotation] 154 | N = len(row) 155 | 156 | # Take spectrum of busy row and find line spacing 157 | window = blackman(N) 158 | spectrum = rfft(row * window) 159 | 160 | frequency = argmax(abs(spectrum)) 161 | 162 | return rotation, spectrum, frequency 163 | 164 | 165 | 166 | # Copied from https://learnopencv.com/otsu-thresholding-with-opencv/ 167 | def OTSU_Threshold(image): 168 | # Set total number of bins in the histogram 169 | 170 | bins_num = 256 171 | 172 | # Get the image histogram 173 | 174 | hist, bin_edges = np.histogram(image, bins=bins_num) 175 | 176 | # Get normalized histogram if it is required 177 | 178 | #if is_normalized: 179 | 180 | hist = np.divide(hist.ravel(), hist.max()) 181 | 182 | 183 | 184 | # Calculate centers of bins 185 | 186 | bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2. 187 | 188 | 189 | # Iterate over all thresholds (indices) and get the probabilities w1(t), w2(t) 190 | 191 | weight1 = np.cumsum(hist) 192 | 193 | weight2 = np.cumsum(hist[::-1])[::-1] 194 | 195 | # Get the class means mu0(t) 196 | 197 | mean1 = np.cumsum(hist * bin_mids) / weight1 198 | 199 | # Get the class means mu1(t) 200 | 201 | mean2 = (np.cumsum((hist * bin_mids)[::-1]) / weight2[::-1])[::-1] 202 | 203 | inter_class_variance = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2 204 | 205 | # Maximize the inter_class_variance function val 206 | 207 | index_of_max_val = np.argmax(inter_class_variance) 208 | 209 | threshold = bin_mids[:-1][index_of_max_val] 210 | 211 | #print("Otsu's algorithm implementation thresholding result: ", threshold) 212 | return threshold 213 | 214 | 215 | # https://stackoverflow.com/questions/48213278/implementing-otsu-binarization-from-scratch-python 216 | # answer 14 217 | def Otsu2Values(gray): 218 | import math 219 | Media=np.mean(gray) 220 | Desv=np.std(gray) 221 | OcurrenciasMax=0 222 | ValorMax=0 223 | OcurrenciasMin=0 224 | ValorMin=0 225 | his, bins = np.histogram(gray, np.array(range(0, 256))) 226 | for i in range(len(bins)-1): 227 | #print ( " Ocurrencias " + str(his[i]) + " Valor " + str(bins[i])) 228 | if bins[i] > Media: 229 | if his[i] > OcurrenciasMax: 230 | OcurrenciasMax=his[i] 231 | ValorMax=bins[i] 232 | else: 233 | if his[i] > OcurrenciasMin: 234 | OcurrenciasMin=his[i] 235 | ValorMin=bins[i] 236 | 237 | #return ValorMax+(math.sqrt(Desv)/2), ValorMin-(math.sqrt(Desv)/2) 238 | final_img = gray.copy() 239 | #print(final_thresh) 240 | final_img[gray > ( ValorMax-Desv)] = 255 241 | final_img[gray < (ValorMin+Desv)] = 0 242 | return final_img 243 | ######################################################################### 244 | def ApplyCLAHE(gray): 245 | #https://towardsdatascience.com/image-enhancement-techniques-using-opencv-and-python-9191d5c30d45 246 | 247 | gray_img_eqhist=cv2.equalizeHist(gray) 248 | hist=cv2.calcHist(gray_img_eqhist,[0],None,[256],[0,256]) 249 | clahe=cv2.createCLAHE(clipLimit=200,tileGridSize=(3,3)) 250 | gray_img_clahe=clahe.apply(gray_img_eqhist) 251 | return gray_img_clahe 252 | 253 | 254 | def GetPaddleOcr(img): 255 | 256 | """ 257 | Created on Tue Mar 7 10:31:09 2023 258 | 259 | @author: https://pypi.org/project/paddleocr/ (adapted from) 260 | """ 261 | 262 | cv2.imwrite("gray.jpg",img) 263 | img_path = 'gray.jpg' 264 | #cv2.imshow("gray",img) 265 | #cv2.waitKey() 266 | result = ocr.ocr(img_path, cls=True) 267 | for idx in range(len(result)): 268 | res = result[idx] 269 | #for line in res: 270 | # print(line) 271 | 272 | # draw result 273 | from PIL import Image 274 | licensePlate= "" 275 | accuracy=0.0 276 | for i in range(len(result)): 277 | result = result[i] 278 | #image = Image.open(img_path).convert('RGB') 279 | boxes = [line[0] for line in result] 280 | 281 | txts = [line[1][0] for line in result] 282 | scores = [line[1][1] for line in result] 283 | 284 | 285 | #print("RESULTADO "+ str(txts)) 286 | #print("confiabilidad "+ str(scores)) 287 | if len(txts) > 0: 288 | for j in range( len(txts)): 289 | licensePlate= licensePlate + txts[j] 290 | accuracy=float(scores[0]) 291 | #print("SALIDA " + licensePlate) 292 | #print(accuracy) 293 | 294 | return licensePlate, accuracy 295 | 296 | # https://medium.com/@garciafelipe03/image-filters-and-morphological-operations-using-python-89c5bbb8dca0 297 | # 5x5 Gaussian Blur 298 | def gaussian_5x5(img): 299 | 300 | kernel_gb_5 = (1 / 273) * np.array([[1, 4, 7, 4, 1], 301 | [4, 16, 26, 16, 4], 302 | [7, 26, 41, 26, 7], 303 | [4, 16, 26, 16, 4], 304 | [1, 4, 7, 4, 1]]) 305 | 306 | return convolve2d(img, kernel_gb_5, 'valid') 307 | 308 | 309 | 310 | ######################################################################### 311 | def FindLicenseNumber (gray, x_offset, y_offset, License, x_resize, y_resize, \ 312 | Resize_xfactor, Resize_yfactor, BilateralOption): 313 | ######################################################################### 314 | 315 | grayColor=gray 316 | 317 | gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) 318 | 319 | TotHits=0 320 | 321 | X_resize=x_resize 322 | Y_resize=y_resize 323 | 324 | 325 | gray=cv2.resize(gray,None,fx=Resize_xfactor,fy=Resize_yfactor,interpolation=cv2.INTER_CUBIC) 326 | 327 | gray = cv2.resize(gray, (X_resize,Y_resize), interpolation = cv2.INTER_AREA) 328 | 329 | 330 | # en la mayoria de los casos no es necesaria rotacion 331 | # pero en algunos casos si (ver TestRadonWithWilburImage.py) 332 | rotation, spectrum, frquency =GetRotationImage(gray) 333 | rotation=90 - rotation 334 | 335 | if (rotation > 0 and rotation < 30) or (rotation < 0 and rotation > -30): 336 | print(License + " rotate "+ str(rotation)) 337 | gray=imutils.rotate(gray,angle=rotation) 338 | 339 | 340 | TabLicensesFounded=[] 341 | ContLicensesFounded=[] 342 | 343 | 344 | X_resize=x_resize 345 | Y_resize=y_resize 346 | print("gray.shape " + str(gray.shape)) 347 | Resize_xfactor=1.5 348 | Resize_yfactor=1.5 349 | 350 | 351 | TabLicensesFounded=[] 352 | ContLicensesFounded=[] 353 | 354 | TotHits=0 355 | 356 | 357 | gray1=gaussian_5x5(gray) 358 | text, Accuraccy = GetPaddleOcr(gray1) 359 | text = ''.join(char for char in text if char.isalnum()) 360 | text=ProcessText(text) 361 | if ProcessText(text) != "": 362 | 363 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 364 | if text==License: 365 | print(text + " Hit with gaussian_5x5 " ) 366 | TotHits=TotHits+1 367 | else: 368 | print(License + " detected with Filter gaussian_5x5 "+ text) 369 | 370 | 371 | 372 | # https://medium.com/practical-data-science-and-engineering/image-kernels-88162cb6585d 373 | kernel = np.ones((2,2),np.uint8) 374 | 375 | gray1 = cv2.GaussianBlur(gray, (3, 3), 0) 376 | gray1 = cv2.dilate(gray1,kernel,iterations = 1) 377 | for z in range (10,11): 378 | 379 | 380 | kernel = np.array([[0, -1, 0], 381 | [-1,z, -1], 382 | [0, -1, 0]]) 383 | dst = cv2.filter2D(gray1, -1, kernel) 384 | img_concat = cv2.hconcat([gray1, dst]) 385 | text, Accuraccy = GetPaddleOcr(img_concat) 386 | text = ''.join(char for char in text if char.isalnum()) 387 | text=ProcessText(text) 388 | if ProcessText(text) != "": 389 | 390 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 391 | if text==License: 392 | print(text + " Hit with image concat z= " + str(z)) 393 | TotHits=TotHits+1 394 | else: 395 | print(License + " detected with Filter image concat "+ text+ " z= "+ str(z)) 396 | 397 | 398 | 399 | 400 | gray1= ocv_model.upsample(gray) 401 | #cv2.imshow("Ocv",gray1) 402 | #cv2.waitKey() 403 | text, Accuraccy = GetPaddleOcr(gray1) 404 | text = ''.join(char for char in text if char.isalnum()) 405 | text=ProcessText(text) 406 | if ProcessText(text) != "": 407 | 408 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 409 | if text==License: 410 | print(text + " Hit with FSRCNN ") 411 | TotHits=TotHits+1 412 | TabTotHitsFilter[0]=TabTotHitsFilter[0]+1 413 | else: 414 | print(License + " detected with Filter FSRCNN "+ text) 415 | TabTotFailuresFilter[0]=TabTotFailuresFilter[0]+1 416 | 417 | 418 | 419 | 420 | 421 | 422 | gray1=Otsu2Values(gray) 423 | #cv2.imshow("Otsu2",gray1) 424 | #cv2.waitKey() 425 | text, Accuraccy = GetPaddleOcr(gray1) 426 | text = ''.join(char for char in text if char.isalnum()) 427 | text=ProcessText(text) 428 | if ProcessText(text) != "": 429 | 430 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 431 | if text==License: 432 | print(text + " Hit with Otsu2Values ") 433 | TotHits=TotHits+1 434 | #TabTotHitsFilter[0]=TabTotHitsFilter[0]+1 435 | else: 436 | print(License + " detected with Filter Otsu2Values "+ text) 437 | #TabTotFailuresFilter[0]=TabTotFailuresFilter[0]+1 438 | 439 | 440 | # threshold contrast, author Alfonso Blanco 441 | # usefull in some cases 442 | #print ("Media " + str(int(np.mean(gray))) + " Desviacion "+str(np.std(gray))) 443 | ret, thresh = cv2.threshold(gray, int(np.mean(gray)- np.std(gray) ), int(np.mean(gray)+ np.std(gray) ), cv2.THRESH_BINARY) 444 | 445 | #cv2.imshow("thr contrast",thresh) 446 | #cv2.waitKey() 447 | text, Accuraccy = GetPaddleOcr(thresh) 448 | text = ''.join(char for char in text if char.isalnum()) 449 | text=ProcessText(text) 450 | if ProcessText(text) != "": 451 | 452 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 453 | if text==License: 454 | print(text + " Hit with threshold contrast ") 455 | TotHits=TotHits+1 456 | #TabTotHitsFilter[0]=TabTotHitsFilter[0]+1 457 | else: 458 | print(License + " detected with Filter threshold contrast "+ text) 459 | #TabTotFailuresFilter[0]=TabTotFailuresFilter[0]+1 460 | 461 | ################################################ 462 | # https://medium.com/@sahilutekar.su/unlocking-the-full-potential-of-images-with-python-and-opencv-a-step-by-step-guide-to-advanced-7f0a8618c732 463 | # equalization 464 | # Apply Gaussian blur 465 | blur = cv2.GaussianBlur(gray, (5, 5), 0) 466 | 467 | # Apply histogram equalization 468 | equalized = cv2.equalizeHist(gray) 469 | clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8)) 470 | clahe_output = clahe.apply(equalized) 471 | #cv2.imshow('Equalized', equalized) 472 | #cv2.imshow('CLAHE Output', clahe_output) 473 | 474 | # Calculate the high-pass filtered image 475 | high_pass = cv2.subtract(gray, blur) 476 | 477 | # Add the high-pass filtered image back to the original image 478 | sharpened = cv2.addWeighted(gray, 1.2, high_pass, -1.5, 0) 479 | #cv2.imshow('equalization', sharpened) 480 | #cv2.waitKey() 481 | text, Accuraccy = GetPaddleOcr(sharpened) 482 | text = ''.join(char for char in text if char.isalnum()) 483 | text=ProcessText(text) 484 | if ProcessText(text) != "": 485 | 486 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 487 | if text==License: 488 | print(text + " Hit with equalization ") 489 | TotHits=TotHits+1 490 | else: 491 | print(License + " detected with Filter equalization "+ text) 492 | 493 | 494 | 495 | 496 | kernel = np.ones((3,3),np.float32)/90 497 | gray1 = cv2.filter2D(gray,-1,kernel) 498 | #gray_clahe = cv2.GaussianBlur(gray, (5, 5), 0) 499 | gray_img_clahe=ApplyCLAHE(gray1) 500 | 501 | th=OTSU_Threshold(gray_img_clahe) 502 | max_val=255 503 | 504 | ret, o3 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO) 505 | text, Accuraccy = GetPaddleOcr(o3) 506 | 507 | text = ''.join(char for char in text if char.isalnum()) 508 | text=ProcessText(text) 509 | if ProcessText(text) != "": 510 | 511 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 512 | if text==License: 513 | print(text + " Hit with CLAHE and THRESH_TOZERO" ) 514 | #TotHits=TotHits+1 515 | else: 516 | print(License + " detected with CLAHE and THRESH_TOZERO as "+ text) 517 | 518 | 519 | 520 | for z in range(5,6): 521 | 522 | 523 | kernel = np.array([[0,-1,0], [-1,z,-1], [0,-1,0]]) 524 | gray1 = cv2.filter2D(gray, -1, kernel) 525 | 526 | text, Accuraccy = GetPaddleOcr(gray1) 527 | 528 | text = ''.join(char for char in text if char.isalnum()) 529 | text=ProcessText(text) 530 | if ProcessText(text) != "": 531 | 532 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 533 | if text==License: 534 | print(text + " Hit with Sharpen filter z= " +str(z)) 535 | TotHits=TotHits+1 536 | else: 537 | print(License + " detected with Sharpen filter z= " +str(z) + " as "+ text) 538 | 539 | 540 | gray_img_clahe=ApplyCLAHE(gray) 541 | 542 | th=OTSU_Threshold(gray_img_clahe) 543 | max_val=255 544 | 545 | # Otsu's thresholding 546 | ret2,gray1 = cv2.threshold(gray,0,255,cv2.THRESH_TRUNC+cv2.THRESH_OTSU) 547 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 548 | text, Accuraccy = GetPaddleOcr(gray1) 549 | 550 | text = ''.join(char for char in text if char.isalnum()) 551 | 552 | text=ProcessText(text) 553 | if ProcessText(text) != "": 554 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 555 | if text==License: 556 | print(text + " Hit with Otsu's thresholding of cv2 and THRESH_TRUNC" ) 557 | TotHits=TotHits+1 558 | else: 559 | print(License + " detected with Otsu's thresholding of cv2 and THRESH_TRUNC as "+ text) 560 | 561 | 562 | #################################################### 563 | # experimental formula based on the brightness 564 | # of the whole image 565 | #################################################### 566 | 567 | SumBrightness=np.sum(gray) 568 | threshold=(SumBrightness/177600.00) 569 | 570 | ##################################################### 571 | 572 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TOZERO) 573 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 574 | text, Accuraccy = GetPaddleOcr(gray1) 575 | 576 | text = ''.join(char for char in text if char.isalnum()) 577 | 578 | text=ProcessText(text) 579 | if ProcessText(text) != "": 580 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 581 | if text==License: 582 | print(text + " Hit with Brightness and THRESH_TOZERO" ) 583 | TotHits=TotHits+1 584 | else: 585 | print(License + " detected with Brightness and THRESH_TOZERO as "+ text) 586 | 587 | 588 | ################################################################ 589 | return TabLicensesFounded, ContLicensesFounded 590 | 591 | ######################################################################## 592 | def loadimagesRoboflow (dirname): 593 | ######################################################################### 594 | # adapted from: 595 | # https://www.aprendemachinelearning.com/clasificacion-de-imagenes-en-python/ 596 | # by Alfonso Blanco García 597 | ######################################################################## 598 | imgpath = dirname + "\\" 599 | 600 | images = [] 601 | Licenses=[] 602 | 603 | print("Reading imagenes from ",imgpath) 604 | NumImage=-2 605 | 606 | Cont=0 607 | for root, dirnames, filenames in os.walk(imgpath): 608 | 609 | NumImage=NumImage+1 610 | 611 | for filename in filenames: 612 | 613 | if re.search("\.(jpg|jpeg|png|bmp|tiff)$", filename): 614 | 615 | 616 | filepath = os.path.join(root, filename) 617 | License=filename[:len(filename)-4] 618 | #if License != "PGMN112": continue 619 | 620 | image = cv2.imread(filepath) 621 | # Roboflow images are (416,416) 622 | #image=cv2.resize(image,(416,416)) 623 | # kaggle images 624 | #image=cv2.resize(image, (640,640)) 625 | 626 | 627 | images.append(image) 628 | Licenses.append(License) 629 | 630 | Cont+=1 631 | 632 | return images, Licenses 633 | 634 | def Detect_International_LicensePlate(Text): 635 | if len(Text) < 3 : return -1 636 | for i in range(len(Text)): 637 | if (Text[i] >= "0" and Text[i] <= "9" ) or (Text[i] >= "A" and Text[i] <= "Z" ): 638 | continue 639 | else: 640 | return -1 641 | 642 | return 1 643 | 644 | def ProcessText(text): 645 | 646 | if len(text) > 10: 647 | text=text[len(text)-10] 648 | if len(text) > 9: 649 | text=text[len(text)-9] 650 | else: 651 | if len(text) > 8: 652 | text=text[len(text)-8] 653 | else: 654 | 655 | if len(text) > 7: 656 | text=text[len(text)-7:] 657 | if Detect_International_LicensePlate(text)== -1: 658 | return "" 659 | else: 660 | return text 661 | 662 | def ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text): 663 | 664 | SwFounded=0 665 | for i in range( len(TabLicensesFounded)): 666 | if text==TabLicensesFounded[i]: 667 | ContLicensesFounded[i]=ContLicensesFounded[i]+1 668 | SwFounded=1 669 | break 670 | if SwFounded==0: 671 | TabLicensesFounded.append(text) 672 | ContLicensesFounded.append(1) 673 | return TabLicensesFounded, ContLicensesFounded 674 | 675 | 676 | # ttps://medium.chom/@chanon.krittapholchai/build-object-detection-gui-with-yolov8-and-pysimplegui-76d5f5464d6c 677 | def DetectLicenseWithYolov8 (img): 678 | 679 | TabcropLicense=[] 680 | y=[] 681 | yMax=[] 682 | x=[] 683 | xMax=[] 684 | results = model.predict(img) 685 | for i in range(len(results)): 686 | # may be several plates in a frame 687 | result=results[i] 688 | 689 | xyxy= result.boxes.xyxy.numpy() 690 | confidence= result.boxes.conf.numpy() 691 | class_id= result.boxes.cls.numpy().astype(int) 692 | # Get Class name 693 | class_name = [class_list[z] for z in class_id] 694 | # Pack together for easy use 695 | sum_output = list(zip(class_name, confidence,xyxy)) 696 | # Copy image, in case that we need original image for something 697 | out_image = img.copy() 698 | for run_output in sum_output : 699 | # Unpack 700 | #print(class_name) 701 | label, con, box = run_output 702 | if label == "vehicle":continue 703 | cropLicense=out_image[int(box[1]):int(box[3]),int(box[0]):int(box[2])] 704 | #cv2.imshow("Crop", cropLicense) 705 | #cv2.waitKey(0) 706 | TabcropLicense.append(cropLicense) 707 | y.append(int(box[1])) 708 | yMax.append(int(box[3])) 709 | x.append(int(box[0])) 710 | xMax.append(int(box[2])) 711 | 712 | return TabcropLicense, y,yMax,x,xMax 713 | 714 | 715 | ########################################################### 716 | # MAIN 717 | ########################################################## 718 | 719 | imagesComplete, Licenses=loadimagesRoboflow(dirname) 720 | 721 | print("Number of imagenes : " + str(len(imagesComplete))) 722 | 723 | print("Number of licenses : " + str(len(Licenses))) 724 | 725 | ContDetected=0 726 | ContNoDetected=0 727 | TotHits=0 728 | TotFailures=0 729 | with open( "LicenseResults.txt" ,"w") as w: 730 | for i in range (len(imagesComplete)): 731 | 732 | gray=imagesComplete[i] 733 | 734 | License=Licenses[i] 735 | #gray1, gray = Preprocess.preprocess(gray) 736 | TabImgSelect, y, yMax, x, xMax =DetectLicenseWithYolov8(gray) 737 | 738 | if TabImgSelect==[]: 739 | print(License + " NON DETECTED") 740 | ContNoDetected=ContNoDetected+1 741 | continue 742 | else: 743 | ContDetected=ContDetected+1 744 | print(License + " DETECTED ") 745 | for x in range(len(TabImgSelect)): 746 | if TabImgSelect[x] == []: continue 747 | gray=TabImgSelect[x] 748 | #if len(TabImgSelect) > 1: 749 | # gray=TabImgSelect[1] 750 | #cv2.imshow('Frame', gray) 751 | 752 | x_off=3 753 | y_off=2 754 | 755 | #x_resize=220 756 | x_resize=215 757 | y_resize=70 758 | 759 | Resize_xfactor=1.78 760 | Resize_yfactor=1.78 761 | 762 | ContLoop=0 763 | 764 | SwFounded=0 765 | 766 | BilateralOption=0 767 | TabLicensesFounded=[] 768 | ContLicensesFounded=[] 769 | 770 | TabLicensesFounded, ContLicensesFounded= FindLicenseNumber (gray, x_off, y_off, License, x_resize, y_resize, \ 771 | Resize_xfactor, Resize_yfactor, BilateralOption) 772 | 773 | 774 | print(TabLicensesFounded) 775 | print(ContLicensesFounded) 776 | 777 | ymax=-1 778 | contmax=0 779 | licensemax="" 780 | 781 | for z in range(len(TabLicensesFounded)): 782 | if ContLicensesFounded[z] > contmax: 783 | contmax=ContLicensesFounded[z] 784 | licensemax=TabLicensesFounded[z] 785 | 786 | if licensemax == License: 787 | print(License + " correctly recognized") 788 | TotHits+=1 789 | else: 790 | print(License + " Detected but not correctly recognized") 791 | TotFailures +=1 792 | print ("") 793 | lineaw=[] 794 | lineaw.append(License) 795 | lineaw.append(licensemax) 796 | lineaWrite =','.join(lineaw) 797 | lineaWrite=lineaWrite + "\n" 798 | w.write(lineaWrite) 799 | 800 | 801 | print("") 802 | print("Total Hits = " + str(TotHits ) + " from " + str(len(imagesComplete)) + " images readed") 803 | 804 | print("") 805 | 806 | #print("Total Hits filtro nuevo= " + str(TabTotHitsFilter[0] ) + " from " + str(len(imagesComplete)) + " images readed") 807 | #print("Total Failures filtro nuevo= " + str(TabTotFailuresFilter[0] ) + " from " + str(len(imagesComplete)) + " images readed") 808 | 809 | print( " Time in seconds "+ str(time.time()-Ini)) 810 | -------------------------------------------------------------------------------- /LicensePlateYolov8Train.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # https://medium.com/@alimustoofaa/how-to-load-model-yolov8-onnx-cv2-dnn-3e176cde16e6 3 | # https://learnopencv.com/ultralytics-yolov8/#How-to-Use-YOLOv8? 4 | from ultralytics import YOLO 5 | 6 | model = YOLO("yolov8s.pt") 7 | model.train(data="C:\\LicensePlate_Yolov8_Filters_PaddleOCR\\licence_data.yaml", epochs=100,batch=8) # train the model 8 | model.val() # evaluate model performance on the validation set 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LicensePlate_Yolov8_Filters_PaddleOCR 2 | Recognition of license plate numbers, in any format, by automatic detection with Yolov8, pipeline of filters and paddleocr as OCR 3 | 4 | The main improvement with respect to the project presented before ( https://github.com/ablanco1950/LicensePlate_Yolov8_MaxFilters) has been the use of paddleocr instead of pytesseract as well as the reduction of the number of filters, some of which although they were right in certain circumstances produced noise in other. 5 | 6 | On 04/27/2023, a new version is incorporated that considers license plates with several lines and several license plates in one image. 7 | 8 | Requirements: 9 | 10 | paddleocr must be installed ( https://pypi.org/project/paddleocr/) 11 | 12 | pip install paddleocr 13 | 14 | yolo must be installed, if not, follow the instructions indicated in: 15 | https://learnopencv.com/ultralytics-yolov8/#How-to-Use-YOLOv8? 16 | 17 | pip install ultralytics 18 | 19 | also must be installed the usual modules in computer vision: numpy, cv2, os, re, imutils, parabolic 20 | 21 | All needed modules can be installed with a plain pip 22 | 23 | Functioning: 24 | 25 | 26 | Download the project to a folder on disk. 27 | 28 | Download to that folder the roboflow files that will be used for training by yolov8: 29 | 30 | https://public.roboflow.com/object-detection/license-plates-us-eu/3 31 | 32 | In that folder you should find the train and valid folders necessary to build the model 33 | 34 | To ensure the version, the used roboflow file roboflow.zip is attached 35 | 36 | Unzip the files with the test images: Test.zip and test6Training.zip, taking into account when unzipping you can create a folder 37 | inside another folder,there should be only one folder, otherwise programs will not find the test images 38 | 39 | Model Train: 40 | 41 | the train and valid folders of the roboflow folder, resulting from the unziping of robflow.zip, must be placed in the same directory where the execution program LicensePlateYolov8Train.py is located, according to the requirements indicated in license_data.yaml 42 | 43 | run the program 44 | 45 | LicensePlateYolov8Train.py 46 | 47 | which only has a few lines, but the line numbered 7 should indicate the full path where the license_data.yaml file is located. 48 | 49 | Running from a simple laptop, the 100 epochs of the program will take a long time, but you can always lower the cover of the laptop and 50 | continue the next day (besides, there are only 245 images for training). As obtaining best.pt is problematic, the one used in the project tests is attached. 51 | 52 | As a result, inside the project folder, the directory runs\detect\trainN\weights( where in trainN, N indicates 53 | the last train directory created, in which the best.pt file is located), best.pt is the base of the model and 54 | is referenced in line 17 of the GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py program (modify the route, the name of trainN, so that it points to the last train and best.pt created 55 | 56 | As obtaining best.pt is problematic, the one used in the project tests is attached,it must be adjusted the route in instruction 17 in GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py 57 | 58 | Run the program. 59 | 60 | GetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR_V1.py 61 | 62 | The car license plates and successes or failures through the different filters appear on the screen. 63 | 64 | The LicenseResults.txt file lists the car license plates with their corresponding recognized ones. 65 | 66 | The combination of the efficiency of paddleocr and the plate detection precision of a simple yolov8 on Roboflow images, together with an adequate selection of filters, allows us to obtain a comparison of the plates recognized with the best license plate detector I know: https: //www.doubango.org/webapps/alpr/ which is included in the attached Excel file: ComparisonWithDOUBANGO.xls, testing with the images contained in the attached Test directory 67 | 68 | By changing the path in instruction 12,activating the directory "test6Training\\images" an deactivating the path "Test" in instruction 15: In a test with 117 images, 107 hits are achieved 69 | In the same manner any other image directory can be tested (In this case, the LicenseResults.txt file must be consulted to indicate the licenses plates, if the files are not named with the license number, as in test6Training occurs, it cannot be determined if the assignment was successful automatically) 70 | 71 | The video version is also included: 72 | 73 | VIDEOGetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR_Demonstration.py 74 | 75 | operating on the attached video: 76 | 77 | Traffic IP Camera video.mp4 78 | 79 | downloaded from project: 80 | https://github.com/anmspro/Traffic-Signal-Violation-Detection-System/tree/master/Resources 81 | 82 | In its execution, on the monitor screen, the detected license plates are detailed with a summary at the end. 83 | 84 | Three files are obtained: 85 | 86 | VIDEOLicenseResults,txt with the registration of license plates detected with a lot of noise. 87 | 88 | VIDEOLicenseSummary.txt with the following results, which seem pretty tight as can be seen visually from the video. 89 | 90 | A8254S,145,198.2291808128357 91 | 92 | AR606L,10,31.03719687461853 93 | 94 | AE670S,10,15.752639770507812 95 | 96 | A3K96,8,25.679476976394653 97 | 98 | A3K961,3,14.658559083938599 99 | 100 | A968B6,5,7.775115013122559 101 | 102 | AV6190,10,17.38904595375061 103 | 104 | The first field is the license plate detected and the second is the number of snapshots of that license plate. 105 | 106 | As a maximum number of snapshots of 3 has been set (LimitSnapshot=3 parameter in the program), to avoid noise, the license plate of the APHI88 car that was going faster and that only recorded one snapshot does not appear (it can be checked in the VIDEOLicenseResults.txt logging file) 107 | 108 | Also is produced a summary video: demonstration.mp4 109 | 110 | Two videos of test results: demonstration1.mp4 and demonstration2.mp4 are attached. 111 | 112 | The program is prepared to run in a time of 800 seconds (parameter: TimeLimit) so you have to wait that time until it ends or press q key. 113 | 114 | More precise and exploitable results, although less apparent and more slowly, are obtained by executing: 115 | 116 | VIDEOGetNumberInternationalLicensePlate_RoboflowModel_Filters_PaddleOCR.py 117 | 118 | Other test videos can be downloaded from the addresses indicated in the program and in the references section. 119 | 120 | 121 | References: 122 | 123 | https://pypi.org/project/paddleocr/ 124 | 125 | https://learnopencv.com/ultralytics-yolov8/#How-to-Use-YOLOv8? 126 | 127 | https://public.roboflow.com/object-detection/license-plates-us-eu/3 128 | 129 | https://docs.ultralytics.com/python/ 130 | 131 | https://medium.com/@chanon.krittapholchai/build-object-detection-gui-with-yolov8-and-pysimplegui-76d5f5464d6c 132 | 133 | https://medium.com/@alimustoofaa/how-to-load-model-yolov8-onnx-cv2-dnn-3e176cde16e6 134 | 135 | https://medium.com/adevinta-tech-blog/text-in-image-2-0-improving-ocr-service-with-paddleocr-61614c886f93 136 | 137 | https://machinelearningprojects.net/number-plate-detection-using-yolov7/ 138 | 139 | https://github.com/ablanco1950/LicensePlate_Yolov8_MaxFilters 140 | 141 | https://github.com/mrzaizai2k/VIETNAMESE_LICENSE_PLATE 142 | 143 | https: //www.doubango.org/webapps/alpr/ 144 | 145 | Filters: 146 | 147 | https://github.com/Saafke/FSRCNN_Tensorflow/tree/master/models ( downloaded module FSRCNN_x4.pb) 148 | 149 | https://learnopencv.com/super-resolution-in-opencv/#sec5 150 | 151 | https://learnopencv.com/super-resolution-in-opencv/ 152 | 153 | https://gist.github.com/endolith/334196bac1cac45a4893# 154 | 155 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 156 | 157 | https://gist.github.com/endolith/255291#file-parabolic-py 158 | 159 | https://learnopencv.com/otsu-thresholding-with-opencv/ 160 | 161 | https://towardsdatascience.com/image-enhancement-techniques-using-opencv-and-python-9191d5c30d45 162 | 163 | https://blog.katastros.com/a?ID=01800-4bf623a1-3917-4d54-9b6a-775331ebaf05 164 | 165 | https://programmerclick.com/article/89421544914/ 166 | 167 | https://anishgupta1005.medium.com/building-an-optical-character-recognizer-in-python-bbd09edfe438 168 | 169 | https://datasmarts.net/es/como-usar-el-detector-de-puntos-clave-mser-en-opencv/ 170 | 171 | https://felipemeganha.medium.com/detecting-handwriting-regions-with-opencv-and-python-ff0b1050aa4e 172 | 173 | https://github.com/victorgzv/Lighting-correction-with-OpenCV 174 | 175 | https://medium.com/@yyuanli19/using-mnist-to-visualize-basic-conv-filtering-95d24679643e 176 | 177 | Projects with videos to download to test: 178 | 179 | https://github.com/anmspro/Traffic-Signal-Violation-Detection-System/tree/master/Resources 180 | "Traffic IP Camera video.mp4" 181 | 182 | https://github.com/hasaan21/Car-Number-Plate-Recognition-Sysytem 183 | "vid.mp4" 184 | 185 | //www.pexels.com/video/video-of-famous-landmark-on-a-city-during-daytime-1721294/ 186 | "Pexels Videos 1721294.mp4" 187 | 188 | -------------------------------------------------------------------------------- /Test.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/Test.zip -------------------------------------------------------------------------------- /TestWilburImage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on May 2023 4 | 5 | @author: Alfonso Blanco 6 | """ 7 | 8 | import time 9 | ###################################################################### 10 | from paddleocr import PaddleOCR 11 | # Paddleocr supports Chinese, English, French, German, Korean and Japanese. 12 | # You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan` 13 | # to switch the language model in order. 14 | # https://pypi.org/project/paddleocr/ 15 | # 16 | # supress anoysing logging messages parameter show_log = False 17 | # https://github.com/PaddlePaddle/PaddleOCR/issues/2348 18 | ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log = False) # need to run only once to download and load model into memory 19 | 20 | import numpy as np 21 | 22 | import cv2 23 | 24 | X_resize=220 25 | Y_resize=70 26 | 27 | import imutils 28 | 29 | 30 | ##################################################################### 31 | """ 32 | Copied from https://gist.github.com/endolith/334196bac1cac45a4893# 33 | 34 | other source: 35 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 36 | """ 37 | 38 | from skimage.transform import radon 39 | 40 | import numpy 41 | from numpy import mean, array, blackman, sqrt, square 42 | from numpy.fft import rfft 43 | 44 | try: 45 | # More accurate peak finding from 46 | # https://gist.github.com/endolith/255291#file-parabolic-py 47 | from parabolic import parabolic 48 | 49 | def argmax(x): 50 | return parabolic(x, numpy.argmax(x))[0] 51 | except ImportError: 52 | from numpy import argmax 53 | 54 | 55 | def GetRotationImage(image): 56 | 57 | 58 | I=image 59 | I = I - mean(I) # Demean; make the brightness extend above and below zero 60 | 61 | 62 | # Do the radon transform and display the result 63 | sinogram = radon(I) 64 | 65 | 66 | # Find the RMS value of each row and find "busiest" rotation, 67 | # where the transform is lined up perfectly with the alternating dark 68 | # text and white lines 69 | 70 | # rms_flat does no exist in recent versions 71 | #r = array([mlab.rms_flat(line) for line in sinogram.transpose()]) 72 | r = array([sqrt(mean(square(line))) for line in sinogram.transpose()]) 73 | rotation = argmax(r) 74 | #print('Rotation: {:.2f} degrees'.format(90 - rotation)) 75 | #plt.axhline(rotation, color='r') 76 | 77 | # Plot the busy row 78 | row = sinogram[:, rotation] 79 | N = len(row) 80 | 81 | # Take spectrum of busy row and find line spacing 82 | window = blackman(N) 83 | spectrum = rfft(row * window) 84 | 85 | frequency = argmax(abs(spectrum)) 86 | 87 | return rotation, spectrum, frequency 88 | 89 | 90 | 91 | 92 | def GetPaddleOcr(img): 93 | 94 | """ 95 | Created on Tue Mar 7 10:31:09 2023 96 | 97 | @author: https://pypi.org/project/paddleocr/ (adapted from) 98 | """ 99 | 100 | cv2.imwrite("gray.jpg",img) 101 | img_path = 'gray.jpg' 102 | #cv2.imshow("gray",img) 103 | #cv2.waitKey() 104 | result = ocr.ocr(img_path, cls=True) 105 | for idx in range(len(result)): 106 | res = result[idx] 107 | #for line in res: 108 | # print(line) 109 | 110 | # draw result 111 | from PIL import Image 112 | licensePlate= "" 113 | accuracy=0.0 114 | for i in range(len(result)): 115 | result = result[i] 116 | #image = Image.open(img_path).convert('RGB') 117 | boxes = [line[0] for line in result] 118 | 119 | txts = [line[1][0] for line in result] 120 | scores = [line[1][1] for line in result] 121 | 122 | 123 | #print("RESULTADO "+ str(txts)) 124 | #print("confiabilidad "+ str(scores)) 125 | if len(txts) > 0: 126 | for j in range( len(txts)): 127 | licensePlate= licensePlate + txts[j] 128 | accuracy=float(scores[0]) 129 | #print("SALIDA " + licensePlate) 130 | #print(accuracy) 131 | 132 | return licensePlate, accuracy 133 | 134 | 135 | 136 | 137 | def Detect_International_LicensePlate(Text): 138 | if len(Text) < 3 : return -1 139 | for i in range(len(Text)): 140 | if (Text[i] >= "0" and Text[i] <= "9" ) or (Text[i] >= "A" and Text[i] <= "Z" ): 141 | continue 142 | else: 143 | return -1 144 | 145 | return 1 146 | 147 | def ProcessText(text): 148 | 149 | if len(text) > 10: 150 | text=text[len(text)-10] 151 | if len(text) > 9: 152 | text=text[len(text)-9] 153 | else: 154 | if len(text) > 8: 155 | text=text[len(text)-8] 156 | else: 157 | 158 | if len(text) > 7: 159 | text=text[len(text)-7:] 160 | if Detect_International_LicensePlate(text)== -1: 161 | return "" 162 | else: 163 | return text 164 | 165 | ########################################################### 166 | # MAIN 167 | ########################################################## 168 | Ini=time.time() 169 | gray = cv2.imread("WilburImage.jpg") 170 | #cv2.imshow("gray",gray) 171 | #cv2.waitKey() 172 | grayColor=gray 173 | 174 | gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) 175 | 176 | TotHits=0 177 | 178 | X_resize=215 179 | Y_resize=70 180 | 181 | 182 | gray=cv2.resize(gray,None,fx=1.78,fy=1.78,interpolation=cv2.INTER_CUBIC) 183 | 184 | gray = cv2.resize(gray, (X_resize,Y_resize), interpolation = cv2.INTER_AREA) 185 | 186 | rotation, spectrum, frquency =GetRotationImage(gray) 187 | rotation=90.0 - rotation 188 | #rotation=10.09 189 | if (rotation > 0 and rotation < 30) or (rotation < 0 and rotation > -30): 190 | print(" rotate "+ str(rotation)) 191 | gray=imutils.rotate(gray,angle=rotation) 192 | 193 | 194 | kernel = np.ones((2,2),np.uint8) 195 | 196 | gray = cv2.GaussianBlur(gray, (3, 3), 0) 197 | 198 | gray = cv2.dilate(gray,kernel,iterations = 1) 199 | 200 | 201 | # https://medium.com/practical-data-science-and-engineering/image-kernels-88162cb6585d 202 | 203 | kernel = np.array([[0, -1, 0], 204 | [-1,10, -1], 205 | [0, -1, 0]]) 206 | dst = cv2.filter2D(gray, -1, kernel) 207 | img_concat = cv2.hconcat([gray, dst]) 208 | 209 | text, Accuraccy = GetPaddleOcr(img_concat) 210 | text = ''.join(char for char in text if char.isalnum()) 211 | text=ProcessText(text) 212 | 213 | print(" License detected = "+ text) 214 | print(" Time in seconds " + str(time.time() - Ini)) -------------------------------------------------------------------------------- /Traffic IP Camera video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/Traffic IP Camera video.mp4 -------------------------------------------------------------------------------- /VIDEOGetNumberInternationalLicensePlate_RoboflowModel_Filters_PaddleOCR_Demonstration.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Apr 25 20:1 7:29 2022 4 | 5 | @author: Alfonso Blanco 6 | """ 7 | ###################################################################### 8 | # PARAMETERS 9 | ##################################################################### 10 | ##################################################################### 11 | API_KEY="" 12 | 13 | ###################################################### 14 | # Video from https://github.com/anmspro/Traffic-Signal-Violation-Detection-System/tree/master/Resources 15 | dirVideo="Traffic IP Camera video.mp4" 16 | #dirVideo="vid.mp4" 17 | # in 14 minutes = 800 seconds finish 18 | TimeLimit=1000 19 | # Max number of Snapshots to consider a image 20 | # lower 5 snapshots is consider noisy 21 | LimitSnapshot=5 22 | # to increase the speed of the process, 23 | # even if some license plates are lost, 24 | # only one snapshot out of every SpeedUpSnapshot is processed 25 | SpeedUpSnapshot=10 26 | ############################################################## 27 | # DOWNLOAD VIDEOS TO TEST 28 | ############################################################### 29 | # video from https://github.com/hasaan21/Car-Number-Plate-Recognition-Sysytem 30 | #dirVideo="vid.mp4" 31 | 32 | #dirVideo="video12.mp4" 33 | #dirVideo="C:\\Car_Speed_Detection\\Comma.ai.Data.and.Model\\Comma.ai Model\\train.mp4" 34 | 35 | # from https://www.pexels.com/video/video-of-famous-landmark-on-a-city-during-daytime-1721294/ 36 | #dirVideo="Pexels Videos 1721294.mp4" 37 | 38 | import json 39 | 40 | """ 41 | @author: https://blog.roboflow.com/how-to-crop-computer-vision-model-predictions/ 42 | """ 43 | 44 | from roboflow import Roboflow 45 | 46 | rf = Roboflow(api_key=API_KEY) 47 | project = rf.workspace().project("license-plate-recognition-rxg4e") 48 | model = project.version(4).model 49 | 50 | 51 | 52 | ###################################################################### 53 | from paddleocr import PaddleOCR 54 | # Paddleocr supports Chinese, English, French, German, Korean and Japanese. 55 | # You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan` 56 | # to switch the language model in order. 57 | # https://pypi.org/project/paddleocr/ 58 | # 59 | # supress anoysing logging messages parameter show_log = False 60 | # https://github.com/PaddlePaddle/PaddleOCR/issues/2348 61 | ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log = False) # need to run only once to download and load model into memory 62 | 63 | import numpy as np 64 | 65 | import cv2 66 | 67 | import time 68 | 69 | TimeIni=time.time() 70 | 71 | 72 | X_resize=220 73 | Y_resize=70 74 | 75 | import imutils 76 | 77 | ##################################################################### 78 | """ 79 | Copied from https://gist.github.com/endolith/334196bac1cac45a4893# 80 | 81 | other source: 82 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 83 | """ 84 | 85 | from skimage.transform import radon 86 | 87 | import numpy 88 | from numpy import mean, array, blackman, sqrt, square 89 | from numpy.fft import rfft 90 | 91 | try: 92 | # More accurate peak finding from 93 | # https://gist.github.com/endolith/255291#file-parabolic-py 94 | from parabolic import parabolic 95 | 96 | def argmax(x): 97 | return parabolic(x, numpy.argmax(x))[0] 98 | except ImportError: 99 | from numpy import argmax 100 | 101 | 102 | def GetRotationImage(image): 103 | 104 | 105 | I=image 106 | I = I - mean(I) # Demean; make the brightness extend above and below zero 107 | 108 | 109 | # Do the radon transform and display the result 110 | sinogram = radon(I) 111 | 112 | 113 | # Find the RMS value of each row and find "busiest" rotation, 114 | # where the transform is lined up perfectly with the alternating dark 115 | # text and white lines 116 | 117 | # rms_flat does no exist in recent versions 118 | #r = array([mlab.rms_flat(line) for line in sinogram.transpose()]) 119 | r = array([sqrt(mean(square(line))) for line in sinogram.transpose()]) 120 | rotation = argmax(r) 121 | #print('Rotation: {:.2f} degrees'.format(90 - rotation)) 122 | #plt.axhline(rotation, color='r') 123 | 124 | # Plot the busy row 125 | row = sinogram[:, rotation] 126 | N = len(row) 127 | 128 | # Take spectrum of busy row and find line spacing 129 | window = blackman(N) 130 | spectrum = rfft(row * window) 131 | 132 | frequency = argmax(abs(spectrum)) 133 | 134 | return rotation, spectrum, frequency 135 | 136 | ##################################################################### 137 | def ThresholdStable(image): 138 | # -*- coding: utf-8 -*- 139 | """ 140 | Created on Fri Aug 12 21:04:48 2022 141 | Author: Alfonso Blanco García 142 | 143 | Looks for the threshold whose variations keep the image STABLE 144 | (there are only small variations with the image of the previous 145 | threshold). 146 | Similar to the method followed in cv2.MSER 147 | https://datasmarts.net/es/como-usar-el-detector-de-puntos-clave-mser-en-opencv/https://felipemeganha.medium.com/detecting-handwriting-regions-with-opencv-and-python-ff0b1050aa4e 148 | """ 149 | 150 | thresholds=[] 151 | Repes=[] 152 | Difes=[] 153 | 154 | gray=image 155 | grayAnt=gray 156 | 157 | ContRepe=0 158 | threshold=0 159 | for i in range (255): 160 | 161 | ret, gray1=cv2.threshold(gray,i,255, cv2.THRESH_BINARY) 162 | Dife1 = grayAnt - gray1 163 | Dife2=np.sum(Dife1) 164 | if Dife2 < 0: Dife2=Dife2*-1 165 | Difes.append(Dife2) 166 | if Dife2<22000: # Case only image of license plate 167 | #if Dife2<60000: 168 | ContRepe=ContRepe+1 169 | 170 | threshold=i 171 | grayAnt=gray1 172 | continue 173 | if ContRepe > 0: 174 | 175 | thresholds.append(threshold) 176 | Repes.append(ContRepe) 177 | ContRepe=0 178 | grayAnt=gray1 179 | thresholdMax=0 180 | RepesMax=0 181 | for i in range(len(thresholds)): 182 | #print ("Threshold = " + str(thresholds[i])+ " Repeticiones = " +str(Repes[i])) 183 | if Repes[i] > RepesMax: 184 | RepesMax=Repes[i] 185 | thresholdMax=thresholds[i] 186 | 187 | #print(min(Difes)) 188 | #print ("Threshold Resultado= " + str(thresholdMax)+ " Repeticiones = " +str(RepesMax)) 189 | return thresholdMax 190 | 191 | 192 | 193 | # Copied from https://learnopencv.com/otsu-thresholding-with-opencv/ 194 | def OTSU_Threshold(image): 195 | # Set total number of bins in the histogram 196 | 197 | bins_num = 256 198 | 199 | # Get the image histogram 200 | 201 | hist, bin_edges = np.histogram(image, bins=bins_num) 202 | 203 | # Get normalized histogram if it is required 204 | 205 | #if is_normalized: 206 | 207 | hist = np.divide(hist.ravel(), hist.max()) 208 | 209 | 210 | 211 | # Calculate centers of bins 212 | 213 | bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2. 214 | 215 | 216 | # Iterate over all thresholds (indices) and get the probabilities w1(t), w2(t) 217 | 218 | weight1 = np.cumsum(hist) 219 | 220 | weight2 = np.cumsum(hist[::-1])[::-1] 221 | 222 | # Get the class means mu0(t) 223 | 224 | mean1 = np.cumsum(hist * bin_mids) / weight1 225 | 226 | # Get the class means mu1(t) 227 | 228 | mean2 = (np.cumsum((hist * bin_mids)[::-1]) / weight2[::-1])[::-1] 229 | 230 | inter_class_variance = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2 231 | 232 | # Maximize the inter_class_variance function val 233 | 234 | index_of_max_val = np.argmax(inter_class_variance) 235 | 236 | threshold = bin_mids[:-1][index_of_max_val] 237 | 238 | #print("Otsu's algorithm implementation thresholding result: ", threshold) 239 | return threshold 240 | 241 | ######################################################################### 242 | def ApplyCLAHE(gray): 243 | #https://towardsdatascience.com/image-enhancement-techniques-using-opencv-and-python-9191d5c30d45 244 | 245 | gray_img_eqhist=cv2.equalizeHist(gray) 246 | hist=cv2.calcHist(gray_img_eqhist,[0],None,[256],[0,256]) 247 | clahe=cv2.createCLAHE(clipLimit=200,tileGridSize=(3,3)) 248 | gray_img_clahe=clahe.apply(gray_img_eqhist) 249 | return gray_img_clahe 250 | 251 | 252 | def GetPaddleOcr(img): 253 | 254 | """ 255 | Created on Tue Mar 7 10:31:09 2023 256 | 257 | @author: https://pypi.org/project/paddleocr/ (adapted from) 258 | """ 259 | 260 | cv2.imwrite("gray.jpg",img) 261 | img_path = 'gray.jpg' 262 | 263 | 264 | result = ocr.ocr(img_path, cls=True) 265 | # draw result 266 | #from PIL import Image 267 | result = result[0] 268 | #image = Image.open(img_path).convert('RGB') 269 | boxes = [line[0] for line in result] 270 | 271 | txts = [line[1][0] for line in result] 272 | scores = [line[1][1] for line in result] 273 | 274 | licensePlate= "" 275 | accuracy=0.0 276 | #print("RESULTADO "+ str(txts)) 277 | #print("confiabilidad "+ str(scores)) 278 | if len(txts) > 0: 279 | licensePlate= txts[0] 280 | accuracy=float(scores[0]) 281 | #print(licensePlate) 282 | #print(accuracy) 283 | 284 | return licensePlate, accuracy 285 | 286 | 287 | ######################################################################### 288 | def FindLicenseNumber (gray, x_offset, y_offset, License, x_resize, y_resize, \ 289 | Resize_xfactor, Resize_yfactor, BilateralOption): 290 | ######################################################################### 291 | 292 | 293 | gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) 294 | 295 | TotHits=0 296 | 297 | X_resize=x_resize 298 | Y_resize=y_resize 299 | 300 | 301 | gray=cv2.resize(gray,None,fx=Resize_xfactor,fy=Resize_yfactor,interpolation=cv2.INTER_CUBIC) 302 | 303 | gray = cv2.resize(gray, (X_resize,Y_resize), interpolation = cv2.INTER_AREA) 304 | 305 | rotation, spectrum, frquency =GetRotationImage(gray) 306 | rotation=90 - rotation 307 | #print("Car" + str(NumberImageOrder) + " Brillo : " +str(SumBrightnessLic) + 308 | # " Desviacion : " + str(DesvLic)) 309 | if (rotation > 0 and rotation < 30) or (rotation < 0 and rotation > -30): 310 | print(License + " rotate "+ str(rotation)) 311 | gray=imutils.rotate(gray,angle=rotation) 312 | 313 | 314 | TabLicensesFounded=[] 315 | ContLicensesFounded=[] 316 | 317 | 318 | X_resize=x_resize 319 | Y_resize=y_resize 320 | print("gray.shape " + str(gray.shape)) 321 | Resize_xfactor=1.5 322 | Resize_yfactor=1.5 323 | 324 | 325 | TabLicensesFounded=[] 326 | ContLicensesFounded=[] 327 | AccuraccyMin=0.7 328 | TotHits=0 329 | 330 | # https://medium.com/practical-data-science-and-engineering/image-kernels-88162cb6585d 331 | #kernel = np.array([[0, -1, 0], 332 | # [-1, 5, -1], 333 | # [0, -1, 0]]) 334 | 335 | 336 | kernel = np.array([[0, -1, 0], 337 | [-1,10, -1], 338 | [0, -1, 0]]) 339 | dst = cv2.filter2D(gray, -1, kernel) 340 | img_concat = cv2.hconcat([gray, dst]) 341 | text, Accuraccy = GetPaddleOcr(img_concat) 342 | if Accuraccy < AccuraccyMin: 343 | text="" 344 | text = ''.join(char for char in text if char.isalnum()) 345 | text=ProcessText(text) 346 | if ProcessText(text) != "": 347 | 348 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 349 | if text==License: 350 | print(text + " Hit with image concat ") 351 | TotHits=TotHits+1 352 | else: 353 | print(License + " detected with Filter image concat "+ text) 354 | 355 | 356 | 357 | kernel = np.ones((3,3),np.float32)/90 358 | gray1 = cv2.filter2D(gray,-1,kernel) 359 | #gray_clahe = cv2.GaussianBlur(gray, (5, 5), 0) 360 | gray_img_clahe=ApplyCLAHE(gray1) 361 | 362 | th=OTSU_Threshold(gray_img_clahe) 363 | max_val=255 364 | 365 | ret, o3 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO) 366 | text, Accuraccy = GetPaddleOcr(o3) 367 | if Accuraccy < AccuraccyMin: 368 | text="" 369 | 370 | text = ''.join(char for char in text if char.isalnum()) 371 | text=ProcessText(text) 372 | if ProcessText(text) != "": 373 | 374 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 375 | if text==License: 376 | print(text + " Hit with CLAHE and THRESH_TOZERO" ) 377 | #TotHits=TotHits+1 378 | else: 379 | print(License + " detected with CLAHE and THRESH_TOZERO as "+ text) 380 | 381 | 382 | 383 | for z in range(5,6): 384 | 385 | kernel = np.array([[0,-1,0], [-1,z,-1], [0,-1,0]]) 386 | gray1 = cv2.filter2D(gray, -1, kernel) 387 | 388 | text, Accuraccy = GetPaddleOcr(gray1) 389 | if Accuraccy < AccuraccyMin: 390 | text="" 391 | 392 | text = ''.join(char for char in text if char.isalnum()) 393 | text=ProcessText(text) 394 | if ProcessText(text) != "": 395 | 396 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 397 | if text==License: 398 | print(text + " Hit with Sharpen filter z= " +str(z)) 399 | TotHits=TotHits+1 400 | else: 401 | print(License + " detected with Sharpen filter z= " +str(z) + " as "+ text) 402 | 403 | 404 | gray_img_clahe=ApplyCLAHE(gray) 405 | 406 | th=OTSU_Threshold(gray_img_clahe) 407 | max_val=255 408 | 409 | # Otsu's thresholding 410 | ret2,gray1 = cv2.threshold(gray,0,255,cv2.THRESH_TRUNC+cv2.THRESH_OTSU) 411 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 412 | text, Accuraccy = GetPaddleOcr(gray1) 413 | if Accuraccy < AccuraccyMin: 414 | text="" 415 | 416 | text = ''.join(char for char in text if char.isalnum()) 417 | text=ProcessText(text) 418 | if ProcessText(text) != "": 419 | #if Detect_International_LicensePlate(text)== 1: 420 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 421 | if text==License: 422 | print(text + " Hit with Otsu's thresholding of cv2 and THRESH_TRUNC" ) 423 | TotHits=TotHits+1 424 | else: 425 | print(License + " detected with Otsu's thresholding of cv2 and THRESH_TRUNC as "+ text) 426 | 427 | 428 | threshold=ThresholdStable(gray) 429 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TRUNC) 430 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 431 | text, Accuraccy = GetPaddleOcr(gray1) 432 | if Accuraccy < AccuraccyMin: 433 | text="" 434 | 435 | text = ''.join(char for char in text if char.isalnum()) 436 | text=ProcessText(text) 437 | if ProcessText(text) != "": 438 | #if Detect_International_LicensePlate(text)== 1: 439 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 440 | if text==License: 441 | print(text + " Hit with Stable and THRESH_TRUNC" ) 442 | TotHits=TotHits+1 443 | else: 444 | print(License + " detected with Stable and THRESH_TRUNC as "+ text) 445 | 446 | 447 | #################################################### 448 | # experimental formula based on the brightness 449 | # of the whole image 450 | #################################################### 451 | 452 | SumBrightness=np.sum(gray) 453 | threshold=(SumBrightness/177600.00) 454 | 455 | ##################################################### 456 | 457 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TOZERO) 458 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 459 | text, Accuraccy = GetPaddleOcr(gray1) 460 | if Accuraccy < AccuraccyMin: 461 | text="" 462 | 463 | text = ''.join(char for char in text if char.isalnum()) 464 | text=ProcessText(text) 465 | if ProcessText(text) != "": 466 | #if Detect_International_LicensePlate(text)== 1: 467 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 468 | if text==License: 469 | print(text + " Hit with Brightness and THRESH_TOZERO" ) 470 | TotHits=TotHits+1 471 | else: 472 | print(License + " detected with Brightness and THRESH_TOZERO as "+ text) 473 | 474 | 475 | ################################################################ 476 | return TabLicensesFounded, ContLicensesFounded 477 | 478 | 479 | def Detect_International_LicensePlate(Text): 480 | if len(Text) < 3 : return -1 481 | for i in range(len(Text)): 482 | if (Text[i] >= "0" and Text[i] <= "9" ) or (Text[i] >= "A" and Text[i] <= "Z" ): 483 | continue 484 | else: 485 | return -1 486 | 487 | return 1 488 | 489 | def ProcessText(text): 490 | 491 | if len(text) > 10: 492 | text=text[len(text)-10] 493 | if len(text) > 9: 494 | text=text[len(text)-9] 495 | else: 496 | if len(text) > 8: 497 | text=text[len(text)-8] 498 | else: 499 | 500 | if len(text) > 7: 501 | text=text[len(text)-7:] 502 | if Detect_International_LicensePlate(text)== -1: 503 | return "" 504 | else: 505 | return text 506 | 507 | def ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text): 508 | 509 | SwFounded=0 510 | for i in range( len(TabLicensesFounded)): 511 | if text==TabLicensesFounded[i]: 512 | ContLicensesFounded[i]=ContLicensesFounded[i]+1 513 | SwFounded=1 514 | break 515 | if SwFounded==0: 516 | TabLicensesFounded.append(text) 517 | ContLicensesFounded.append(1) 518 | return TabLicensesFounded, ContLicensesFounded 519 | 520 | 521 | def DetectLicenseWithRoboflow (img): 522 | 523 | TabcropLicense=[] 524 | #img = cv2.resize(img, (640, 640), interpolation = cv2.INTER_AREA) 525 | cv2.imwrite("gray.jpg",img) 526 | img_path = 'gray.jpg' 527 | inference = model.predict(img_path, confidence=40) 528 | #print(inference[0]) 529 | #json_load=inference 530 | if str(inference)=="": 531 | return TabcropLicense,0,0,0,0 532 | 533 | # json.loads only admit one inference for that inference[0] 534 | # https://stackoverflow.com/questions/21058935/python-json-loads-shows-valueerror-extra-data 535 | # answer 14.1 536 | json_load = (json.loads(str(inference[0]))) 537 | 538 | i=0 539 | for z in json_load: 540 | i=i+1 541 | if i > 4: break 542 | if i==1: x=json_load[z] 543 | if i==2: y=json_load[z] 544 | if i==3: w=json_load[z] 545 | if i==4:h=json_load[z] 546 | 547 | 548 | x=int(x) 549 | y=int(y) 550 | w=int(w) 551 | h=int(h) 552 | limitY=int(h/2) 553 | limitX=int(w/2) 554 | cropLicense=img[y-limitY:y+limitY,x-limitX:x+limitX] 555 | #cv2.imshow("Crop", cropLicense) 556 | #cv2.waitKey(0) 557 | TabcropLicense.append(cropLicense) 558 | return TabcropLicense, x, y, limitX, limitY 559 | 560 | 561 | ########################################################### 562 | # MAIN 563 | ########################################################## 564 | 565 | TabLicensesmax=[] 566 | ContLicensesmax=[] 567 | TimeIniLicensesmax=[] 568 | TimeEndLicensesmax=[] 569 | 570 | ContDetected=0 571 | ContNoDetected=0 572 | TotHits=0 573 | TotFailures=0 574 | ContSnapshots=0 575 | with open( "VIDEOLicenseResults.txt" ,"w") as w: 576 | 577 | cap = cv2.VideoCapture(dirVideo) 578 | # https://levelup.gitconnected.com/opencv-python-reading-and-writing-images-and-videos-ed01669c660c 579 | 580 | fourcc = cv2.VideoWriter_fourcc(*'MP4V') 581 | fps=5.0 582 | frame_width = 680 583 | frame_height = 480 584 | cap.set(cv2.CAP_PROP_FRAME_WIDTH, frame_width) 585 | cap.set(cv2.CAP_PROP_FRAME_HEIGHT, frame_height) 586 | size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) 587 | 588 | video_writer = cv2.VideoWriter('demonstration.mp4',fourcc,fps, size) 589 | while (cap.isOpened()): 590 | ret, img = cap.read() 591 | 592 | 593 | if ret != True: break 594 | 595 | else: 596 | #print(ret) 597 | # cv2.imshow('original video', img) 598 | # 599 | # if cv2.waitKey(25) & 0xFF == ord('q'): 600 | # break 601 | gray=img 602 | # speed up a little 603 | ContSnapshots=ContSnapshots+1 604 | if ContSnapshots < SpeedUpSnapshot: 605 | continue 606 | else: 607 | ContSnapshots=0 608 | 609 | 610 | License="License" 611 | TabImgSelect , x, y, limitX, limitY =DetectLicenseWithRoboflow(gray) 612 | if TabImgSelect==[]: 613 | print(License + " NON DETECTED") 614 | ContNoDetected=ContNoDetected+1 615 | continue 616 | else: 617 | ContDetected=ContDetected+1 618 | print(License + " DETECTED ") 619 | 620 | gray=TabImgSelect[0] 621 | 622 | x_off=3 623 | y_off=2 624 | 625 | x_resize=220 626 | y_resize=70 627 | 628 | Resize_xfactor=1.78 629 | Resize_yfactor=1.78 630 | 631 | ContLoop=0 632 | 633 | SwFounded=0 634 | 635 | BilateralOption=0 636 | 637 | TabLicensesFounded, ContLicensesFounded= FindLicenseNumber (gray, x_off, y_off, License, x_resize, y_resize, \ 638 | Resize_xfactor, Resize_yfactor, BilateralOption) 639 | 640 | 641 | print(TabLicensesFounded) 642 | print(ContLicensesFounded) 643 | 644 | ymax=-1 645 | contmax=0 646 | licensemax="" 647 | 648 | for z in range(len(TabLicensesFounded)): 649 | if ContLicensesFounded[z] > contmax: 650 | contmax=ContLicensesFounded[z] 651 | licensemax=TabLicensesFounded[z] 652 | 653 | if licensemax == License: 654 | print(License + " correctly recognized") 655 | TotHits+=1 656 | else: 657 | print(License + " Detected but not correctly recognized") 658 | TotFailures +=1 659 | ######################################### 660 | ######################################### 661 | print ("") 662 | lineaw=[] 663 | lineaw.append(License) 664 | lineaw.append(licensemax) 665 | lineaWrite =','.join(lineaw) 666 | lineaWrite=lineaWrite + "\n" 667 | w.write(lineaWrite) 668 | #if len(licensemax) < 2: continue 669 | SwFounded=0 670 | for i in range( len(TabLicensesmax)): 671 | if licensemax==TabLicensesmax[i]: 672 | ContLicensesmax[i]=ContLicensesmax[i]+1 673 | TimeEndLicensesmax[i]=time.time() 674 | SwFounded=1 675 | break 676 | if SwFounded ==0: 677 | TabLicensesmax.append(licensemax) 678 | ContLicensesmax.append(1) 679 | TimeIniLicensesmax.append(time.time()) 680 | TimeEndLicensesmax.append(time.time()) 681 | #y-limitY:y+limitY,x-limitX:x+limitX 682 | start_point=(x-limitX,y-limitY) 683 | end_point=(x+limitX, y+limitY) 684 | color=(0,0,255) 685 | # Using cv2.rectangle() method 686 | # Draw a rectangle with blue line borders of thickness of 2 px 687 | img = cv2.rectangle(img, start_point, end_point,(36,255,12), 1) 688 | # Put text 689 | text_location = (x, y) 690 | text_color = (255,255,255) 691 | cv2.putText(img, licensemax ,text_location 692 | , cv2.FONT_HERSHEY_SIMPLEX , 1 693 | , text_color, 2 ,cv2.LINE_AA) 694 | cv2.imshow('Frame', img) 695 | # Press Q on keyboard to exit 696 | if cv2.waitKey(25) & 0xFF == ord('q'): break 697 | # saving video 698 | video_writer.write(img) 699 | # a los 10 minutos = 600 segundos acaba 700 | if time.time() - TimeIni > TimeLimit: 701 | 702 | break 703 | 704 | #print(TabLicensesmax) 705 | #print(ContLicensesmax) 706 | #break 707 | cap.release() 708 | video_writer.release() 709 | cv2.destroyAllWindows() 710 | w.close() 711 | with open( "VIDEOLicenseSummary.txt" ,"w") as w1: 712 | for j in range (len(TabLicensesmax)): 713 | if ContLicensesmax[j] < LimitSnapshot:continue 714 | if TabLicensesmax[j] == "":continue 715 | Duration=TimeEndLicensesmax[j]-TimeIniLicensesmax[j] 716 | #Duration=Duration/ContLicensesmax[j] 717 | print(TabLicensesmax[j] + " snapshots: "+ str(ContLicensesmax[j]) + " Duration = "+str(Duration)) 718 | lineaw1=[] 719 | lineaw1.append(TabLicensesmax[j]) 720 | lineaw1.append(str(ContLicensesmax[j])) 721 | lineaw1.append(str(Duration)) 722 | lineaWrite =','.join(lineaw1) 723 | lineaWrite=lineaWrite + "\n" 724 | w1.write(lineaWrite) 725 | w1.close() 726 | print("") 727 | -------------------------------------------------------------------------------- /VIDEOGetNumberInternationalLicensePlate_Yolov8_Filters_PaddleOCR.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Mon Apr 25 20:1 7:29 2022 4 | 5 | @author: Alfonso Blanco 6 | """ 7 | ###################################################################### 8 | # PARAMETERS 9 | ##################################################################### 10 | ###################################################### 11 | # Video from https://github.com/anmspro/Traffic-Signal-Violation-Detection-System/tree/master/Resources 12 | dirVideo="Traffic IP Camera video.mp4" 13 | 14 | # in 14 minutes = 800 seconds finish 15 | TimeLimit=800 16 | # Max number of Snapshots to consider a image 17 | LimitSnapshot=3 18 | ############################################################### 19 | # video from https://github.com/hasaan21/Car-Number-Plate-Recognition-Sysytem 20 | #dirVideo="vid.mp4" 21 | 22 | #dirVideo="video12.mp4" 23 | #dirVideo="C:\\Car_Speed_Detection\\Comma.ai.Data.and.Model\\Comma.ai Model\\train.mp4" 24 | 25 | # from https://www.pexels.com/video/video-of-famous-landmark-on-a-city-during-daytime-1721294/ 26 | #dirVideo="Pexels Videos 1721294.mp4" 27 | #dirVideo="vidMio.mp4" 28 | #dirVideo="video12.mp4" 29 | 30 | 31 | dirnameYolo="best.pt" 32 | # https://docs.ultralytics.com/python/ 33 | from ultralytics import YOLO 34 | model = YOLO(dirnameYolo) 35 | class_list = model.model.names 36 | #print(class_list) 37 | 38 | 39 | ###################################################################### 40 | from paddleocr import PaddleOCR 41 | # Paddleocr supports Chinese, English, French, German, Korean and Japanese. 42 | # You can set the parameter `lang` as `ch`, `en`, `french`, `german`, `korean`, `japan` 43 | # to switch the language model in order. 44 | # https://pypi.org/project/paddleocr/ 45 | # 46 | # supress anoysing logging messages parameter show_log = False 47 | # https://github.com/PaddlePaddle/PaddleOCR/issues/2348 48 | ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log = False) # need to run only once to download and load model into memory 49 | 50 | import numpy as np 51 | 52 | import cv2 53 | 54 | import time 55 | 56 | TimeIni=time.time() 57 | 58 | 59 | X_resize=220 60 | Y_resize=70 61 | 62 | import os 63 | import re 64 | 65 | import imutils 66 | 67 | TabTotHitsFilter=[] 68 | TabTotFailuresFilter=[] 69 | 70 | for j in range(7): 71 | TabTotHitsFilter.append(0) 72 | TabTotFailuresFilter.append(0) 73 | ##################################################################### 74 | """ 75 | Copied from https://gist.github.com/endolith/334196bac1cac45a4893# 76 | 77 | other source: 78 | https://stackoverflow.com/questions/46084476/radon-transformation-in-python 79 | """ 80 | 81 | from skimage.transform import radon 82 | 83 | import numpy 84 | from numpy import mean, array, blackman, sqrt, square 85 | from numpy.fft import rfft 86 | 87 | try: 88 | # More accurate peak finding from 89 | # https://gist.github.com/endolith/255291#file-parabolic-py 90 | from parabolic import parabolic 91 | 92 | def argmax(x): 93 | return parabolic(x, numpy.argmax(x))[0] 94 | except ImportError: 95 | from numpy import argmax 96 | 97 | 98 | def GetRotationImage(image): 99 | 100 | 101 | I=image 102 | I = I - mean(I) # Demean; make the brightness extend above and below zero 103 | 104 | 105 | # Do the radon transform and display the result 106 | sinogram = radon(I) 107 | 108 | 109 | # Find the RMS value of each row and find "busiest" rotation, 110 | # where the transform is lined up perfectly with the alternating dark 111 | # text and white lines 112 | 113 | # rms_flat does no exist in recent versions 114 | #r = array([mlab.rms_flat(line) for line in sinogram.transpose()]) 115 | r = array([sqrt(mean(square(line))) for line in sinogram.transpose()]) 116 | rotation = argmax(r) 117 | #print('Rotation: {:.2f} degrees'.format(90 - rotation)) 118 | #plt.axhline(rotation, color='r') 119 | 120 | # Plot the busy row 121 | row = sinogram[:, rotation] 122 | N = len(row) 123 | 124 | # Take spectrum of busy row and find line spacing 125 | window = blackman(N) 126 | spectrum = rfft(row * window) 127 | 128 | frequency = argmax(abs(spectrum)) 129 | 130 | return rotation, spectrum, frequency 131 | 132 | ##################################################################### 133 | def ThresholdStable(image): 134 | # -*- coding: utf-8 -*- 135 | """ 136 | Created on Fri Aug 12 21:04:48 2022 137 | Author: Alfonso Blanco García 138 | 139 | Looks for the threshold whose variations keep the image STABLE 140 | (there are only small variations with the image of the previous 141 | threshold). 142 | Similar to the method followed in cv2.MSER 143 | https://datasmarts.net/es/como-usar-el-detector-de-puntos-clave-mser-en-opencv/https://felipemeganha.medium.com/detecting-handwriting-regions-with-opencv-and-python-ff0b1050aa4e 144 | """ 145 | 146 | thresholds=[] 147 | Repes=[] 148 | Difes=[] 149 | 150 | gray=image 151 | grayAnt=gray 152 | 153 | ContRepe=0 154 | threshold=0 155 | for i in range (255): 156 | 157 | ret, gray1=cv2.threshold(gray,i,255, cv2.THRESH_BINARY) 158 | Dife1 = grayAnt - gray1 159 | Dife2=np.sum(Dife1) 160 | if Dife2 < 0: Dife2=Dife2*-1 161 | Difes.append(Dife2) 162 | if Dife2<22000: # Case only image of license plate 163 | #if Dife2<60000: 164 | ContRepe=ContRepe+1 165 | 166 | threshold=i 167 | grayAnt=gray1 168 | continue 169 | if ContRepe > 0: 170 | 171 | thresholds.append(threshold) 172 | Repes.append(ContRepe) 173 | ContRepe=0 174 | grayAnt=gray1 175 | thresholdMax=0 176 | RepesMax=0 177 | for i in range(len(thresholds)): 178 | #print ("Threshold = " + str(thresholds[i])+ " Repeticiones = " +str(Repes[i])) 179 | if Repes[i] > RepesMax: 180 | RepesMax=Repes[i] 181 | thresholdMax=thresholds[i] 182 | 183 | #print(min(Difes)) 184 | #print ("Threshold Resultado= " + str(thresholdMax)+ " Repeticiones = " +str(RepesMax)) 185 | return thresholdMax 186 | 187 | 188 | 189 | # Copied from https://learnopencv.com/otsu-thresholding-with-opencv/ 190 | def OTSU_Threshold(image): 191 | # Set total number of bins in the histogram 192 | 193 | bins_num = 256 194 | 195 | # Get the image histogram 196 | 197 | hist, bin_edges = np.histogram(image, bins=bins_num) 198 | 199 | # Get normalized histogram if it is required 200 | 201 | #if is_normalized: 202 | 203 | hist = np.divide(hist.ravel(), hist.max()) 204 | 205 | 206 | 207 | # Calculate centers of bins 208 | 209 | bin_mids = (bin_edges[:-1] + bin_edges[1:]) / 2. 210 | 211 | 212 | # Iterate over all thresholds (indices) and get the probabilities w1(t), w2(t) 213 | 214 | weight1 = np.cumsum(hist) 215 | 216 | weight2 = np.cumsum(hist[::-1])[::-1] 217 | 218 | # Get the class means mu0(t) 219 | 220 | mean1 = np.cumsum(hist * bin_mids) / weight1 221 | 222 | # Get the class means mu1(t) 223 | 224 | mean2 = (np.cumsum((hist * bin_mids)[::-1]) / weight2[::-1])[::-1] 225 | 226 | inter_class_variance = weight1[:-1] * weight2[1:] * (mean1[:-1] - mean2[1:]) ** 2 227 | 228 | # Maximize the inter_class_variance function val 229 | 230 | index_of_max_val = np.argmax(inter_class_variance) 231 | 232 | threshold = bin_mids[:-1][index_of_max_val] 233 | 234 | #print("Otsu's algorithm implementation thresholding result: ", threshold) 235 | return threshold 236 | 237 | ######################################################################### 238 | def ApplyCLAHE(gray): 239 | #https://towardsdatascience.com/image-enhancement-techniques-using-opencv-and-python-9191d5c30d45 240 | 241 | gray_img_eqhist=cv2.equalizeHist(gray) 242 | hist=cv2.calcHist(gray_img_eqhist,[0],None,[256],[0,256]) 243 | clahe=cv2.createCLAHE(clipLimit=200,tileGridSize=(3,3)) 244 | gray_img_clahe=clahe.apply(gray_img_eqhist) 245 | return gray_img_clahe 246 | 247 | 248 | def GetPaddleOcr(img): 249 | 250 | """ 251 | Created on Tue Mar 7 10:31:09 2023 252 | 253 | @author: https://pypi.org/project/paddleocr/ (adapted from) 254 | """ 255 | 256 | cv2.imwrite("gray.jpg",img) 257 | img_path = 'gray.jpg' 258 | 259 | 260 | result = ocr.ocr(img_path, cls=True) 261 | # draw result 262 | #from PIL import Image 263 | result = result[0] 264 | #image = Image.open(img_path).convert('RGB') 265 | boxes = [line[0] for line in result] 266 | 267 | txts = [line[1][0] for line in result] 268 | scores = [line[1][1] for line in result] 269 | 270 | licensePlate= "" 271 | accuracy=0.0 272 | #print("RESULTADO "+ str(txts)) 273 | #print("confiabilidad "+ str(scores)) 274 | if len(txts) > 0: 275 | licensePlate= txts[0] 276 | accuracy=float(scores[0]) 277 | #print(licensePlate) 278 | #print(accuracy) 279 | 280 | return licensePlate, accuracy 281 | 282 | 283 | ######################################################################### 284 | def FindLicenseNumber (gray, x_offset, y_offset, License, x_resize, y_resize, \ 285 | Resize_xfactor, Resize_yfactor, BilateralOption): 286 | ######################################################################### 287 | 288 | 289 | gray = cv2.cvtColor(gray, cv2.COLOR_BGR2GRAY) 290 | 291 | TotHits=0 292 | 293 | X_resize=x_resize 294 | Y_resize=y_resize 295 | 296 | 297 | gray=cv2.resize(gray,None,fx=Resize_xfactor,fy=Resize_yfactor,interpolation=cv2.INTER_CUBIC) 298 | 299 | gray = cv2.resize(gray, (X_resize,Y_resize), interpolation = cv2.INTER_AREA) 300 | 301 | rotation, spectrum, frquency =GetRotationImage(gray) 302 | rotation=90 - rotation 303 | #print("Car" + str(NumberImageOrder) + " Brillo : " +str(SumBrightnessLic) + 304 | # " Desviacion : " + str(DesvLic)) 305 | if (rotation > 0 and rotation < 30) or (rotation < 0 and rotation > -30): 306 | print(License + " rotate "+ str(rotation)) 307 | gray=imutils.rotate(gray,angle=rotation) 308 | 309 | 310 | TabLicensesFounded=[] 311 | ContLicensesFounded=[] 312 | 313 | 314 | X_resize=x_resize 315 | Y_resize=y_resize 316 | print("gray.shape " + str(gray.shape)) 317 | Resize_xfactor=1.5 318 | Resize_yfactor=1.5 319 | 320 | 321 | TabLicensesFounded=[] 322 | ContLicensesFounded=[] 323 | AccuraccyMin=0.7 324 | TotHits=0 325 | 326 | # https://medium.com/practical-data-science-and-engineering/image-kernels-88162cb6585d 327 | #kernel = np.array([[0, -1, 0], 328 | # [-1, 5, -1], 329 | # [0, -1, 0]]) 330 | 331 | 332 | kernel = np.array([[0, -1, 0], 333 | [-1,10, -1], 334 | [0, -1, 0]]) 335 | dst = cv2.filter2D(gray, -1, kernel) 336 | img_concat = cv2.hconcat([gray, dst]) 337 | text, Accuraccy = GetPaddleOcr(img_concat) 338 | if Accuraccy < AccuraccyMin: 339 | text="" 340 | text = ''.join(char for char in text if char.isalnum()) 341 | text=ProcessText(text) 342 | if ProcessText(text) != "": 343 | 344 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 345 | if text==License: 346 | print(text + " Hit with image concat ") 347 | TotHits=TotHits+1 348 | else: 349 | print(License + " detected with Filter image concat "+ text) 350 | 351 | 352 | 353 | kernel = np.ones((3,3),np.float32)/90 354 | gray1 = cv2.filter2D(gray,-1,kernel) 355 | #gray_clahe = cv2.GaussianBlur(gray, (5, 5), 0) 356 | gray_img_clahe=ApplyCLAHE(gray1) 357 | 358 | th=OTSU_Threshold(gray_img_clahe) 359 | max_val=255 360 | 361 | ret, o3 = cv2.threshold(gray_img_clahe, th, max_val, cv2.THRESH_TOZERO) 362 | text, Accuraccy = GetPaddleOcr(o3) 363 | if Accuraccy < AccuraccyMin: 364 | text="" 365 | 366 | text = ''.join(char for char in text if char.isalnum()) 367 | text=ProcessText(text) 368 | if ProcessText(text) != "": 369 | 370 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 371 | if text==License: 372 | print(text + " Hit with CLAHE and THRESH_TOZERO" ) 373 | #TotHits=TotHits+1 374 | else: 375 | print(License + " detected with CLAHE and THRESH_TOZERO as "+ text) 376 | 377 | 378 | 379 | for z in range(5,6): 380 | 381 | kernel = np.array([[0,-1,0], [-1,z,-1], [0,-1,0]]) 382 | gray1 = cv2.filter2D(gray, -1, kernel) 383 | 384 | text, Accuraccy = GetPaddleOcr(gray1) 385 | if Accuraccy < AccuraccyMin: 386 | text="" 387 | 388 | text = ''.join(char for char in text if char.isalnum()) 389 | text=ProcessText(text) 390 | if ProcessText(text) != "": 391 | 392 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 393 | if text==License: 394 | print(text + " Hit with Sharpen filter z= " +str(z)) 395 | TotHits=TotHits+1 396 | else: 397 | print(License + " detected with Sharpen filter z= " +str(z) + " as "+ text) 398 | 399 | 400 | gray_img_clahe=ApplyCLAHE(gray) 401 | 402 | th=OTSU_Threshold(gray_img_clahe) 403 | max_val=255 404 | 405 | # Otsu's thresholding 406 | ret2,gray1 = cv2.threshold(gray,0,255,cv2.THRESH_TRUNC+cv2.THRESH_OTSU) 407 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 408 | text, Accuraccy = GetPaddleOcr(gray1) 409 | if Accuraccy < AccuraccyMin: 410 | text="" 411 | 412 | text = ''.join(char for char in text if char.isalnum()) 413 | text=ProcessText(text) 414 | if ProcessText(text) != "": 415 | #if Detect_International_LicensePlate(text)== 1: 416 | TabLicensesFounded, ContLicensesFounded =ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 417 | if text==License: 418 | print(text + " Hit with Otsu's thresholding of cv2 and THRESH_TRUNC" ) 419 | TotHits=TotHits+1 420 | else: 421 | print(License + " detected with Otsu's thresholding of cv2 and THRESH_TRUNC as "+ text) 422 | 423 | 424 | threshold=ThresholdStable(gray) 425 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TRUNC) 426 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 427 | text, Accuraccy = GetPaddleOcr(gray1) 428 | if Accuraccy < AccuraccyMin: 429 | text="" 430 | 431 | text = ''.join(char for char in text if char.isalnum()) 432 | text=ProcessText(text) 433 | if ProcessText(text) != "": 434 | #if Detect_International_LicensePlate(text)== 1: 435 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 436 | if text==License: 437 | print(text + " Hit with Stable and THRESH_TRUNC" ) 438 | TotHits=TotHits+1 439 | else: 440 | print(License + " detected with Stable and THRESH_TRUNC as "+ text) 441 | 442 | 443 | #################################################### 444 | # experimental formula based on the brightness 445 | # of the whole image 446 | #################################################### 447 | 448 | SumBrightness=np.sum(gray) 449 | threshold=(SumBrightness/177600.00) 450 | 451 | ##################################################### 452 | 453 | ret, gray1=cv2.threshold(gray,threshold,255, cv2.THRESH_TOZERO) 454 | #gray1 = cv2.GaussianBlur(gray1, (1, 1), 0) 455 | text, Accuraccy = GetPaddleOcr(gray1) 456 | if Accuraccy < AccuraccyMin: 457 | text="" 458 | 459 | text = ''.join(char for char in text if char.isalnum()) 460 | text=ProcessText(text) 461 | if ProcessText(text) != "": 462 | #if Detect_International_LicensePlate(text)== 1: 463 | ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text) 464 | if text==License: 465 | print(text + " Hit with Brightness and THRESH_TOZERO" ) 466 | TotHits=TotHits+1 467 | else: 468 | print(License + " detected with Brightness and THRESH_TOZERO as "+ text) 469 | 470 | 471 | ################################################################ 472 | return TabLicensesFounded, ContLicensesFounded 473 | 474 | 475 | def Detect_International_LicensePlate(Text): 476 | if len(Text) < 3 : return -1 477 | for i in range(len(Text)): 478 | if (Text[i] >= "0" and Text[i] <= "9" ) or (Text[i] >= "A" and Text[i] <= "Z" ): 479 | continue 480 | else: 481 | return -1 482 | 483 | return 1 484 | 485 | def ProcessText(text): 486 | 487 | if len(text) > 10: 488 | text=text[len(text)-10] 489 | if len(text) > 9: 490 | text=text[len(text)-9] 491 | else: 492 | if len(text) > 8: 493 | text=text[len(text)-8] 494 | else: 495 | 496 | if len(text) > 7: 497 | text=text[len(text)-7:] 498 | if Detect_International_LicensePlate(text)== -1: 499 | return "" 500 | else: 501 | return text 502 | 503 | def ApendTabLicensesFounded (TabLicensesFounded, ContLicensesFounded, text): 504 | 505 | SwFounded=0 506 | for i in range( len(TabLicensesFounded)): 507 | if text==TabLicensesFounded[i]: 508 | ContLicensesFounded[i]=ContLicensesFounded[i]+1 509 | SwFounded=1 510 | break 511 | if SwFounded==0: 512 | TabLicensesFounded.append(text) 513 | ContLicensesFounded.append(1) 514 | return TabLicensesFounded, ContLicensesFounded 515 | 516 | 517 | # ttps://medium.chom/@chanon.krittapholchai/build-object-detection-gui-with-yolov8-and-pysimplegui-76d5f5464d6c 518 | def DetectLicenseWithYolov8 (img): 519 | 520 | TabcropLicense=[] 521 | results = model.predict(img) 522 | 523 | result=results[0] 524 | 525 | xyxy= result.boxes.xyxy.numpy() 526 | confidence= result.boxes.conf.numpy() 527 | class_id= result.boxes.cls.numpy().astype(int) 528 | # Get Class name 529 | class_name = [class_list[x] for x in class_id] 530 | # Pack together for easy use 531 | sum_output = list(zip(class_name, confidence,xyxy)) 532 | # Copy image, in case that we need original image for something 533 | out_image = img.copy() 534 | for run_output in sum_output : 535 | # Unpack 536 | #print(class_name) 537 | label, con, box = run_output 538 | if label == "vehicle":continue 539 | cropLicense=out_image[int(box[1]):int(box[3]),int(box[0]):int(box[2])] 540 | #cv2.imshow("Crop", cropLicense) 541 | #cv2.waitKey(0) 542 | TabcropLicense.append(cropLicense) 543 | return TabcropLicense 544 | 545 | 546 | ########################################################### 547 | # MAIN 548 | ########################################################## 549 | 550 | TabLicensesmax=[] 551 | ContLicensesmax=[] 552 | TimeIniLicensesmax=[] 553 | TimeEndLicensesmax=[] 554 | 555 | ContDetected=0 556 | ContNoDetected=0 557 | TotHits=0 558 | TotFailures=0 559 | 560 | with open( "VIDEOLicenseResults.txt" ,"w") as w: 561 | 562 | cap = cv2.VideoCapture(dirVideo) 563 | 564 | while (cap.isOpened()): 565 | ret, img = cap.read() 566 | 567 | if ret != True: break 568 | 569 | else: 570 | print(ret) 571 | # cv2.imshow('original video', img) 572 | # 573 | # if cv2.waitKey(25) & 0xFF == ord('q'): 574 | # break 575 | gray=img 576 | 577 | 578 | License="License" 579 | TabImgSelect =DetectLicenseWithYolov8(gray) 580 | if TabImgSelect==[]: 581 | print(License + " NON DETECTED") 582 | ContNoDetected=ContNoDetected+1 583 | continue 584 | else: 585 | ContDetected=ContDetected+1 586 | print(License + " DETECTED ") 587 | 588 | gray=TabImgSelect[0] 589 | 590 | x_off=3 591 | y_off=2 592 | 593 | x_resize=220 594 | y_resize=70 595 | 596 | Resize_xfactor=1.78 597 | Resize_yfactor=1.78 598 | 599 | ContLoop=0 600 | 601 | SwFounded=0 602 | 603 | BilateralOption=0 604 | 605 | TabLicensesFounded, ContLicensesFounded= FindLicenseNumber (gray, x_off, y_off, License, x_resize, y_resize, \ 606 | Resize_xfactor, Resize_yfactor, BilateralOption) 607 | 608 | 609 | print(TabLicensesFounded) 610 | print(ContLicensesFounded) 611 | 612 | ymax=-1 613 | contmax=0 614 | licensemax="" 615 | 616 | for y in range(len(TabLicensesFounded)): 617 | if ContLicensesFounded[y] > contmax: 618 | contmax=ContLicensesFounded[y] 619 | licensemax=TabLicensesFounded[y] 620 | 621 | if licensemax == License: 622 | print(License + " correctly recognized") 623 | TotHits+=1 624 | else: 625 | print(License + " Detected but not correctly recognized") 626 | TotFailures +=1 627 | print ("") 628 | lineaw=[] 629 | lineaw.append(License) 630 | lineaw.append(licensemax) 631 | lineaWrite =','.join(lineaw) 632 | lineaWrite=lineaWrite + "\n" 633 | w.write(lineaWrite) 634 | #if len(licensemax) < 2: continue 635 | SwFounded=0 636 | for i in range( len(TabLicensesmax)): 637 | if licensemax==TabLicensesmax[i]: 638 | ContLicensesmax[i]=ContLicensesmax[i]+1 639 | TimeEndLicensesmax[i]=time.time() 640 | SwFounded=1 641 | break 642 | if SwFounded ==0: 643 | TabLicensesmax.append(licensemax) 644 | ContLicensesmax.append(1) 645 | TimeIniLicensesmax.append(time.time()) 646 | TimeEndLicensesmax.append(time.time()) 647 | 648 | # a los 10 minutos = 600 segundos acaba 649 | if time.time() - TimeIni > TimeLimit: 650 | 651 | break 652 | 653 | #print(TabLicensesmax) 654 | #print(ContLicensesmax) 655 | #break 656 | cap.release() 657 | w.close() 658 | with open( "VIDEOLicenseSummary.txt" ,"w") as w1: 659 | for j in range (len(TabLicensesmax)): 660 | if ContLicensesmax[j] < LimitSnapshot:continue 661 | if TabLicensesmax[j] == "":continue 662 | Duration=TimeEndLicensesmax[j]-TimeIniLicensesmax[j] 663 | #Duration=Duration/ContLicensesmax[j] 664 | print(TabLicensesmax[j] + " snapshots: "+ str(ContLicensesmax[j]) + " Duration = "+str(Duration)) 665 | lineaw1=[] 666 | lineaw1.append(TabLicensesmax[j]) 667 | lineaw1.append(str(ContLicensesmax[j])) 668 | lineaw1.append(str(Duration)) 669 | lineaWrite =','.join(lineaw1) 670 | lineaWrite=lineaWrite + "\n" 671 | w1.write(lineaWrite) 672 | w1.close() 673 | print("") 674 | -------------------------------------------------------------------------------- /WilburImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/WilburImage.jpg -------------------------------------------------------------------------------- /best.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/best.pt -------------------------------------------------------------------------------- /demonstration1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/demonstration1.mp4 -------------------------------------------------------------------------------- /demonstration2.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/demonstration2.mp4 -------------------------------------------------------------------------------- /licence_data.yaml: -------------------------------------------------------------------------------- 1 | train: ../train/images 2 | val: ../valid/images 3 | 4 | nc: 2 5 | names: ['license-plate', 'vehicle'] -------------------------------------------------------------------------------- /roboflow.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/roboflow.zip -------------------------------------------------------------------------------- /test6Training.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/test6Training.zip -------------------------------------------------------------------------------- /yolov8s.pt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ablanco1950/LicensePlate_Yolov8_Filters_PaddleOCR/f7c662490f91150a78c8f9f75268b1cdc161a352/yolov8s.pt --------------------------------------------------------------------------------