Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

add support for pascal voc dataset and evaluate #131

Closed
wants to merge 0 commits into from

Conversation

lufficc
Copy link
Contributor

@lufficc lufficc commented Nov 8, 2018

See #128.

@facebook-github-bot
Copy link

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

@facebook-github-bot facebook-github-bot added the CLA Signed Do not delete this pull request or issue due to inactivity. label Nov 8, 2018
@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

Copy link
Contributor

@fmassa fmassa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thanks a lot for the PR!

I made some comments.

In general, I think now it would be a good opportunity to clean up a bit more the inference.py file, in order to make it more generic and less specific to a particular dataset implementation.

I'll try to come up with a plan in the next week, but let me know if you have proposals for it.

from collections import defaultdict
import itertools
import numpy as np
import numpy as xp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need this duplicated import?

Copy link
Contributor Author

@lufficc lufficc Nov 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a duplicated import, I've optimized it.

labels = ann['labels']

boxes = torch.as_tensor(boxes).reshape(-1, 4) # guard against no boxes
target = BoxList(boxes, img.size, mode="xyxy").convert("xyxy")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need the .convert("xyxy") here, as the mode is already specified as xyxy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've optimized it.


from maskrcnn_benchmark.structures.bounding_box import BoxList

VOC_BBOX_LABEL_NAMES = ('aeroplane',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you add a __background__ in the first position, you don't need the mapping json_category_id_to_contiguous_id nor contiguous_category_id_to_json_id I think

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great advise. I've optimized it.

v: k for k, v in self.json_category_id_to_contiguous_id.items()
}
self.anns = {}
for img_id in self.ids:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to know, how much time it takes to load this for all images in the trainval set?
One other possibility would be to perform the file loading in __getitem__

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infact it costs very little time(<1s).To keep consistent, I add a print to tell the cost time like coco api does.

import six


def bbox_iou(bbox_a, bbox_b):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we already have a bbox_iou function in structures/boxlist_ops.py, can't we use that instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep consistent, I removed bbox_iou and used structures/boxlist_ops.py's boxlist_iou.

from tqdm import tqdm

from ..data.datasets.voc import VOC_BBOX_LABEL_NAMES
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we would keep the inference engine agnostic to particular dataset implementations. But this will require some refactorings.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it.


from ..config import cfg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not import the config in this file, but keep it agnostic to it. You can probably pass the name of the dataset as an argument to the inference function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed.

return area_i / (area_a[:, None] + area_b - area_i)


