Path: blob/master/number_plate_redaction.py
640 views
import io1import json2import math3import re4from itertools import combinations5from pathlib import Path67from PIL import Image, ImageFilter89from plate_recognition import draw_bb, parse_arguments, recognition_api101112def blur(im, blur_amount, api_res, ignore_no_bb=False, ignore_list=None):13for res in api_res.get("results", []):14if ignore_no_bb and res["vehicle"]["score"] == 0.0:15continue1617if ignore_list:18skip_blur = False19for ignore_regex in ignore_list:20if re.search(ignore_regex, res["plate"]):21skip_blur = True22break23if skip_blur:24continue2526b = res["box"]27width, height = b["xmax"] - b["xmin"], b["ymax"] - b["ymin"]28crop_box = (b["xmin"], b["ymin"], b["xmax"], b["ymax"])29ic = im.crop(crop_box)3031# Increase amount of blur with size of bounding box32blur_image = ic.filter(33ImageFilter.GaussianBlur(34radius=math.sqrt(width * height) * 0.3 * blur_amount / 1035)36)37im.paste(blur_image, crop_box)38return im394041def bb_iou(a, b):42# determine the (x, y)-coordinates of the intersection rectangle43x_a = max(a["xmin"], b["xmin"])44y_a = max(a["ymin"], b["ymin"])45x_b = min(a["xmax"], b["xmax"])46y_b = min(a["ymax"], b["ymax"])4748# compute the area of both the prediction and ground-truth49# rectangles50area_a = (a["xmax"] - a["xmin"]) * (a["ymax"] - a["ymin"])51area_b = (b["xmax"] - b["xmin"]) * (b["ymax"] - b["ymin"])5253# compute the area of intersection rectangle54area_inter = max(0, x_b - x_a) * max(0, y_b - y_a)55return area_inter / float(max(area_a + area_b - area_inter, 1))565758def clean_objs(objects, threshold=0.1):59# Only keep the ones with best score or no overlap60for o1, o2 in combinations(objects, 2):61if (62"remove" in o163or "remove" in o264or bb_iou(o1["box"], o2["box"]) <= threshold65):66continue67if o1["score"] > o2["score"]:68o2["remove"] = True69else:70o1["remove"] = True71return [x for x in objects if "remove" not in x]727374def merge_results(images):75result = dict(results=[])76for data in images:77for item in data["prediction"]["results"]:78result["results"].append(item)79for b in [item["box"], item["vehicle"].get("box", {})]:80b["ymin"] += data["y"]81b["xmin"] += data["x"]82b["ymax"] += data["y"]83b["xmax"] += data["x"]84result["results"] = clean_objs(result["results"])85return result868788def inside(a, b):89return (90a["xmin"] > b["xmin"]91and a["ymin"] > b["ymin"]92and a["xmax"] < b["xmax"]93and a["ymax"] < b["ymax"]94)959697def post_processing(results):98new_list = []99for item in results["results"]:100if item["score"] < 0.2 and any(101[inside(x["box"], item["box"]) for x in results["results"] if x != item]102):103continue104new_list.append(item)105results["results"] = new_list106return results107108109def process_image(path, args, i):110config = dict(111threshold_d=args.detection_threshold,112threshold_o=args.ocr_threshold,113mode="redaction",114)115116# Predictions117source_im = Image.open(path)118if source_im.mode != "RGB":119source_im = source_im.convert("RGB")120images = [((0, 0), source_im)] # Entire image121# Top left and top right crops122if args.split_image:123y = 0124win_size = 0.55125width, height = source_im.width * win_size, source_im.height * win_size126for x in [0, int((1 - win_size) * source_im.width)]:127images.append(((x, y), source_im.crop((x, y, x + width, y + height))))128129# Inference130results = []131for (x, y), im in images:132im_bytes = io.BytesIO()133im.save(im_bytes, "JPEG", quality=95)134im_bytes.seek(0)135im_results = recognition_api(136im_bytes, args.regions, args.api_key, args.sdk_url, config=config137)138results.append(dict(prediction=im_results, x=x, y=y))139results = post_processing(merge_results(results))140results["filename"] = Path(path).name141142# Set bounding box padding143for item in results["results"]:144# Decrease padding size for large bounding boxes145b = item["box"]146width, height = b["xmax"] - b["xmin"], b["ymax"] - b["ymin"]147padding_x = int(max(0, width * (0.3 * math.exp(-10 * width / source_im.width))))148padding_y = int(149max(0, height * (0.3 * math.exp(-10 * height / source_im.height)))150)151b["xmin"] = b["xmin"] - padding_x152b["ymin"] = b["ymin"] - padding_y153b["xmax"] = b["xmax"] + padding_x154b["ymax"] = b["ymax"] + padding_y155156if args.show_boxes or args.save_blurred:157im = blur(158source_im,1595,160results,161ignore_no_bb=args.ignore_no_bb,162ignore_list=args.ignore_regexp,163)164165if args.show_boxes:166im.show()167if args.save_blurred:168filename = Path(path)169im.save(filename.parent / (f"{filename.stem}_blurred{filename.suffix}"))170if 0:171draw_bb(source_im, results["results"]).show()172return results173174175def custom_args(parser):176parser.epilog += "To analyse the image for redaction: python number_plate_redaction.py --api-key MY_API_KEY --split-image /tmp/car.jpg"177parser.add_argument(178"--split-image",179action="store_true",180help="Do extra lookups on parts of the image. Useful on high resolution images.",181)182parser.add_argument(183"--show-boxes", action="store_true", help="Display the resulting blurred image."184)185parser.add_argument(186"--save-blurred",187action="store_true",188help="Blur license plates and save image in filename_blurred.jpg.",189)190parser.add_argument(191"--ignore-regexp",192action="append",193help="Plate regex to ignore during blur. Usually invalid plate numbers.",194)195parser.add_argument(196"--ignore-no-bb",197action="store_true",198help="Ignore detections without a vehicle bounding box during blur.",199)200parser.add_argument(201"--detection-threshold",202type=float,203default=0.2,204help="Keep all detections above this threshold. Between 0 and 1.",205)206parser.add_argument(207"--ocr-threshold",208type=float,209default=0.5,210help="Keep all plates if the characters reading score is above this threshold. Between 0 and 1.",211)212213214def main():215args = parse_arguments(custom_args)216result = []217for i, path in enumerate(args.files):218if Path(path).is_file():219result.append(process_image(path, args, i))220if 0:221for im_result in result:222for i, x in enumerate(im_result["results"]):223im_result["results"][i] = dict(224dscore=x["dscore"], score=x["score"], box=x["box"]225)226print(json.dumps(result, indent=2))227228229if __name__ == "__main__":230main()231232233