Skip to content

Commit

Permalink
resolve conflict and move bbbox2results to one_stage
Browse files Browse the repository at this point in the history
  • Loading branch information
zhangshilong committed Jun 1, 2021
2 parents a8c681d + 69a4ed7 commit 90656ee
Show file tree
Hide file tree
Showing 19 changed files with 332 additions and 175 deletions.
6 changes: 5 additions & 1 deletion mmdet/models/dense_heads/anchor_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,10 @@ def aug_test(self, feats, img_metas, rescale=False):
Defaults to False.
Returns:
list[ndarray]: bbox results of each class
list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
The first item is bboxes with shape (n, 5), where 5 represent
(tl_x, tl_y, br_x, br_y, score).
The shape of the second tensor in the tuple is labels
with shape (n,), Length of list should always be 1.
"""
return self.aug_test_bboxes(feats, img_metas, rescale=rescale)
17 changes: 17 additions & 0 deletions mmdet/models/dense_heads/base_dense_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,20 @@ def forward_train(self,
else:
proposal_list = self.get_bboxes(*outs, img_metas, cfg=proposal_cfg)
return losses, proposal_list

def simple_test(self, feats, img_metas, rescale=False):
"""Test function without test-time augmentation.
Args:
feats (tuple[torch.Tensor]): Multi-level features from the
upstream network, each is a 4D-tensor.
img_metas (list[dict]): List of image information.
rescale (bool, optional): Whether to rescale the results.
Defaults to False.
Returns:
list[list[np.ndarray]]: BBox results of each image and classes.
The outer list corresponds to each image. The inner list
corresponds to each class.
"""
return self.simple_test_bboxes(feats, img_metas, rescale=rescale)
3 changes: 2 additions & 1 deletion mmdet/models/dense_heads/cascade_rpn_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -782,4 +782,5 @@ def simple_test_rpn(self, x, img_metas):

def aug_test_rpn(self, x, img_metas):
"""Augmented forward test function."""
raise NotImplementedError
raise NotImplementedError(
'CascadeRPNHead does not support test-time augmentation')
3 changes: 2 additions & 1 deletion mmdet/models/dense_heads/centernet_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
from ..utils.gaussian_target import (get_local_maximum, get_topk_from_heatmap,
transpose_and_gather_feat)
from .base_dense_head import BaseDenseHead
from .dense_test_mixins import BBoxTestMixin


@HEADS.register_module()
class CenterNetHead(BaseDenseHead):
class CenterNetHead(BaseDenseHead, BBoxTestMixin):
"""Objects as Points Head. CenterHead use center_point to indicate object's
position. Paper link <https://arxiv.org/abs/1904.07850>
Expand Down
3 changes: 2 additions & 1 deletion mmdet/models/dense_heads/corner_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
get_topk_from_heatmap,
transpose_and_gather_feat)
from .base_dense_head import BaseDenseHead
from .dense_test_mixins import BBoxTestMixin


class BiCornerPool(BaseModule):
Expand Down Expand Up @@ -80,7 +81,7 @@ def forward(self, x):


@HEADS.register_module()
class CornerHead(BaseDenseHead):
class CornerHead(BaseDenseHead, BBoxTestMixin):
"""Head of CornerNet: Detecting Objects as Paired Keypoints.
Code is modified from the `official github repo
Expand Down
151 changes: 123 additions & 28 deletions mmdet/models/dense_heads/dense_test_mixins.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,42 @@
import sys
from inspect import signature

import torch

from mmdet.core import bbox2result, bbox_mapping_back, multiclass_nms
from mmdet.core import bbox_mapping_back, merge_aug_proposals, multiclass_nms

if sys.version_info >= (3, 7):
from mmdet.utils.contextmanagers import completed

class BBoxTestMixin:
"""Mixin class for test time augmentation of bboxes."""

def merge_aug_bboxes(self, aug_bboxes, aug_scores, img_metas):
"""Merge augmented detection bboxes and scores.
class BBoxTestMixin(object):
"""Mixin class for testing det bboxes via DenseHead."""

def simple_test_bboxes(self, feats, img_metas, rescale=False):
"""Test det bboxes without test-time augmentation, can be applied in
DenseHead except for RPNHead and its variants(GuidedRPN etc.)
Args:
aug_bboxes (list[Tensor]): shape (n, 4*#class)
aug_scores (list[Tensor] or None): shape (n, #class)
img_shapes (list[Tensor]): shape (3, ).
feats (tuple[torch.Tensor]): Multi-level features from the
upstream network, each is a 4D-tensor.
img_metas (list[dict]): List of image information.
rescale (bool, optional): Whether to rescale the results.
Defaults to False.
Returns:
tuple: (bboxes, scores)
list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
The first item is bboxes with shape (n, 5), where 5 represent
(tl_x, tl_y, br_x, br_y, score).
The shape of the second tensor in the tuple is labels
with shape (n,)
"""
recovered_bboxes = []
for bboxes, img_info in zip(aug_bboxes, img_metas):
img_shape = img_info[0]['img_shape']
scale_factor = img_info[0]['scale_factor']
flip = img_info[0]['flip']
flip_direction = img_info[0]['flip_direction']
bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,
flip_direction)
recovered_bboxes.append(bboxes)
bboxes = torch.cat(recovered_bboxes, dim=0)
if aug_scores is None:
return bboxes
else:
scores = torch.cat(aug_scores, dim=0)
return bboxes, scores
outs = self.forward(feats)
results_list = self.get_bboxes(*outs, img_metas, rescale=rescale)
return results_list