def eval_detection_voc(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If those functions come from an already-existing repo, it would be good to reference them here.

Copy link
Contributor Author

@lufficc lufficc Nov 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a modification version from chainercv repository.
(See https://github.com/chainer/chainercv/blob/master/chainercv/evaluations/eval_detection_voc.py)
I add this in top of voc_eval.py file.


torch.save(results, os.path.join(output_folder, "coco_results.pth"))
return results, coco_results
if 'coco' in cfg.DATASETS.TEST[0]:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't work if we pass multiple datasets during evaluation. I think it might be better to pass a dataset name as an argument to the inference function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fixed it by add a name property to dataset.

@lufficc
Copy link
Contributor Author

lufficc commented Nov 13, 2018

Thanks very much for your review. I've submmited a new pr.

@lufficc
Copy link
Contributor Author

lufficc commented Nov 13, 2018

Sorry, I cannot make a new pr, this pr has already contained the new changes. My mistakes.

Copy link
Contributor

@fmassa fmassa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking much better, thanks!

I've made some more comments, let me know what you think.

In particular, I think that there are two points that we should pay attention to:

  • the difficult instances
  • if we should split the inference into dataset-specific files. For example all the prepare_for_coco_detection functions could be moved to their own file, and leave entirely dataset-agnostic functions in inference.py, like compute_predictions and inference.

Thoughts?

@@ -366,7 +363,9 @@ def inference(
)
logger = logging.getLogger("maskrcnn_benchmark.inference")
dataset = data_loader.dataset
logger.info("Start evaluation on {} images".format(len(dataset)))
assert hasattr(dataset, 'name'), 'Dataset must has a name to perform evaluating.'
dataset_name = dataset.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was actually thinking about passing a dataset_name as an argument to inference, instead of adding a field in the dataset itself. What do you think?

'boxes': [[int(obj['bndbox']['xmin']),
int(obj['bndbox']['ymin']),
int(obj['bndbox']['xmax']),
int(obj['bndbox']['ymax'])] for obj in data['object'] if int(obj['difficult']) == 0],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is not the best thing to do unconditionally.

Indeed, we might want to remove the difficult instances during training, but during testing there should be a way of knowing which one of the instances are difficult, so that they are not taken into account during evaluation.

If I understand it correctly, the way it is currently done will be penalizing the difficult instances, as if we detect a difficult instance with our model, it will be counted as a false match.

I think we should have a flag that lets us decide if we want to return difficult instances or not.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it.

):
super(COCODataset, self).__init__(root, ann_file)

self.name = name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's maybe remove the name attribute in favour of the approach I mentioned just before?

What are your thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it. This would be a better choice.

return results, coco_results, predictions


def do_coco_evaluation(predictions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about moving the coco-specific and pascal-specific evaluation functions into their own file, and have the inference file here be very agnostic to the dataset? It is almost there, right?

@@ -0,0 +1,186 @@
# A modification version from chainercv repository.
# (See https://github.com/chainer/chainercv/blob/master/chainercv/evaluations/eval_detection_voc.py)
from __future__ import division
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this file should live in datasets, or in a separate folder dedicated to inference. Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, inference.py looks a little chaotic.
I think inference.py should only do the predication, then pass results and call different evaluatation methods according to the dataset_name. Like you mentioned before.

The location of 'different evaluatation methods', I think, can be:

datasets
  -evaluatation
    -coco
      ...
      ...
    -voc
     ...
     ...
   coco.py
   voc.py

What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you, let's keep this structure that you proposed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've make a new commit. Thanks for your review again.

Copy link
Contributor

@fmassa fmassa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is starting to look very good, thanks!

I still think there is something that should be addressed with the gt_difficult part.
My thought is that if you load the xml at every iteration (instead of pre-loading and filtering it all in the init), you could decide to change the keep_difficult flag in the dataset after it was created.

Also, I would maybe attach a field to each bounding box indicating if it is difficult or not (for testing only). Something like

boxlist = BoxList(...)
boxlist.add_field("difficult", difficult)

this would be an easy way of handling the difficult boxes in the evaluation. But that's not necessary

assert len(gt_boxlists) == len(pred_boxlists), 'Length of gt and pred lists need to be same.'
prec, rec = calc_detection_voc_prec_rec(pred_boxlists=pred_boxlists,
gt_boxlists=gt_boxlists,
iou_thresh=iou_thresh)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still need to pass gt_difficults in here if you want it to be evaluated properly

@lufficc
Copy link
Contributor Author

lufficc commented Nov 15, 2018

If I load the xml at every iteration, there may be a duplicated loading(when call get_img_info). So I think a better choice is load all annotations(include difficult objects) into memory, then filter them dynamically(according to use_difficult flag).

@fmassa
Copy link
Contributor

fmassa commented Nov 15, 2018

This is looking much better. I'd rename difficulties to difficult though, but that's minor.

I'll try using the code and training a model in the weekend / next week and once I've verified that everything is working as expected I'll merge the PR or make further comments.

Thanks!

@lufficc
Copy link
Contributor Author

lufficc commented Nov 15, 2018

OK. Thanks!

@henrywang1
Copy link
Contributor

henrywang1 commented Nov 21, 2018

Hi,
Thanks for the wonderful commit and review.
I was also trying different datasets on maskrcnn-benchmark, and I have some comments.

  1. I think we could load the notation from json files directly like Detectron, it has VOC Annotations in COCO Format.
  2. And we could also evaluate the segmentation result on voc dataset. (The information is also in the json files.)
    What do you think? Maybe I could create another issue?

Thanks!

@fmassa
Copy link
Contributor

fmassa commented Nov 21, 2018

Hi @henrywang1 ,
Yesterday I pulled out this repo and I started doing some minor changes, I should get this ready by tomorrow.

About your comments:
1 - yes, it is possible to use COCO annotations for Pascal VOC dataset. But I think it is good to have built-in support for some evaluation code which doesn't depend on COCO, and this PR adds that
2 - indeed, it could be interesting to add. Do you know if we generally evaluate instance segmentation on Pascal as well? I know that semantic segmentation is commonly evaluated, but I'm not sure about instance segmentation.

Thanks

@henrywang1
Copy link
Contributor

Hi @fmassa
As we know, COCO is a more commonly used dataset for instance segmentation.
But there are still some recent papers evaluating their instance segmentation models on PASCAL.
I list some of the papers for your reference.

In my opinion, the feature could benefit people who want to try Mask-RCNN, but they don't have much computing power.

Thanks

@fmassa
Copy link
Contributor

fmassa commented Nov 21, 2018

Sounds good, it would be great to add support for instance masks as well on Pascal at a later PR.

But that might mean also implementing the evaluation code, or just directly using the data in COCO format

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

Hi @lufficc ,

I've rebased your PR and modified a few more things (like removing the need of an external lxml lib).
Can you give me rights to append to this PR, information can be seen in https://help.github.com/articles/allowing-changes-to-a-pull-request-branch-created-from-a-fork/

Or else I can submit a PR, while keeping the history of commits

@lufficc
Copy link
Contributor Author

lufficc commented Nov 23, 2018

I've checked "Allow edits from maintainers", do you have permission now?

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

shoot, I did something wrong here, let me try to fix it
in the meantime, don't pull from your branch. Sorry!

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

@lufficc I couldn't make it work as I was expecting, so I opened a new PR in #207 .
It contains all your diffs, plus a few more from me.

I'm very sorry, I need to learn how to do this properly... :-/

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

I was following instructions from https://help.github.com/articles/committing-changes-to-a-pull-request-branch-created-from-a-fork/ , but I definitely did something wrong...

@lufficc
Copy link
Contributor Author

lufficc commented Nov 23, 2018

It's OK, I'm not very familiar with that either 😆 . What should I do next?

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

Maybe you could have a look at #207 (or more precisely the diffs that I added) and see if you are ok with that?

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

Maybe the problem happened because your diff was based on the master branch, and not on a separate branch? But I'm not 100% sure

@lufficc
Copy link
Contributor Author

lufficc commented Nov 23, 2018

I'm OK with your new commits.
Or maybe I didn't merge the pr with the master's HEAD?

@fmassa
Copy link
Contributor

fmassa commented Nov 23, 2018

I don't actually know. I did almost exactly the same thing with #102 (comment) but it worked out fine in the end.

@zimenglan-sysu-512
Copy link
Contributor

hi @fmassa
will u add the results of pascal voc into MODEL_ZOO.md?

@fmassa
Copy link
Contributor

fmassa commented Nov 26, 2018

Maybe, but I'd need to train a few more models and compare against what is currently available, so this will probably not happen in the near future

@narendoraiswamy
Copy link

narendoraiswamy commented Oct 4, 2019

Hello @fmassa, @lufficc @henrywang1. Correct me if I am wrong but support for pascal voc dataset for instance segmentation has not been completed yet? Using the voc.py file in data/datasets only supports the detection purpose and not the instance segmentation purpose.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
CLA Signed Do not delete this pull request or issue due to inactivity.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants