From 1be297bc7a009e72fdd2cbe8b43e4a7561db7a3c Mon Sep 17 00:00:00 2001 From: Testbild Date: Tue, 12 Oct 2021 10:16:43 +0200 Subject: [PATCH 1/2] Added voc2coco for rotated Bbox Allows to use roLabelImage https://github.com/cgvict/roLabelImg to make rotated object detection boxes in PASCAL VOC format and convert them to JSON for e.g. https://github.com/NVIDIA/retinanet-examples --- voc2coco_rotate.py | 152 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 voc2coco_rotate.py diff --git a/voc2coco_rotate.py b/voc2coco_rotate.py new file mode 100644 index 0000000..a3a776b --- /dev/null +++ b/voc2coco_rotate.py @@ -0,0 +1,152 @@ +#!/usr/bin/python + +# pip install lxml + +import sys +import os +import json +import xml.etree.ElementTree as ET +import glob + +START_BOUNDING_BOX_ID = 1 +PRE_DEFINE_CATEGORIES = None +# If necessary, pre-define category and its id +# PRE_DEFINE_CATEGORIES = {"aeroplane": 1, "bicycle": 2, "bird": 3, "boat": 4, +# "bottle":5, "bus": 6, "car": 7, "cat": 8, "chair": 9, +# "cow": 10, "diningtable": 11, "dog": 12, "horse": 13, +# "motorbike": 14, "person": 15, "pottedplant": 16, +# "sheep": 17, "sofa": 18, "train": 19, "tvmonitor": 20} + + +def get(root, name): + vars = root.findall(name) + return vars + + +def get_and_check(root, name, length): + vars = root.findall(name) + if len(vars) == 0: + raise ValueError("Can not find %s in %s." % (name, root.tag)) + if length > 0 and len(vars) != length: + raise ValueError( + "The size of %s is supposed to be %d, but is %d." + % (name, length, len(vars)) + ) + if length == 1: + vars = vars[0] + return vars + + +def get_filename_as_int(filename): + try: + filename = filename.replace("\\", "/") + filename = os.path.splitext(os.path.basename(filename))[0] + return int(filename) + except: + raise ValueError("Filename %s is supposed to be an integer." % (filename)) + + +def get_categories(xml_files): + """Generate category name to id mapping from a list of xml files. + + Arguments: + xml_files {list} -- A list of xml file paths. + + Returns: + dict -- category name to id mapping. + """ + classes_names = [] + for xml_file in xml_files: + tree = ET.parse(xml_file) + root = tree.getroot() + for member in root.findall("object"): + classes_names.append(member[0].text) + classes_names = list(set(classes_names)) + classes_names.sort() + return {name: i for i, name in enumerate(classes_names)} + + +def convert(xml_files, json_file): + json_dict = {"images": [], "type": "instances", "annotations": [], "categories": []} + if PRE_DEFINE_CATEGORIES is not None: + categories = PRE_DEFINE_CATEGORIES + else: + categories = get_categories(xml_files) + bnd_id = START_BOUNDING_BOX_ID + for xml_file in xml_files: + tree = ET.parse(xml_file) + root = tree.getroot() + path = get(root, "path") + if len(path) == 1: + filename = os.path.basename(path[0].text) + elif len(path) == 0: + filename = get_and_check(root, "filename", 1).text + else: + raise ValueError("%d paths found in %s" % (len(path), xml_file)) + ## The filename must be a number + image_id = get_filename_as_int(filename) + size = get_and_check(root, "size", 1) + width = int(get_and_check(size, "width", 1).text) + height = int(get_and_check(size, "height", 1).text) + image = { + "file_name": filename, + "height": height, + "width": width, + "id": image_id, + } + json_dict["images"].append(image) + ## Currently we do not support segmentation. + # segmented = get_and_check(root, 'segmented', 1).text + # assert segmented == '0' + for obj in get(root, "object"): + category = get_and_check(obj, "name", 1).text + if category not in categories: + new_id = len(categories) + categories[category] = new_id + category_id = categories[category] + bndbox = get_and_check(obj, "robndbox", 1) + xmin = int(float(get_and_check(bndbox, "cx", 1).text)) - 1 + ymin = int(float(get_and_check(bndbox, "cy", 1).text)) - 1 + width = int(float(get_and_check(bndbox, "w", 1).text)) + height = int(float(get_and_check(bndbox, "h", 1).text)) + theta = round(float(get_and_check(bndbox, "angle", 1).text),2) + + ann = { + "area": width * height, + "iscrowd": 0, + "image_id": image_id, + "bbox": [xmin, ymin, width, height, theta], + "category_id": category_id, + "id": bnd_id, + "ignore": 0, + "segmentation": [], + } + json_dict["annotations"].append(ann) + bnd_id = bnd_id + 1 + + for cate, cid in categories.items(): + cat = {"supercategory": "none", "id": cid, "name": cate} + json_dict["categories"].append(cat) + + os.makedirs(os.path.dirname(json_file), exist_ok=True) + json_fp = open(json_file, "w") + json_str = json.dumps(json_dict) + json_fp.write(json_str) + json_fp.close() + + +if __name__ == "__main__": + import argparse + + parser = argparse.ArgumentParser( + description="Convert Pascal VOC annotation to COCO format." + ) + parser.add_argument("xml_dir", help="Directory path to xml files.", type=str) + parser.add_argument("json_file", help="Output COCO format json file.", type=str) + args = parser.parse_args() + xml_files = glob.glob(os.path.join(args.xml_dir, "*.xml")) + + # If you want to do train/test split, you can pass a subset of xml files to convert function. + print("Number of xml files: {}".format(len(xml_files))) + convert(xml_files, args.json_file) + print("Success: {}".format(args.json_file)) From 8e5f7ded99b523dca20e12dd596aff1aa3a8f478 Mon Sep 17 00:00:00 2001 From: Testbild Date: Tue, 12 Oct 2021 10:17:37 +0200 Subject: [PATCH 2/2] Added voc2coco_rotate.py --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c2739e0..1940afd 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,9 @@ Then you can run the `voc2coco.py` script to generate a COCO data formatted JSON file for you. ``` python voc2coco.py ./data/VOC/Annotations ./data/coco/output.json +python voc2coco_rotate.py ./data/VOC/Annotations ./data/coco/output.json ``` Then you can run the following Jupyter notebook to visualize the coco annotations. `COCO_Image_Viewer.ipynb` -Further instruction on how to create your own datasets, read the [tutorial](https://www.dlology.com/blog/how-to-create-custom-coco-data-set-for-object-detection/). \ No newline at end of file +Further instruction on how to create your own datasets, read the [tutorial](https://www.dlology.com/blog/how-to-create-custom-coco-data-set-for-object-detection/).