def aug_test_bboxes(self, feats, img_metas, rescale=False):
"""Test det bboxes with test time augmentation.
"""Test det bboxes with test time augmentation, can be applied in
DenseHead except for RPNHead and its variants(GuidedRPN etc.)
Args:
feats (list[Tensor]): the outer list indicates test-time
Expand All @@ -49,7 +49,11 @@ def aug_test_bboxes(self, feats, img_metas, rescale=False):
Defaults to False.
Returns:
list[ndarray]: bbox results of each class
list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
The first item is bboxes with shape (n, 5), where 5 represent
(tl_x, tl_y, br_x, br_y, score).
The shape of the second tensor in the tuple is labels
with shape (n,), Length of list should always be 1.
"""
# check with_nms argument
gb_sig = signature(self.get_bboxes)
Expand Down Expand Up @@ -96,5 +100,96 @@ def aug_test_bboxes(self, feats, img_metas, rescale=False):
_det_bboxes = det_bboxes.clone()
_det_bboxes[:, :4] *= det_bboxes.new_tensor(
img_metas[0][0]['scale_factor'])
bbox_results = bbox2result(_det_bboxes, det_labels, self.num_classes)
return bbox_results

return [
(_det_bboxes, det_labels),
]

def simple_test_rpn(self, x, img_metas):
"""Test without augmentation, only for RPNHead and its
variants(GuidedRPN etc.)
Args:
x (tuple[Tensor]): Features from the upstream network, each is
a 4D-tensor.
img_metas (list[dict]): Meta info of each image.
Returns:
list[Tensor]: Proposals of each image, each item has shape (n, 5),
where 5 represent (tl_x, tl_y, br_x, br_y, score).
"""
rpn_outs = self(x)
proposal_list = self.get_bboxes(*rpn_outs, img_metas)
return proposal_list

def aug_test_rpn(self, feats, img_metas):
"""Test with augmentation for only for RPNHead and
its variants(GuidedRPN etc.)
Args:
feats (tuple[Tensor]): Features from the upstream network, each is
a 4D-tensor.
img_metas (list[dict]): Meta info of each image.
Returns:
list[Tensor]: Proposals of each image, each item has shape (n, 5),
where 5 represent (tl_x, tl_y, br_x, br_y, score).
"""
samples_per_gpu = len(img_metas[0])
aug_proposals = [[] for _ in range(samples_per_gpu)]
for x, img_meta in zip(feats, img_metas):
proposal_list = self.simple_test_rpn(x, img_meta)
for i, proposals in enumerate(proposal_list):
aug_proposals[i].append(proposals)
# reorganize the order of 'img_metas' to match the dimensions
# of 'aug_proposals'
aug_img_metas = []
for i in range(samples_per_gpu):
aug_img_meta = []
for j in range(len(img_metas)):
aug_img_meta.append(img_metas[j][i])
aug_img_metas.append(aug_img_meta)
# after merging, proposals will be rescaled to the original image size
merged_proposals = [
merge_aug_proposals(proposals, aug_img_meta, self.test_cfg)
for proposals, aug_img_meta in zip(aug_proposals, aug_img_metas)
]
return merged_proposals

if sys.version_info >= (3, 7):

async def async_simple_test_rpn(self, x, img_metas):
sleep_interval = self.test_cfg.pop('async_sleep_interval', 0.025)
async with completed(
__name__, 'rpn_head_forward',
sleep_interval=sleep_interval):
rpn_outs = self(x)

proposal_list = self.get_bboxes(*rpn_outs, img_metas)
return proposal_list

def merge_aug_bboxes(self, aug_bboxes, aug_scores, img_metas):
"""Merge augmented detection bboxes and scores.
Args:
aug_bboxes (list[Tensor]): shape (n, 4*#class)
aug_scores (list[Tensor] or None): shape (n, #class)
img_shapes (list[Tensor]): shape (3, ).
Returns:
tuple: (bboxes, scores)
"""
recovered_bboxes = []
for bboxes, img_info in zip(aug_bboxes, img_metas):
img_shape = img_info[0]['img_shape']
scale_factor = img_info[0]['scale_factor']
flip = img_info[0]['flip']
flip_direction = img_info[0]['flip_direction']
bboxes = bbox_mapping_back(bboxes, img_shape, scale_factor, flip,
flip_direction)
recovered_bboxes.append(bboxes)
bboxes = torch.cat(recovered_bboxes, dim=0)
if aug_scores is None:
return bboxes
else:
scores = torch.cat(aug_scores, dim=0)
return bboxes, scores
26 changes: 26 additions & 0 deletions mmdet/models/dense_heads/detr_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,29 @@ def _get_bboxes_single(self,
det_bboxes = torch.cat((det_bboxes, scores.unsqueeze(1)), -1)

return det_bboxes, det_labels

def simple_test_bboxes(self, feats, img_metas, rescale=False):
"""Test det bboxes without test-time augmentation.
Args:
feats (tuple[torch.Tensor]): Multi-level features from the
upstream network, each is a 4D-tensor.
img_metas (list[dict]): List of image information.
rescale (bool, optional): Whether to rescale the results.
Defaults to False.
Returns:
list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
The first item is bboxes with shape (n, 5), where 5 represent
(tl_x, tl_y, br_x, br_y, score).
The shape of the second tensor in the tuple is labels
with shape (n,)
"""
batch_size = len(img_metas)
assert batch_size == 1, 'Currently only batch_size 1 for inference ' \
f'mode is supported. Found batch_size {batch_size}.'

# forward of this head requires img_metas
outs = self.forward(feats, img_metas)
results_list = self.get_bboxes(*outs, img_metas, rescale=rescale)
return results_list
8 changes: 8 additions & 0 deletions mmdet/models/dense_heads/embedding_rpn_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,11 @@ def forward_train(self, img, img_metas):
def simple_test_rpn(self, img, img_metas):
"""Forward function in testing stage."""
return self._decode_init_proposals(img, img_metas)

def simple_test(self, img, img_metas):
"""Forward function in testing stage."""
raise NotImplementedError

def aug_test_rpn(self, feats, img_metas):
raise NotImplementedError(
'EmbeddingRPNHead does not support test-time augmentation')
3 changes: 1 addition & 2 deletions mmdet/models/dense_heads/ga_rpn_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@

from ..builder import HEADS
from .guided_anchor_head import GuidedAnchorHead
from .rpn_test_mixin import RPNTestMixin


@HEADS.register_module()
class GARPNHead(RPNTestMixin, GuidedAnchorHead):
class GARPNHead(GuidedAnchorHead):
"""Guided-Anchor-based RPN head."""

def __init__(self,
Expand Down
4 changes: 3 additions & 1 deletion mmdet/models/dense_heads/paa_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,9 @@ def _get_bboxes(self,
cls_scores. Besides, score voting is used when `` score_voting``
is set to True.
"""
assert with_nms, 'PAA only supports "with_nms=True" now'
assert with_nms, 'PAA only supports "with_nms=True" now and it ' \
'means PAAHead does not support ' \
'test-time augmentation'
assert len(cls_scores) == len(bbox_preds) == len(mlvl_anchors)
batch_size = cls_scores[0].shape[0]

Expand Down
3 changes: 1 addition & 2 deletions mmdet/models/dense_heads/rpn_head.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@

from ..builder import HEADS
from .anchor_head import AnchorHead
from .rpn_test_mixin import RPNTestMixin


@HEADS.register_module()
class RPNHead(RPNTestMixin, AnchorHead):
class RPNHead(AnchorHead):
"""RPN head.
Args:
Expand Down
59 changes: 0 additions & 59 deletions mmdet/models/dense_heads/rpn_test_mixin.py

This file was deleted.

Loading

0 comments on commit 90656ee

Please sign in to comment.