Spaces:
Configuration error
Configuration error
Patryk Szlagowski
commited on
Commit
·
e121a44
0
Parent(s):
detect and read license plate
Browse files- .gitignore +4 -0
- detect.py +28 -0
- requirements.txt +8 -0
- vision/__init__.py +0 -0
- vision/enhancer.py +73 -0
- 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]
|