| |
| from typing import Optional, Tuple, Union |
|
|
| import torch |
| from mmcv.ops.nms import batched_nms |
| from torch import Tensor |
|
|
| from mmdet.structures.bbox import bbox_overlaps |
| from mmdet.utils import ConfigType |
|
|
|
|
| def multiclass_nms( |
| multi_bboxes: Tensor, |
| multi_scores: Tensor, |
| score_thr: float, |
| nms_cfg: ConfigType, |
| max_num: int = -1, |
| score_factors: Optional[Tensor] = None, |
| return_inds: bool = False, |
| box_dim: int = 4 |
| ) -> Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: |
| """NMS for multi-class bboxes. |
| |
| Args: |
| multi_bboxes (Tensor): shape (n, #class*4) or (n, 4) |
| multi_scores (Tensor): shape (n, #class), where the last column |
| contains scores of the background class, but this will be ignored. |
| score_thr (float): bbox threshold, bboxes with scores lower than it |
| will not be considered. |
| nms_cfg (Union[:obj:`ConfigDict`, dict]): a dict that contains |
| the arguments of nms operations. |
| max_num (int, optional): if there are more than max_num bboxes after |
| NMS, only top max_num will be kept. Default to -1. |
| score_factors (Tensor, optional): The factors multiplied to scores |
| before applying NMS. Default to None. |
| return_inds (bool, optional): Whether return the indices of kept |
| bboxes. Default to False. |
| box_dim (int): The dimension of boxes. Defaults to 4. |
| |
| Returns: |
| Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: |
| (dets, labels, indices (optional)), tensors of shape (k, 5), |
| (k), and (k). Dets are boxes with scores. Labels are 0-based. |
| """ |
| num_classes = multi_scores.size(1) - 1 |
| |
| if multi_bboxes.shape[1] > box_dim: |
| bboxes = multi_bboxes.view(multi_scores.size(0), -1, box_dim) |
| else: |
| bboxes = multi_bboxes[:, None].expand( |
| multi_scores.size(0), num_classes, box_dim) |
|
|
| scores = multi_scores[:, :-1] |
|
|
| labels = torch.arange(num_classes, dtype=torch.long, device=scores.device) |
| labels = labels.view(1, -1).expand_as(scores) |
|
|
| bboxes = bboxes.reshape(-1, box_dim) |
| scores = scores.reshape(-1) |
| labels = labels.reshape(-1) |
|
|
| if not torch.onnx.is_in_onnx_export(): |
| |
| |
| valid_mask = scores > score_thr |
| |
| |
| if score_factors is not None: |
| |
| score_factors = score_factors.view(-1, 1).expand( |
| multi_scores.size(0), num_classes) |
| score_factors = score_factors.reshape(-1) |
| scores = scores * score_factors |
|
|
| if not torch.onnx.is_in_onnx_export(): |
| |
| inds = valid_mask.nonzero(as_tuple=False).squeeze(1) |
| bboxes, scores, labels = bboxes[inds], scores[inds], labels[inds] |
| else: |
| |
| |
| bboxes = torch.cat([bboxes, bboxes.new_zeros(1, box_dim)], dim=0) |
| scores = torch.cat([scores, scores.new_zeros(1)], dim=0) |
| labels = torch.cat([labels, labels.new_zeros(1)], dim=0) |
|
|
| if bboxes.numel() == 0: |
| if torch.onnx.is_in_onnx_export(): |
| raise RuntimeError('[ONNX Error] Can not record NMS ' |
| 'as it has not been executed this time') |
| dets = torch.cat([bboxes, scores[:, None]], -1) |
| if return_inds: |
| return dets, labels, inds |
| else: |
| return dets, labels |
|
|
| dets, keep = batched_nms(bboxes, scores, labels, nms_cfg) |
|
|
| if max_num > 0: |
| dets = dets[:max_num] |
| keep = keep[:max_num] |
|
|
| if return_inds: |
| return dets, labels[keep], inds[keep] |
| else: |
| return dets, labels[keep] |
|
|
|
|
| def fast_nms( |
| multi_bboxes: Tensor, |
| multi_scores: Tensor, |
| multi_coeffs: Tensor, |
| score_thr: float, |
| iou_thr: float, |
| top_k: int, |
| max_num: int = -1 |
| ) -> Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: |
| """Fast NMS in `YOLACT <https://arxiv.org/abs/1904.02689>`_. |
| |
| Fast NMS allows already-removed detections to suppress other detections so |
| that every instance can be decided to be kept or discarded in parallel, |
| which is not possible in traditional NMS. This relaxation allows us to |
| implement Fast NMS entirely in standard GPU-accelerated matrix operations. |
| |
| Args: |
| multi_bboxes (Tensor): shape (n, #class*4) or (n, 4) |
| multi_scores (Tensor): shape (n, #class+1), where the last column |
| contains scores of the background class, but this will be ignored. |
| multi_coeffs (Tensor): shape (n, #class*coeffs_dim). |
| score_thr (float): bbox threshold, bboxes with scores lower than it |
| will not be considered. |
| iou_thr (float): IoU threshold to be considered as conflicted. |
| top_k (int): if there are more than top_k bboxes before NMS, |
| only top top_k will be kept. |
| max_num (int): if there are more than max_num bboxes after NMS, |
| only top max_num will be kept. If -1, keep all the bboxes. |
| Default: -1. |
| |
| Returns: |
| Union[Tuple[Tensor, Tensor, Tensor], Tuple[Tensor, Tensor]]: |
| (dets, labels, coefficients), tensors of shape (k, 5), (k, 1), |
| and (k, coeffs_dim). Dets are boxes with scores. |
| Labels are 0-based. |
| """ |
|
|
| scores = multi_scores[:, :-1].t() |
| scores, idx = scores.sort(1, descending=True) |
|
|
| idx = idx[:, :top_k].contiguous() |
| scores = scores[:, :top_k] |
| num_classes, num_dets = idx.size() |
| boxes = multi_bboxes[idx.view(-1), :].view(num_classes, num_dets, 4) |
| coeffs = multi_coeffs[idx.view(-1), :].view(num_classes, num_dets, -1) |
|
|
| iou = bbox_overlaps(boxes, boxes) |
| iou.triu_(diagonal=1) |
| iou_max, _ = iou.max(dim=1) |
|
|
| |
| keep = iou_max <= iou_thr |
|
|
| |
| keep *= scores > score_thr |
|
|
| |
| classes = torch.arange( |
| num_classes, device=boxes.device)[:, None].expand_as(keep) |
| classes = classes[keep] |
|
|
| boxes = boxes[keep] |
| coeffs = coeffs[keep] |
| scores = scores[keep] |
|
|
| |
| scores, idx = scores.sort(0, descending=True) |
| if max_num > 0: |
| idx = idx[:max_num] |
| scores = scores[:max_num] |
|
|
| classes = classes[idx] |
| boxes = boxes[idx] |
| coeffs = coeffs[idx] |
|
|
| cls_dets = torch.cat([boxes, scores[:, None]], dim=1) |
| return cls_dets, classes, coeffs |
|
|