Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solve some errors and add more info to README and requirements.txt #8

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
data/*
venv/
.idea/
*.pyc
*.pyc
.vscode/*
65 changes: 58 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
## Cityscapes to CoCo Conversion Tool
## Cityscapes to COCO Conversion Tool
![](assets/preview.png)

This script allows to convert the [Cityscapes Dataset](https://www.cityscapes-dataset.com/) to Mircosoft's [CoCo Format](http://cocodataset.org/). The code heavily relies on Facebook's [Detection Repo](https://github.com/facebookresearch/Detectron/blob/master/tools/convert_cityscapes_to_coco.py) and [Cityscapes Scripts](https://github.com/mcordts/cityscapesScripts).
Forked from https://github.com/TillBeemelmanns/cityscapes-to-coco-conversion

This script allows to convert the [Cityscapes Dataset](https://www.cityscapes-dataset.com/) to Mircosoft's [COCO Format](http://cocodataset.org/). The code heavily relies on Facebook's [Detection Repo](https://github.com/facebookresearch/Detectron/blob/master/tools/convert_cityscapes_to_coco.py) and [Cityscapes Scripts](https://github.com/mcordts/cityscapesScripts).

The converted annotations can be easily used for [Mask-RCNN](https://github.com/matterport/Mask_RCNN) or other deep learning projects.

Expand All @@ -20,28 +22,77 @@ data/
├── test
├── train
└── val
utils/
main.py
inspect_coco.py
README.md
requirements.txt
```

## Installation
```
```shell
pip install -r requirements.txt
```


## Run
To run the conversion execute the following
```
```shell
python main.py --dataset cityscapes --datadir data/cityscapes --outdir data/cityscapes/annotations
```

In order to run the visualization of the CoCo dataset you may run
Takes about 12 minutes to execute.

The script will create the files

- ```instancesonly_filtered_gtFine_train.json```
- ```instancesonly_filtered_gtFine_val.json```

in the directory ```annotations``` for the ```train``` and ```val``` split which contain the Coco annotations.

The variable category_instancesonly defines which classes should be considered in the conversion process. By default has this value:

```python
category_instancesonly = [
'person',
'rider',
'car',
'truck',
'bus',
'train',
'motorcycle',
'bicycle',
]
```

which in COCO format (in .yaml file format) is

```yaml
NUM_CLASSES: 9
CLASSES: [
{ 'supercategory': 'none', 'id': 0, 'name': 'background' },
{ 'supercategory': 'none', 'id': 1, 'name': 'person' },
{ 'supercategory': 'none', 'id': 2, 'name': 'rider' },
{ 'supercategory': 'none', 'id': 3, 'name': 'car' },
{ 'supercategory': 'none', 'id': 4, 'name': 'bicycle' },
{ 'supercategory': 'none', 'id': 5, 'name': 'motorcycle' },
{ 'supercategory': 'none', 'id': 6, 'name': 'bus' },
{ 'supercategory': 'none', 'id': 7, 'name': 'truck' },
{ 'supercategory': 'none', 'id': 8, 'name': 'train' },
]
```

It is not possible to enable more classes as there is no instance annotation

Sometimes the segmentation annotations are so small that no reasonable big enough object could be created. In this case the, the object will be skipped and the following message is printed:

```
Warning: invalid contours.
```

In order to run the visualization of the COCO dataset you may run
```shell
python inspect_coco.py --coco_dir data/cityscapes
```

## Output
![vis1](assets/plot1.png "Cityscapes in CoCo format") ![vis2](assets/plot2.png "Cityscapes in CoCo format")
![vis1](assets/plot1.png "Cityscapes in COCO format") ![vis2](assets/plot2.png "Cityscapes in COCO format")
2 changes: 2 additions & 0 deletions data/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
5 changes: 3 additions & 2 deletions inspect_coco.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import utils
from utils import visualize
from utils.utils import CocoDataset
import numpy as np


def main(coco_dir, num_plot_examples):
Expand All @@ -20,14 +21,14 @@ def main(coco_dir, num_plot_examples):

# plot masks for each class
for _ in range(num_plot_examples):
random_image_id = random.choice(dataset.image_ids)
random_image_id = np.random.choice(dataset.image_ids)
image = dataset.load_image(random_image_id)
mask, class_ids = dataset.load_mask(random_image_id)
visualize.display_top_masks(image, mask, class_ids, dataset.class_names)

# Plot display instances
for _ in range(num_plot_examples):
random_image_id = random.choice(dataset.image_ids)
random_image_id = np.random.choice(dataset.image_ids)
image = dataset.load_image(random_image_id)
mask, class_ids = dataset.load_mask(random_image_id)
bbox = utils.utils.extract_bboxes(mask)
Expand Down
37 changes: 25 additions & 12 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from __future__ import unicode_literals

import sys
from typing import OrderedDict
from pathlib import Path

# Image processing
# Check if PIL is actually Pillow as expected
Expand Down Expand Up @@ -132,19 +134,25 @@ def convert_cityscapes_instance_only(data_dir, out_dir):
img_id = 0
ann_id = 0
cat_id = 1
category_dict = {}
category_dict = OrderedDict()

category_instancesonly = [
'person',
'rider',
'car',
'truck',
'bicycle',
'motorcycle',
'bus',
'truck',
'train',
'motorcycle',
'bicycle',
]

# It is not possible to enable more classes as there is no instance annotation of that classes

# Fill the category dict in an ordered manner
for i, cat in enumerate(category_instancesonly):
category_dict[cat] = i + 1 # +1 to start from 1 (category 0 is for BG in Faster RCNN)

for data_set, ann_dir in zip(sets, ann_dirs):
print('Starting %s' % data_set)
ann_dict = {}
Expand All @@ -165,11 +173,13 @@ def convert_cityscapes_instance_only(data_dir, out_dir):
img_id += 1
image['width'] = json_ann['imgWidth']
image['height'] = json_ann['imgHeight']
image['file_name'] = os.path.join("leftImg8bit",
data_set.split("/")[-1],
filename.split('_')[0],
filename.replace("_gtFine_polygons.json", '_leftImg8bit.png'))
image['seg_file_name'] = filename.replace("_polygons.json", "_instanceIds.png")
image['file_name'] = Path(
os.path.join("leftImg8bit",
data_set.split("/")[-1],
filename.split('_')[0],
filename.replace("_gtFine_polygons.json", '_leftImg8bit.png'))
).as_posix()
image['seg_file_name'] = Path(filename.replace("_polygons.json", "_instanceIds.png")).as_posix()
images.append(image)

fullname = os.path.join(root, image['seg_file_name'])
Expand All @@ -185,6 +195,8 @@ def convert_cityscapes_instance_only(data_dir, out_dir):
continue # skip non-instance categories

len_p = [len(p) for p in obj['contours']]
if object_cls == 'traffic sign':
print("New label found")
if min(len_p) <= 4:
print('Warning: invalid contours.')
continue # skip non-instance categories
Expand All @@ -195,9 +207,10 @@ def convert_cityscapes_instance_only(data_dir, out_dir):
ann['image_id'] = image['id']
ann['segmentation'] = obj['contours']

if object_cls not in category_dict:
category_dict[object_cls] = cat_id
cat_id += 1
# if object_cls not in category_dict:
# category_dict[object_cls] = cat_id
# cat_id += 1

ann['category_id'] = category_dict[object_cls]
ann['iscrowd'] = 0
ann['area'] = obj['pixelCount']
Expand Down
14 changes: 7 additions & 7 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
numpy
h5py
scipy
Pillow
opencv-python
pycocotools
scikit-image
numpy==1.24.2
h5py==3.8.0
scipy==1.10.1
Pillow==9.4.0
opencv-python==4.7.0.72
pycocotools==2.0.6
scikit-image==0.20.0
10 changes: 5 additions & 5 deletions utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ def minimize_mask(bbox, mask, mini_shape):
raise Exception("Invalid bounding box with area of zero")
# Resize with bilinear interpolation
m = resize(m, mini_shape)
mini_mask[:, :, i] = np.around(m).astype(np.bool)
mini_mask[:, :, i] = np.around(m).astype(bool)
return mini_mask


Expand All @@ -363,7 +363,7 @@ def expand_mask(bbox, mini_mask, image_shape):
w = x2 - x1
# Resize with bilinear interpolation
m = resize(m, (h, w))
mask[y1:y2, x1:x2, i] = np.around(m).astype(np.bool)
mask[y1:y2, x1:x2, i] = np.around(m).astype(bool)
return mask


Expand All @@ -383,10 +383,10 @@ def unmold_mask(mask, bbox, image_shape):
threshold = 0.5
y1, x1, y2, x2 = bbox
mask = resize(mask, (y2 - y1, x2 - x1))
mask = np.where(mask >= threshold, 1, 0).astype(np.bool)
mask = np.where(mask >= threshold, 1, 0).astype(bool)

# Put the mask in the right location.
full_mask = np.zeros(image_shape[:2], dtype=np.bool)
full_mask = np.zeros(image_shape[:2], dtype=bool)
full_mask[y1:y2, x1:x2] = mask
return full_mask

Expand Down Expand Up @@ -487,7 +487,7 @@ def load_mask(self, image_id):

# Pack instance masks into an array
if class_ids:
mask = np.stack(instance_masks, axis=2).astype(np.bool)
mask = np.stack(instance_masks, axis=2).astype(bool)
class_ids = np.array(class_ids, dtype=np.int32)
return mask, class_ids
else:
Expand Down