|
|
|
from typing import Optional, Union |
|
|
|
import torch |
|
from mmengine.structures import InstanceData |
|
from torch import Tensor |
|
|
|
from mmdet.registry import TASK_UTILS |
|
from .assign_result import AssignResult |
|
from .base_assigner import BaseAssigner |
|
|
|
|
|
@TASK_UTILS.register_module() |
|
class MaxIoUAssigner(BaseAssigner): |
|
"""Assign a corresponding gt bbox or background to each bbox. |
|
|
|
Each proposals will be assigned with `-1`, or a semi-positive integer |
|
indicating the ground truth index. |
|
|
|
- -1: negative sample, no assigned gt |
|
- semi-positive integer: positive sample, index (0-based) of assigned gt |
|
|
|
Args: |
|
pos_iou_thr (float): IoU threshold for positive bboxes. |
|
neg_iou_thr (float or tuple): IoU threshold for negative bboxes. |
|
min_pos_iou (float): Minimum iou for a bbox to be considered as a |
|
positive bbox. Positive samples can have smaller IoU than |
|
pos_iou_thr due to the 4th step (assign max IoU sample to each gt). |
|
`min_pos_iou` is set to avoid assigning bboxes that have extremely |
|
small iou with GT as positive samples. It brings about 0.3 mAP |
|
improvements in 1x schedule but does not affect the performance of |
|
3x schedule. More comparisons can be found in |
|
`PR #7464 <https://github.com/open-mmlab/mmdetection/pull/7464>`_. |
|
gt_max_assign_all (bool): Whether to assign all bboxes with the same |
|
highest overlap with some gt to that gt. |
|
ignore_iof_thr (float): IoF threshold for ignoring bboxes (if |
|
`gt_bboxes_ignore` is specified). Negative values mean not |
|
ignoring any bboxes. |
|
ignore_wrt_candidates (bool): Whether to compute the iof between |
|
`bboxes` and `gt_bboxes_ignore`, or the contrary. |
|
match_low_quality (bool): Whether to allow low quality matches. This is |
|
usually allowed for RPN and single stage detectors, but not allowed |
|
in the second stage. Details are demonstrated in Step 4. |
|
gpu_assign_thr (int): The upper bound of the number of GT for GPU |
|
assign. When the number of gt is above this threshold, will assign |
|
on CPU device. Negative values mean not assign on CPU. |
|
iou_calculator (dict): Config of overlaps Calculator. |
|
""" |
|
|
|
def __init__(self, |
|
pos_iou_thr: float, |
|
neg_iou_thr: Union[float, tuple], |
|
min_pos_iou: float = .0, |
|
gt_max_assign_all: bool = True, |
|
ignore_iof_thr: float = -1, |
|
ignore_wrt_candidates: bool = True, |
|
match_low_quality: bool = True, |
|
gpu_assign_thr: float = -1, |
|
iou_calculator: dict = dict(type='BboxOverlaps2D')): |
|
self.pos_iou_thr = pos_iou_thr |
|
self.neg_iou_thr = neg_iou_thr |
|
self.min_pos_iou = min_pos_iou |
|
self.gt_max_assign_all = gt_max_assign_all |
|
self.ignore_iof_thr = ignore_iof_thr |
|
self.ignore_wrt_candidates = ignore_wrt_candidates |
|
self.gpu_assign_thr = gpu_assign_thr |
|
self.match_low_quality = match_low_quality |
|
self.iou_calculator = TASK_UTILS.build(iou_calculator) |
|
|
|
def assign(self, |
|
pred_instances: InstanceData, |
|
gt_instances: InstanceData, |
|
gt_instances_ignore: Optional[InstanceData] = None, |
|
**kwargs) -> AssignResult: |
|
"""Assign gt to bboxes. |
|
|
|
This method assign a gt bbox to every bbox (proposal/anchor), each bbox |
|
will be assigned with -1, or a semi-positive number. -1 means negative |
|
sample, semi-positive number is the index (0-based) of assigned gt. |
|
The assignment is done in following steps, the order matters. |
|
|
|
1. assign every bbox to the background |
|
2. assign proposals whose iou with all gts < neg_iou_thr to 0 |
|
3. for each bbox, if the iou with its nearest gt >= pos_iou_thr, |
|
assign it to that bbox |
|
4. for each gt bbox, assign its nearest proposals (may be more than |
|
one) to itself |
|
|
|
Args: |
|
pred_instances (:obj:`InstanceData`): Instances of model |
|
predictions. It includes ``priors``, and the priors can |
|
be anchors or points, or the bboxes predicted by the |
|
previous stage, has shape (n, 4). The bboxes predicted by |
|
the current model or stage will be named ``bboxes``, |
|
``labels``, and ``scores``, the same as the ``InstanceData`` |
|
in other places. |
|
gt_instances (:obj:`InstanceData`): Ground truth of instance |
|
annotations. It usually includes ``bboxes``, with shape (k, 4), |
|
and ``labels``, with shape (k, ). |
|
gt_instances_ignore (:obj:`InstanceData`, optional): Instances |
|
to be ignored during training. It includes ``bboxes`` |
|
attribute data that is ignored during training and testing. |
|
Defaults to None. |
|
|
|
Returns: |
|
:obj:`AssignResult`: The assign result. |
|
|
|
Example: |
|
>>> from mmengine.structures import InstanceData |
|
>>> self = MaxIoUAssigner(0.5, 0.5) |
|
>>> pred_instances = InstanceData() |
|
>>> pred_instances.priors = torch.Tensor([[0, 0, 10, 10], |
|
... [10, 10, 20, 20]]) |
|
>>> gt_instances = InstanceData() |
|
>>> gt_instances.bboxes = torch.Tensor([[0, 0, 10, 9]]) |
|
>>> gt_instances.labels = torch.Tensor([0]) |
|
>>> assign_result = self.assign(pred_instances, gt_instances) |
|
>>> expected_gt_inds = torch.LongTensor([1, 0]) |
|
>>> assert torch.all(assign_result.gt_inds == expected_gt_inds) |
|
""" |
|
gt_bboxes = gt_instances.bboxes |
|
priors = pred_instances.priors |
|
gt_labels = gt_instances.labels |
|
if gt_instances_ignore is not None: |
|
gt_bboxes_ignore = gt_instances_ignore.bboxes |
|
else: |
|
gt_bboxes_ignore = None |
|
|
|
assign_on_cpu = True if (self.gpu_assign_thr > 0) and ( |
|
gt_bboxes.shape[0] > self.gpu_assign_thr) else False |
|
|
|
if assign_on_cpu: |
|
device = priors.device |
|
priors = priors.cpu() |
|
gt_bboxes = gt_bboxes.cpu() |
|
gt_labels = gt_labels.cpu() |
|
if gt_bboxes_ignore is not None: |
|
gt_bboxes_ignore = gt_bboxes_ignore.cpu() |
|
|
|
overlaps = self.iou_calculator(gt_bboxes, priors) |
|
|
|
if (self.ignore_iof_thr > 0 and gt_bboxes_ignore is not None |
|
and gt_bboxes_ignore.numel() > 0 and priors.numel() > 0): |
|
if self.ignore_wrt_candidates: |
|
ignore_overlaps = self.iou_calculator( |
|
priors, gt_bboxes_ignore, mode='iof') |
|
ignore_max_overlaps, _ = ignore_overlaps.max(dim=1) |
|
else: |
|
ignore_overlaps = self.iou_calculator( |
|
gt_bboxes_ignore, priors, mode='iof') |
|
ignore_max_overlaps, _ = ignore_overlaps.max(dim=0) |
|
overlaps[:, ignore_max_overlaps > self.ignore_iof_thr] = -1 |
|
|
|
assign_result = self.assign_wrt_overlaps(overlaps, gt_labels) |
|
if assign_on_cpu: |
|
assign_result.gt_inds = assign_result.gt_inds.to(device) |
|
assign_result.max_overlaps = assign_result.max_overlaps.to(device) |
|
if assign_result.labels is not None: |
|
assign_result.labels = assign_result.labels.to(device) |
|
return assign_result |
|
|
|
def assign_wrt_overlaps(self, overlaps: Tensor, |
|
gt_labels: Tensor) -> AssignResult: |
|
"""Assign w.r.t. the overlaps of priors with gts. |
|
|
|
Args: |
|
overlaps (Tensor): Overlaps between k gt_bboxes and n bboxes, |
|
shape(k, n). |
|
gt_labels (Tensor): Labels of k gt_bboxes, shape (k, ). |
|
|
|
Returns: |
|
:obj:`AssignResult`: The assign result. |
|
""" |
|
num_gts, num_bboxes = overlaps.size(0), overlaps.size(1) |
|
|
|
|
|
assigned_gt_inds = overlaps.new_full((num_bboxes, ), |
|
-1, |
|
dtype=torch.long) |
|
|
|
if num_gts == 0 or num_bboxes == 0: |
|
|
|
max_overlaps = overlaps.new_zeros((num_bboxes, )) |
|
assigned_labels = overlaps.new_full((num_bboxes, ), |
|
-1, |
|
dtype=torch.long) |
|
if num_gts == 0: |
|
|
|
assigned_gt_inds[:] = 0 |
|
return AssignResult( |
|
num_gts=num_gts, |
|
gt_inds=assigned_gt_inds, |
|
max_overlaps=max_overlaps, |
|
labels=assigned_labels) |
|
|
|
|
|
|
|
max_overlaps, argmax_overlaps = overlaps.max(dim=0) |
|
|
|
|
|
gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1) |
|
|
|
|
|
|
|
if isinstance(self.neg_iou_thr, float): |
|
assigned_gt_inds[(max_overlaps >= 0) |
|
& (max_overlaps < self.neg_iou_thr)] = 0 |
|
elif isinstance(self.neg_iou_thr, tuple): |
|
assert len(self.neg_iou_thr) == 2 |
|
assigned_gt_inds[(max_overlaps >= self.neg_iou_thr[0]) |
|
& (max_overlaps < self.neg_iou_thr[1])] = 0 |
|
|
|
|
|
pos_inds = max_overlaps >= self.pos_iou_thr |
|
assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1 |
|
|
|
if self.match_low_quality: |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for i in range(num_gts): |
|
if gt_max_overlaps[i] >= self.min_pos_iou: |
|
if self.gt_max_assign_all: |
|
max_iou_inds = overlaps[i, :] == gt_max_overlaps[i] |
|
assigned_gt_inds[max_iou_inds] = i + 1 |
|
else: |
|
assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1 |
|
|
|
assigned_labels = assigned_gt_inds.new_full((num_bboxes, ), -1) |
|
pos_inds = torch.nonzero( |
|
assigned_gt_inds > 0, as_tuple=False).squeeze() |
|
if pos_inds.numel() > 0: |
|
assigned_labels[pos_inds] = gt_labels[assigned_gt_inds[pos_inds] - |
|
1] |
|
|
|
return AssignResult( |
|
num_gts=num_gts, |
|
gt_inds=assigned_gt_inds, |
|
max_overlaps=max_overlaps, |
|
labels=assigned_labels) |
|
|