-
Notifications
You must be signed in to change notification settings - Fork 2.5k
add support for pascal voc dataset and evaluate #131
Conversation
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! |
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks! |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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', |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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__
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Thanks very much for your review. I've submmited a new pr. |
Sorry, I cannot make a new pr, this pr has already contained the new changes. My mistakes. |
There was a problem hiding this 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 theprepare_for_coco_detection
functions could be moved to their own file, and leave entirely dataset-agnostic functions ininference.py
, likecompute_predictions
andinference
.
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 |
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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) |
There was a problem hiding this comment.
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
If I load the xml at every iteration, there may be a duplicated loading(when call |
This is looking much better. I'd rename 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! |
OK. Thanks! |
Hi,
Thanks! |
Hi @henrywang1 , About your comments: Thanks |
Hi @fmassa In my opinion, the feature could benefit people who want to try Mask-RCNN, but they don't have much computing power. Thanks |
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 |
Hi @lufficc , I've rebased your PR and modified a few more things (like removing the need of an external Or else I can submit a PR, while keeping the history of commits |
I've checked "Allow edits from maintainers", do you have permission now? |
shoot, I did something wrong here, let me try to fix it |
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... |
It's OK, I'm not very familiar with that either 😆 . What should I do next? |
Maybe you could have a look at #207 (or more precisely the diffs that I added) and see if you are ok with that? |
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 |
I'm OK with your new commits. |
I don't actually know. I did almost exactly the same thing with #102 (comment) but it worked out fine in the end. |
hi @fmassa |
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 |
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 |
See #128.