| | |
| | import functools |
| | import json |
| | import logging |
| | import multiprocessing as mp |
| | import os |
| | from itertools import chain |
| |
|
| | import numpy as np |
| | import pycocotools.mask as mask_util |
| |
|
| | from detectron2.structures import BoxMode |
| | from detectron2.utils.comm import get_world_size |
| | from detectron2.utils.file_io import PathManager |
| | from detectron2.utils.logger import setup_logger |
| | from PIL import Image |
| |
|
| | try: |
| | import cv2 |
| | except ImportError: |
| | |
| | pass |
| |
|
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| |
|
| | def _get_cityscapes_files(image_dir, gt_dir): |
| | files = [] |
| | |
| | cities = PathManager.ls(image_dir) |
| | logger.info(f"{len(cities)} cities found in '{image_dir}'.") |
| | for city in cities: |
| | city_img_dir = os.path.join(image_dir, city) |
| | city_gt_dir = os.path.join(gt_dir, city) |
| | for basename in PathManager.ls(city_img_dir): |
| | image_file = os.path.join(city_img_dir, basename) |
| |
|
| | suffix = "leftImg8bit.png" |
| | assert basename.endswith(suffix), basename |
| | basename = basename[: -len(suffix)] |
| |
|
| | instance_file = os.path.join( |
| | city_gt_dir, basename + "gtFine_instanceIds.png" |
| | ) |
| | label_file = os.path.join(city_gt_dir, basename + "gtFine_labelIds.png") |
| | json_file = os.path.join(city_gt_dir, basename + "gtFine_polygons.json") |
| |
|
| | files.append((image_file, instance_file, label_file, json_file)) |
| | assert len(files), "No images found in {}".format(image_dir) |
| | for f in files[0]: |
| | assert PathManager.isfile(f), f |
| | return files |
| |
|
| |
|
| | def load_cityscapes_instances(image_dir, gt_dir, from_json=True, to_polygons=True): |
| | """ |
| | Args: |
| | image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train". |
| | gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train". |
| | from_json (bool): whether to read annotations from the raw json file or the png files. |
| | to_polygons (bool): whether to represent the segmentation as polygons |
| | (COCO's format) instead of masks (cityscapes's format). |
| | |
| | Returns: |
| | list[dict]: a list of dicts in Detectron2 standard format. (See |
| | `Using Custom Datasets </tutorials/datasets.html>`_ ) |
| | """ |
| | if from_json: |
| | assert to_polygons, ( |
| | "Cityscapes's json annotations are in polygon format. " |
| | "Converting to mask format is not supported now." |
| | ) |
| | files = _get_cityscapes_files(image_dir, gt_dir) |
| |
|
| | logger.info("Preprocessing cityscapes annotations ...") |
| | |
| | |
| | pool = mp.Pool(processes=max(mp.cpu_count() // get_world_size() // 2, 4)) |
| |
|
| | ret = pool.map( |
| | functools.partial( |
| | _cityscapes_files_to_dict, from_json=from_json, to_polygons=to_polygons |
| | ), |
| | files, |
| | ) |
| | logger.info("Loaded {} images from {}".format(len(ret), image_dir)) |
| |
|
| | |
| | from cityscapesscripts.helpers.labels import labels |
| |
|
| | labels = [l for l in labels if l.hasInstances and not l.ignoreInEval] |
| | dataset_id_to_contiguous_id = {l.id: idx for idx, l in enumerate(labels)} |
| | for dict_per_image in ret: |
| | for anno in dict_per_image["annotations"]: |
| | anno["category_id"] = dataset_id_to_contiguous_id[anno["category_id"]] |
| | return ret |
| |
|
| |
|
| | def load_cityscapes_semantic(image_dir, gt_dir): |
| | """ |
| | Args: |
| | image_dir (str): path to the raw dataset. e.g., "~/cityscapes/leftImg8bit/train". |
| | gt_dir (str): path to the raw annotations. e.g., "~/cityscapes/gtFine/train". |
| | |
| | Returns: |
| | list[dict]: a list of dict, each has "file_name" and |
| | "sem_seg_file_name". |
| | """ |
| | ret = [] |
| | |
| | gt_dir = PathManager.get_local_path(gt_dir) |
| | for image_file, _, label_file, json_file in _get_cityscapes_files( |
| | image_dir, gt_dir |
| | ): |
| | label_file = label_file.replace("labelIds", "labelTrainIds") |
| |
|
| | with PathManager.open(json_file, "r") as f: |
| | jsonobj = json.load(f) |
| | ret.append( |
| | { |
| | "file_name": image_file, |
| | "sem_seg_file_name": label_file, |
| | "height": jsonobj["imgHeight"], |
| | "width": jsonobj["imgWidth"], |
| | } |
| | ) |
| | assert len(ret), f"No images found in {image_dir}!" |
| | assert PathManager.isfile( |
| | ret[0]["sem_seg_file_name"] |
| | ), "Please generate labelTrainIds.png with cityscapesscripts/preparation/createTrainIdLabelImgs.py" |
| | return ret |
| |
|
| |
|
| | def _cityscapes_files_to_dict(files, from_json, to_polygons): |
| | """ |
| | Parse cityscapes annotation files to a instance segmentation dataset dict. |
| | |
| | Args: |
| | files (tuple): consists of (image_file, instance_id_file, label_id_file, json_file) |
| | from_json (bool): whether to read annotations from the raw json file or the png files. |
| | to_polygons (bool): whether to represent the segmentation as polygons |
| | (COCO's format) instead of masks (cityscapes's format). |
| | |
| | Returns: |
| | A dict in Detectron2 Dataset format. |
| | """ |
| | from cityscapesscripts.helpers.labels import id2label, name2label |
| |
|
| | image_file, instance_id_file, _, json_file = files |
| |
|
| | annos = [] |
| |
|
| | if from_json: |
| | from shapely.geometry import MultiPolygon, Polygon |
| |
|
| | with PathManager.open(json_file, "r") as f: |
| | jsonobj = json.load(f) |
| | ret = { |
| | "file_name": image_file, |
| | "image_id": os.path.basename(image_file), |
| | "height": jsonobj["imgHeight"], |
| | "width": jsonobj["imgWidth"], |
| | } |
| |
|
| | |
| | polygons_union = Polygon() |
| |
|
| | |
| | |
| | |
| | |
| | |
| | for obj in jsonobj["objects"][::-1]: |
| | if "deleted" in obj: |
| | continue |
| | label_name = obj["label"] |
| |
|
| | try: |
| | label = name2label[label_name] |
| | except KeyError: |
| | if label_name.endswith("group"): |
| | label = name2label[label_name[: -len("group")]] |
| | else: |
| | raise |
| | if label.id < 0: |
| | continue |
| |
|
| | |
| | |
| | poly_coord = np.asarray(obj["polygon"], dtype="f4") + 0.5 |
| | |
| | |
| | |
| | |
| | |
| | poly = Polygon(poly_coord).buffer(0.5, resolution=4) |
| |
|
| | if not label.hasInstances or label.ignoreInEval: |
| | |
| | polygons_union = polygons_union.union(poly) |
| | continue |
| |
|
| | |
| | poly_wo_overlaps = poly.difference(polygons_union) |
| | if poly_wo_overlaps.is_empty: |
| | continue |
| | polygons_union = polygons_union.union(poly) |
| |
|
| | anno = {} |
| | anno["iscrowd"] = label_name.endswith("group") |
| | anno["category_id"] = label.id |
| |
|
| | if isinstance(poly_wo_overlaps, Polygon): |
| | poly_list = [poly_wo_overlaps] |
| | elif isinstance(poly_wo_overlaps, MultiPolygon): |
| | poly_list = poly_wo_overlaps.geoms |
| | else: |
| | raise NotImplementedError( |
| | "Unknown geometric structure {}".format(poly_wo_overlaps) |
| | ) |
| |
|
| | poly_coord = [] |
| | for poly_el in poly_list: |
| | |
| | |
| | |
| | poly_coord.append(list(chain(*poly_el.exterior.coords))) |
| | anno["segmentation"] = poly_coord |
| | (xmin, ymin, xmax, ymax) = poly_wo_overlaps.bounds |
| |
|
| | anno["bbox"] = (xmin, ymin, xmax, ymax) |
| | anno["bbox_mode"] = BoxMode.XYXY_ABS |
| |
|
| | annos.append(anno) |
| | else: |
| | |
| | |
| | with PathManager.open(instance_id_file, "rb") as f: |
| | inst_image = np.asarray(Image.open(f), order="F") |
| | |
| | flattened_ids = np.unique(inst_image[inst_image >= 24]) |
| |
|
| | ret = { |
| | "file_name": image_file, |
| | "image_id": os.path.basename(image_file), |
| | "height": inst_image.shape[0], |
| | "width": inst_image.shape[1], |
| | } |
| |
|
| | for instance_id in flattened_ids: |
| | |
| | |
| | label_id = instance_id // 1000 if instance_id >= 1000 else instance_id |
| | label = id2label[label_id] |
| | if not label.hasInstances or label.ignoreInEval: |
| | continue |
| |
|
| | anno = {} |
| | anno["iscrowd"] = instance_id < 1000 |
| | anno["category_id"] = label.id |
| |
|
| | mask = np.asarray(inst_image == instance_id, dtype=np.uint8, order="F") |
| |
|
| | inds = np.nonzero(mask) |
| | ymin, ymax = inds[0].min(), inds[0].max() |
| | xmin, xmax = inds[1].min(), inds[1].max() |
| | anno["bbox"] = (xmin, ymin, xmax, ymax) |
| | if xmax <= xmin or ymax <= ymin: |
| | continue |
| | anno["bbox_mode"] = BoxMode.XYXY_ABS |
| | if to_polygons: |
| | |
| | |
| | contours = cv2.findContours( |
| | mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE |
| | )[-2] |
| | polygons = [c.reshape(-1).tolist() for c in contours if len(c) >= 3] |
| | |
| | if len(polygons) == 0: |
| | continue |
| | anno["segmentation"] = polygons |
| | else: |
| | anno["segmentation"] = mask_util.encode(mask[:, :, None])[0] |
| | annos.append(anno) |
| | ret["annotations"] = annos |
| | return ret |
| |
|
| |
|
| | def main() -> None: |
| | global logger, labels |
| | """ |
| | Test the cityscapes dataset loader. |
| | |
| | Usage: |
| | python -m detectron2.data.datasets.cityscapes \ |
| | cityscapes/leftImg8bit/train cityscapes/gtFine/train |
| | """ |
| | import argparse |
| |
|
| | parser = argparse.ArgumentParser() |
| | parser.add_argument("image_dir") |
| | parser.add_argument("gt_dir") |
| | parser.add_argument("--type", choices=["instance", "semantic"], default="instance") |
| | args = parser.parse_args() |
| | from cityscapesscripts.helpers.labels import labels |
| | from detectron2.data.catalog import Metadata |
| | from detectron2.utils.visualizer import Visualizer |
| |
|
| | logger = setup_logger(name=__name__) |
| |
|
| | dirname = "cityscapes-data-vis" |
| | os.makedirs(dirname, exist_ok=True) |
| |
|
| | if args.type == "instance": |
| | dicts = load_cityscapes_instances( |
| | args.image_dir, args.gt_dir, from_json=True, to_polygons=True |
| | ) |
| | logger.info("Done loading {} samples.".format(len(dicts))) |
| |
|
| | thing_classes = [ |
| | k.name for k in labels if k.hasInstances and not k.ignoreInEval |
| | ] |
| | meta = Metadata().set(thing_classes=thing_classes) |
| |
|
| | else: |
| | dicts = load_cityscapes_semantic(args.image_dir, args.gt_dir) |
| | logger.info("Done loading {} samples.".format(len(dicts))) |
| |
|
| | stuff_classes = [k.name for k in labels if k.trainId != 255] |
| | stuff_colors = [k.color for k in labels if k.trainId != 255] |
| | meta = Metadata().set(stuff_classes=stuff_classes, stuff_colors=stuff_colors) |
| |
|
| | for d in dicts: |
| | img = np.array(Image.open(PathManager.open(d["file_name"], "rb"))) |
| | visualizer = Visualizer(img, metadata=meta) |
| | vis = visualizer.draw_dataset_dict(d) |
| | |
| | |
| | fpath = os.path.join(dirname, os.path.basename(d["file_name"])) |
| | vis.save(fpath) |
| |
|
| |
|
| | if __name__ == "__main__": |
| | main() |
| |
|