Patryk Szlagowski commited on
Commit
e121a44
·
0 Parent(s):

detect and read license plate

Browse files
Files changed (6) hide show
  1. .gitignore +4 -0
  2. detect.py +28 -0
  3. requirements.txt +8 -0
  4. vision/__init__.py +0 -0
  5. vision/enhancer.py +73 -0
  6. vision/vision.py +44 -0
.gitignore ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ .idea
2
+ *.iml
3
+ enhanced.*
4
+ vision/__pycache__/*
detect.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import getopt
2
+ import sys
3
+
4
+ from vision.enhancer import enhance
5
+ from vision.vision import detect_license_plate, read_license_plate_tesseract, \
6
+ read_license_plate_ml
7
+
8
+ if __name__ == "__main__":
9
+ opts, args = getopt.getopt(sys.argv[1:], "hi:", ["input="])
10
+ input_url = ""
11
+ for opt, arg in opts:
12
+ if opt == '-h':
13
+ print('detect.py -i input_image_url')
14
+ sys.exit()
15
+ elif opt in ("-i", "--input"):
16
+ input_url = arg
17
+
18
+ print("detecting license plate from image: " + input_url)
19
+
20
+ crop = detect_license_plate(input_url)
21
+ enhanced_path = "./enhanced.png"
22
+
23
+ with enhance(crop) as enhanced:
24
+ enhanced.save(enhanced_path)
25
+
26
+ print("license plate trocr: " + read_license_plate_ml(enhanced_path))
27
+ print("license plate tesseract: " + read_license_plate_tesseract(enhanced_path))
28
+
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ pillow
2
+ transformers
3
+ yolov5
4
+ pytesseract
5
+ opencv-python
6
+ numpy
7
+ torch
8
+ sympy
vision/__init__.py ADDED
File without changes
vision/enhancer.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import cv2
2
+ import numpy as np
3
+ from PIL import ImageOps, ImageEnhance, Image
4
+
5
+
6
+ def enhance(crop: Image.Image) -> Image.Image:
7
+ """
8
+ prepare crop for ocr - grayscale, contrast, sharpness, resize
9
+ :param crop: image to prepare
10
+ :rtype: Image.Image
11
+ :return: processed image
12
+ """
13
+ image = ImageOps.grayscale(crop)
14
+ image = ImageEnhance.Contrast(image).enhance(5)
15
+ image = ImageEnhance.Sharpness(image).enhance(5)
16
+ image = ImageOps.scale(image, 3.5)
17
+
18
+ image = _remove_dark_frame(image)
19
+ image = _remove_shades(image)
20
+ return image
21
+
22
+
23
+ def _remove_dark_frame(img: Image.Image) -> Image.Image:
24
+ # Convert PIL Image to OpenCV format
25
+ open_cv_image = np.array(img.convert('RGB'))
26
+ open_cv_image = cv2.cvtColor(open_cv_image, cv2.COLOR_RGB2BGR)
27
+
28
+ # Convert to grayscale
29
+ gray = cv2.cvtColor(open_cv_image, cv2.COLOR_BGR2GRAY)
30
+
31
+ # Blur the image to reduce noise
32
+ blurred = cv2.GaussianBlur(gray, (5, 5), 0)
33
+
34
+ # Apply a binary threshold after blurring
35
+ _, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
36
+
37
+ # Find contours from the binary image
38
+ cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
39
+ cnts = cnts[0] if len(cnts) == 2 else cnts[1]
40
+
41
+ # Assume the largest contour is the frame
42
+ c = max(cnts, key=cv2.contourArea)
43
+
44
+ # Find the bounding box coordinates from the contour
45
+ x, y, w, h = cv2.boundingRect(c)
46
+
47
+ # Crop the original image using the bounding box coordinates
48
+ cropped = open_cv_image[y:y+h, x:x+w]
49
+
50
+ # Convert back to PIL format
51
+ cropped_pil = Image.fromarray(cv2.cvtColor(cropped, cv2.COLOR_BGR2RGB))
52
+ return cropped_pil
53
+
54
+ def _remove_shades(image: Image.Image, threshold: int = 64) -> Image.Image:
55
+ """
56
+ Remove shades between white and black from an image using PIL.
57
+
58
+ Parameters:
59
+ - image_path: The path to the image.
60
+ - threshold: The cutoff for determining whether a pixel should be white or black.
61
+
62
+ Returns:
63
+ - A new PIL Image object with shades removed.
64
+ """
65
+ # Convert the image to grayscale
66
+ gray_img = image.convert("L")
67
+
68
+ # Apply the threshold
69
+ # All pixels value > threshold will be set to 255 (white)
70
+ # All pixels value <= threshold will be set to 0 (black)
71
+ binary_img = gray_img.point(lambda x: 255 if x > threshold else 0, '1')
72
+
73
+ return binary_img
vision/vision.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yolov5
2
+ from PIL import Image
3
+ from pytesseract import pytesseract
4
+ from transformers import TrOCRProcessor, VisionEncoderDecoderModel
5
+
6
+
7
+ def detect_license_plate(image_path: str, save_crops=False, save_dir="/tmp/crops") -> Image.Image:
8
+ """
9
+ Detect license plate from image_path
10
+ :param image_path: image_path path
11
+ :param save_crops: save crops to filesystem
12
+ :param save_dir: directory to save crops
13
+ :rtype: :py:class:`~PIL.Image.Image`
14
+ :returns: An :py:class:`~PIL.Image.Image` object.
15
+ """
16
+ # load model
17
+ model = yolov5.load('keremberke/yolov5n-license-plate')
18
+
19
+ # set model parameters
20
+ model.conf = 0.25 # NMS confidence threshold
21
+ model.iou = 0.45 # NMS IoU threshold
22
+ model.agnostic = False # NMS class-agnostic
23
+ model.multi_label = False # NMS multiple labels per box
24
+ model.max_det = 1 # maximum number of detections per image
25
+
26
+ # perform inference
27
+ results = model(image_path, size=640)
28
+ crops = results.crop(save=save_crops, save_dir=save_dir)
29
+
30
+ return Image.fromarray(crops[0]['im'][..., ::-1])
31
+
32
+
33
+ def read_license_plate_tesseract(image_path) -> str:
34
+ return pytesseract.image_to_string(image_path,
35
+ config='--psm 7 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ')
36
+
37
+
38
+ def read_license_plate_ml(image_path: str) -> str:
39
+ processor = TrOCRProcessor.from_pretrained('microsoft/trocr-base-printed')
40
+ model = VisionEncoderDecoderModel.from_pretrained('microsoft/trocr-base-printed')
41
+ pixel_values = processor(images=Image.open(image_path).convert("RGB"), return_tensors="pt").pixel_values
42
+
43
+ generated_ids = model.generate(pixel_values)
44
+ return processor.batch_decode(generated_ids, skip_special_tokens=True)[0]