Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/number_plate_redaction.py
640 views
1
import io
2
import json
3
import math
4
import re
5
from itertools import combinations
6
from pathlib import Path
7
8
from PIL import Image, ImageFilter
9
10
from plate_recognition import draw_bb, parse_arguments, recognition_api
11
12
13
def blur(im, blur_amount, api_res, ignore_no_bb=False, ignore_list=None):
14
for res in api_res.get("results", []):
15
if ignore_no_bb and res["vehicle"]["score"] == 0.0:
16
continue
17
18
if ignore_list:
19
skip_blur = False
20
for ignore_regex in ignore_list:
21
if re.search(ignore_regex, res["plate"]):
22
skip_blur = True
23
break
24
if skip_blur:
25
continue
26
27
b = res["box"]
28
width, height = b["xmax"] - b["xmin"], b["ymax"] - b["ymin"]
29
crop_box = (b["xmin"], b["ymin"], b["xmax"], b["ymax"])
30
ic = im.crop(crop_box)
31
32
# Increase amount of blur with size of bounding box
33
blur_image = ic.filter(
34
ImageFilter.GaussianBlur(
35
radius=math.sqrt(width * height) * 0.3 * blur_amount / 10
36
)
37
)
38
im.paste(blur_image, crop_box)
39
return im
40
41
42
def bb_iou(a, b):
43
# determine the (x, y)-coordinates of the intersection rectangle
44
x_a = max(a["xmin"], b["xmin"])
45
y_a = max(a["ymin"], b["ymin"])
46
x_b = min(a["xmax"], b["xmax"])
47
y_b = min(a["ymax"], b["ymax"])
48
49
# compute the area of both the prediction and ground-truth
50
# rectangles
51
area_a = (a["xmax"] - a["xmin"]) * (a["ymax"] - a["ymin"])
52
area_b = (b["xmax"] - b["xmin"]) * (b["ymax"] - b["ymin"])
53
54
# compute the area of intersection rectangle
55
area_inter = max(0, x_b - x_a) * max(0, y_b - y_a)
56
return area_inter / float(max(area_a + area_b - area_inter, 1))
57
58
59
def clean_objs(objects, threshold=0.1):
60
# Only keep the ones with best score or no overlap
61
for o1, o2 in combinations(objects, 2):
62
if (
63
"remove" in o1
64
or "remove" in o2
65
or bb_iou(o1["box"], o2["box"]) <= threshold
66
):
67
continue
68
if o1["score"] > o2["score"]:
69
o2["remove"] = True
70
else:
71
o1["remove"] = True
72
return [x for x in objects if "remove" not in x]
73
74
75
def merge_results(images):
76
result = dict(results=[])
77
for data in images:
78
for item in data["prediction"]["results"]:
79
result["results"].append(item)
80
for b in [item["box"], item["vehicle"].get("box", {})]:
81
b["ymin"] += data["y"]
82
b["xmin"] += data["x"]
83
b["ymax"] += data["y"]
84
b["xmax"] += data["x"]
85
result["results"] = clean_objs(result["results"])
86
return result
87
88
89
def inside(a, b):
90
return (
91
a["xmin"] > b["xmin"]
92
and a["ymin"] > b["ymin"]
93
and a["xmax"] < b["xmax"]
94
and a["ymax"] < b["ymax"]
95
)
96
97
98
def post_processing(results):
99
new_list = []
100
for item in results["results"]:
101
if item["score"] < 0.2 and any(
102
[inside(x["box"], item["box"]) for x in results["results"] if x != item]
103
):
104
continue
105
new_list.append(item)
106
results["results"] = new_list
107
return results
108
109
110
def process_image(path, args, i):
111
config = dict(
112
threshold_d=args.detection_threshold,
113
threshold_o=args.ocr_threshold,
114
mode="redaction",
115
)
116
117
# Predictions
118
source_im = Image.open(path)
119
if source_im.mode != "RGB":
120
source_im = source_im.convert("RGB")
121
images = [((0, 0), source_im)] # Entire image
122
# Top left and top right crops
123
if args.split_image:
124
y = 0
125
win_size = 0.55
126
width, height = source_im.width * win_size, source_im.height * win_size
127
for x in [0, int((1 - win_size) * source_im.width)]:
128
images.append(((x, y), source_im.crop((x, y, x + width, y + height))))
129
130
# Inference
131
results = []
132
for (x, y), im in images:
133
im_bytes = io.BytesIO()
134
im.save(im_bytes, "JPEG", quality=95)
135
im_bytes.seek(0)
136
im_results = recognition_api(
137
im_bytes, args.regions, args.api_key, args.sdk_url, config=config
138
)
139
results.append(dict(prediction=im_results, x=x, y=y))
140
results = post_processing(merge_results(results))
141
results["filename"] = Path(path).name
142
143
# Set bounding box padding
144
for item in results["results"]:
145
# Decrease padding size for large bounding boxes
146
b = item["box"]
147
width, height = b["xmax"] - b["xmin"], b["ymax"] - b["ymin"]
148
padding_x = int(max(0, width * (0.3 * math.exp(-10 * width / source_im.width))))
149
padding_y = int(
150
max(0, height * (0.3 * math.exp(-10 * height / source_im.height)))
151
)
152
b["xmin"] = b["xmin"] - padding_x
153
b["ymin"] = b["ymin"] - padding_y
154
b["xmax"] = b["xmax"] + padding_x
155
b["ymax"] = b["ymax"] + padding_y
156
157
if args.show_boxes or args.save_blurred:
158
im = blur(
159
source_im,
160
5,
161
results,
162
ignore_no_bb=args.ignore_no_bb,
163
ignore_list=args.ignore_regexp,
164
)
165
166
if args.show_boxes:
167
im.show()
168
if args.save_blurred:
169
filename = Path(path)
170
im.save(filename.parent / (f"{filename.stem}_blurred{filename.suffix}"))
171
if 0:
172
draw_bb(source_im, results["results"]).show()
173
return results
174
175
176
def custom_args(parser):
177
parser.epilog += "To analyse the image for redaction: python number_plate_redaction.py --api-key MY_API_KEY --split-image /tmp/car.jpg"
178
parser.add_argument(
179
"--split-image",
180
action="store_true",
181
help="Do extra lookups on parts of the image. Useful on high resolution images.",
182
)
183
parser.add_argument(
184
"--show-boxes", action="store_true", help="Display the resulting blurred image."
185
)
186
parser.add_argument(
187
"--save-blurred",
188
action="store_true",
189
help="Blur license plates and save image in filename_blurred.jpg.",
190
)
191
parser.add_argument(
192
"--ignore-regexp",
193
action="append",
194
help="Plate regex to ignore during blur. Usually invalid plate numbers.",
195
)
196
parser.add_argument(
197
"--ignore-no-bb",
198
action="store_true",
199
help="Ignore detections without a vehicle bounding box during blur.",
200
)
201
parser.add_argument(
202
"--detection-threshold",
203
type=float,
204
default=0.2,
205
help="Keep all detections above this threshold. Between 0 and 1.",
206
)
207
parser.add_argument(
208
"--ocr-threshold",
209
type=float,
210
default=0.5,
211
help="Keep all plates if the characters reading score is above this threshold. Between 0 and 1.",
212
)
213
214
215
def main():
216
args = parse_arguments(custom_args)
217
result = []
218
for i, path in enumerate(args.files):
219
if Path(path).is_file():
220
result.append(process_image(path, args, i))
221
if 0:
222
for im_result in result:
223
for i, x in enumerate(im_result["results"]):
224
im_result["results"][i] = dict(
225
dscore=x["dscore"], score=x["score"], box=x["box"]
226
)
227
print(json.dumps(result, indent=2))
228
229
230
if __name__ == "__main__":
231
main()
232
233