Commit dd2907ed authored by Kai Chen's avatar Kai Chen
Browse files

support soft-nms and potential new nms methods

parent af755e01
...@@ -152,7 +152,10 @@ test_cfg = dict( ...@@ -152,7 +152,10 @@ test_cfg = dict(
nms_thr=0.7, nms_thr=0.7,
min_bbox_size=0), min_bbox_size=0),
rcnn=dict( rcnn=dict(
score_thr=0.05, max_per_img=100, nms_thr=0.5, mask_thr_binary=0.5), score_thr=0.05,
nms=dict(type='nms', iou_thr=0.5),
max_per_img=100,
mask_thr_binary=0.5),
keep_all_stages=False) keep_all_stages=False)
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
......
...@@ -137,7 +137,8 @@ test_cfg = dict( ...@@ -137,7 +137,8 @@ test_cfg = dict(
max_num=2000, max_num=2000,
nms_thr=0.7, nms_thr=0.7,
min_bbox_size=0), min_bbox_size=0),
rcnn=dict(score_thr=0.05, max_per_img=100, nms_thr=0.5), rcnn=dict(
score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100),
keep_all_stages=False) keep_all_stages=False)
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
......
...@@ -60,7 +60,10 @@ train_cfg = dict( ...@@ -60,7 +60,10 @@ train_cfg = dict(
debug=False)) debug=False))
test_cfg = dict( test_cfg = dict(
rcnn=dict( rcnn=dict(
score_thr=0.05, max_per_img=100, nms_thr=0.5, mask_thr_binary=0.5)) score_thr=0.05,
nms=dict(type='nms', iou_thr=0.5),
max_per_img=100,
mask_thr_binary=0.5))
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
data_root = 'data/coco/' data_root = 'data/coco/'
......
...@@ -46,7 +46,9 @@ train_cfg = dict( ...@@ -46,7 +46,9 @@ train_cfg = dict(
neg_balance_thr=0), neg_balance_thr=0),
pos_weight=-1, pos_weight=-1,
debug=False)) debug=False))
test_cfg = dict(rcnn=dict(score_thr=0.05, max_per_img=100, nms_thr=0.5)) test_cfg = dict(
rcnn=dict(
score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100))
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
data_root = 'data/coco/' data_root = 'data/coco/'
......
...@@ -81,7 +81,11 @@ test_cfg = dict( ...@@ -81,7 +81,11 @@ test_cfg = dict(
max_num=2000, max_num=2000,
nms_thr=0.7, nms_thr=0.7,
min_bbox_size=0), min_bbox_size=0),
rcnn=dict(score_thr=0.05, max_per_img=100, nms_thr=0.5)) rcnn=dict(
score_thr=0.05, nms=dict(type='nms', iou_thr=0.5), max_per_img=100)
# soft-nms is also supported for rcnn testing
# e.g., nms=dict(type='soft_nms', iou_thr=0.5, min_score=0.05)
)
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
data_root = 'data/coco/' data_root = 'data/coco/'
......
...@@ -94,7 +94,10 @@ test_cfg = dict( ...@@ -94,7 +94,10 @@ test_cfg = dict(
nms_thr=0.7, nms_thr=0.7,
min_bbox_size=0), min_bbox_size=0),
rcnn=dict( rcnn=dict(
score_thr=0.05, max_per_img=100, nms_thr=0.5, mask_thr_binary=0.5)) score_thr=0.05,
nms=dict(type='nms', iou_thr=0.5),
max_per_img=100,
mask_thr_binary=0.5))
# dataset settings # dataset settings
dataset_type = 'CocoDataset' dataset_type = 'CocoDataset'
data_root = 'data/coco/' data_root = 'data/coco/'
......
import torch import torch
from mmdet.ops import nms from mmdet.ops.nms import nms_wrapper
def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_thr, max_num=-1): def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_cfg, max_num=-1):
"""NMS for multi-class bboxes. """NMS for multi-class bboxes.
Args: Args:
...@@ -21,6 +21,9 @@ def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_thr, max_num=-1): ...@@ -21,6 +21,9 @@ def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_thr, max_num=-1):
""" """
num_classes = multi_scores.shape[1] num_classes = multi_scores.shape[1]
bboxes, labels = [], [] bboxes, labels = [], []
nms_cfg_ = nms_cfg.copy()
nms_type = nms_cfg_.pop('type', 'nms')
nms_op = getattr(nms_wrapper, nms_type)
for i in range(1, num_classes): for i in range(1, num_classes):
cls_inds = multi_scores[:, i] > score_thr cls_inds = multi_scores[:, i] > score_thr
if not cls_inds.any(): if not cls_inds.any():
...@@ -32,11 +35,9 @@ def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_thr, max_num=-1): ...@@ -32,11 +35,9 @@ def multiclass_nms(multi_bboxes, multi_scores, score_thr, nms_thr, max_num=-1):
_bboxes = multi_bboxes[cls_inds, i * 4:(i + 1) * 4] _bboxes = multi_bboxes[cls_inds, i * 4:(i + 1) * 4]
_scores = multi_scores[cls_inds, i] _scores = multi_scores[cls_inds, i]
cls_dets = torch.cat([_bboxes, _scores[:, None]], dim=1) cls_dets = torch.cat([_bboxes, _scores[:, None]], dim=1)
# perform nms cls_dets, _ = nms_op(cls_dets, **nms_cfg_)
nms_keep = nms(cls_dets, nms_thr)
cls_dets = cls_dets[nms_keep, :]
cls_labels = multi_bboxes.new_full( cls_labels = multi_bboxes.new_full(
(len(nms_keep), ), i - 1, dtype=torch.long) (cls_dets.shape[0], ), i - 1, dtype=torch.long)
bboxes.append(cls_dets) bboxes.append(cls_dets)
labels.append(cls_labels) labels.append(cls_labels)
if bboxes: if bboxes:
......
...@@ -29,9 +29,7 @@ def merge_aug_proposals(aug_proposals, img_metas, rpn_test_cfg): ...@@ -29,9 +29,7 @@ def merge_aug_proposals(aug_proposals, img_metas, rpn_test_cfg):
scale_factor, flip) scale_factor, flip)
recovered_proposals.append(_proposals) recovered_proposals.append(_proposals)
aug_proposals = torch.cat(recovered_proposals, dim=0) aug_proposals = torch.cat(recovered_proposals, dim=0)
nms_keep = nms(aug_proposals, rpn_test_cfg.nms_thr, merged_proposals, _ = nms(aug_proposals, rpn_test_cfg.nms_thr)
aug_proposals.get_device())
merged_proposals = aug_proposals[nms_keep, :]
scores = merged_proposals[:, 4] scores = merged_proposals[:, 4]
_, order = scores.sort(0, descending=True) _, order = scores.sort(0, descending=True)
num = min(rpn_test_cfg.max_num, merged_proposals.shape[0]) num = min(rpn_test_cfg.max_num, merged_proposals.shape[0])
......
...@@ -100,7 +100,7 @@ class BBoxHead(nn.Module): ...@@ -100,7 +100,7 @@ class BBoxHead(nn.Module):
img_shape, img_shape,
scale_factor, scale_factor,
rescale=False, rescale=False,
nms_cfg=None): cfg=None):
if isinstance(cls_score, list): if isinstance(cls_score, list):
cls_score = sum(cls_score) / float(len(cls_score)) cls_score = sum(cls_score) / float(len(cls_score))
scores = F.softmax(cls_score, dim=1) if cls_score is not None else None scores = F.softmax(cls_score, dim=1) if cls_score is not None else None
...@@ -115,12 +115,11 @@ class BBoxHead(nn.Module): ...@@ -115,12 +115,11 @@ class BBoxHead(nn.Module):
if rescale: if rescale:
bboxes /= scale_factor bboxes /= scale_factor
if nms_cfg is None: if cfg is None:
return bboxes, scores return bboxes, scores
else: else:
det_bboxes, det_labels = multiclass_nms( det_bboxes, det_labels = multiclass_nms(
bboxes, scores, nms_cfg.score_thr, nms_cfg.nms_thr, bboxes, scores, cfg.score_thr, cfg.nms, cfg.max_per_img)
nms_cfg.max_per_img)
return det_bboxes, det_labels return det_bboxes, det_labels
......
...@@ -218,7 +218,7 @@ class CascadeRCNN(BaseDetector, RPNTestMixin): ...@@ -218,7 +218,7 @@ class CascadeRCNN(BaseDetector, RPNTestMixin):
img_shape, img_shape,
scale_factor, scale_factor,
rescale=rescale, rescale=rescale,
nms_cfg=rcnn_test_cfg) cfg=rcnn_test_cfg)
bbox_result = bbox2result(det_bboxes, det_labels, bbox_result = bbox2result(det_bboxes, det_labels,
bbox_head.num_classes) bbox_head.num_classes)
ms_bbox_result['stage{}'.format(i)] = bbox_result ms_bbox_result['stage{}'.format(i)] = bbox_result
...@@ -256,7 +256,7 @@ class CascadeRCNN(BaseDetector, RPNTestMixin): ...@@ -256,7 +256,7 @@ class CascadeRCNN(BaseDetector, RPNTestMixin):
img_shape, img_shape,
scale_factor, scale_factor,
rescale=rescale, rescale=rescale,
nms_cfg=rcnn_test_cfg) cfg=rcnn_test_cfg)
bbox_result = bbox2result(det_bboxes, det_labels, bbox_result = bbox2result(det_bboxes, det_labels,
self.bbox_head[-1].num_classes) self.bbox_head[-1].num_classes)
ms_bbox_result['ensemble'] = bbox_result ms_bbox_result['ensemble'] = bbox_result
......
...@@ -47,7 +47,7 @@ class BBoxTestMixin(object): ...@@ -47,7 +47,7 @@ class BBoxTestMixin(object):
img_shape, img_shape,
scale_factor, scale_factor,
rescale=rescale, rescale=rescale,
nms_cfg=rcnn_test_cfg) cfg=rcnn_test_cfg)
return det_bboxes, det_labels return det_bboxes, det_labels
def aug_test_bboxes(self, feats, img_metas, proposal_list, rcnn_test_cfg): def aug_test_bboxes(self, feats, img_metas, proposal_list, rcnn_test_cfg):
...@@ -73,15 +73,15 @@ class BBoxTestMixin(object): ...@@ -73,15 +73,15 @@ class BBoxTestMixin(object):
img_shape, img_shape,
scale_factor, scale_factor,
rescale=False, rescale=False,
nms_cfg=None) cfg=None)
aug_bboxes.append(bboxes) aug_bboxes.append(bboxes)
aug_scores.append(scores) aug_scores.append(scores)
# after merging, bboxes will be rescaled to the original image size # after merging, bboxes will be rescaled to the original image size
merged_bboxes, merged_scores = merge_aug_bboxes( merged_bboxes, merged_scores = merge_aug_bboxes(
aug_bboxes, aug_scores, img_metas, self.test_cfg.rcnn) aug_bboxes, aug_scores, img_metas, rcnn_test_cfg)
det_bboxes, det_labels = multiclass_nms( det_bboxes, det_labels = multiclass_nms(
merged_bboxes, merged_scores, self.test_cfg.rcnn.score_thr, merged_bboxes, merged_scores, rcnn_test_cfg.score_thr,
self.test_cfg.rcnn.nms_thr, self.test_cfg.rcnn.max_per_img) rcnn_test_cfg.nms, rcnn_test_cfg.max_per_img)
return det_bboxes, det_labels return det_bboxes, det_labels
......
...@@ -234,13 +234,13 @@ class RPNHead(nn.Module): ...@@ -234,13 +234,13 @@ class RPNHead(nn.Module):
proposals = proposals[valid_inds, :] proposals = proposals[valid_inds, :]
scores = scores[valid_inds] scores = scores[valid_inds]
proposals = torch.cat([proposals, scores.unsqueeze(-1)], dim=-1) proposals = torch.cat([proposals, scores.unsqueeze(-1)], dim=-1)
nms_keep = nms(proposals, cfg.nms_thr)[:cfg.nms_post] proposals, _ = nms(proposals, cfg.nms_thr)
proposals = proposals[nms_keep, :] proposals = proposals[:cfg.nms_post, :]
mlvl_proposals.append(proposals) mlvl_proposals.append(proposals)
proposals = torch.cat(mlvl_proposals, 0) proposals = torch.cat(mlvl_proposals, 0)
if cfg.nms_across_levels: if cfg.nms_across_levels:
nms_keep = nms(proposals, cfg.nms_thr)[:cfg.max_num] proposals, _ = nms(proposals, cfg.nms_thr)
proposals = proposals[nms_keep, :] proposals = proposals[:cfg.max_num, :]
else: else:
scores = proposals[:, 4] scores = proposals[:, 4]
_, order = scores.sort(0, descending=True) _, order = scores.sort(0, descending=True)
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# Copyright (c) University of Maryland, College Park # Copyright (c) University of Maryland, College Park
# Licensed under The MIT License [see LICENSE for details] # Licensed under The MIT License [see LICENSE for details]
# Written by Navaneeth Bodla and Bharat Singh # Written by Navaneeth Bodla and Bharat Singh
# Modified by Kai Chen
# ---------------------------------------------------------- # ----------------------------------------------------------
import numpy as np import numpy as np
...@@ -15,12 +16,13 @@ cdef inline np.float32_t max(np.float32_t a, np.float32_t b): ...@@ -15,12 +16,13 @@ cdef inline np.float32_t max(np.float32_t a, np.float32_t b):
cdef inline np.float32_t min(np.float32_t a, np.float32_t b): cdef inline np.float32_t min(np.float32_t a, np.float32_t b):
return a if a <= b else b return a if a <= b else b
def cpu_soft_nms( def cpu_soft_nms(
np.ndarray[float, ndim=2] boxes_in, np.ndarray[float, ndim=2] boxes_in,
float iou_thr,
unsigned int method=1,
float sigma=0.5, float sigma=0.5,
float Nt=0.3, float min_score=0.001,
float threshold=0.001,
unsigned int method=0
): ):
boxes = boxes_in.copy() boxes = boxes_in.copy()
cdef unsigned int N = boxes.shape[0] cdef unsigned int N = boxes.shape[0]
...@@ -36,11 +38,11 @@ def cpu_soft_nms( ...@@ -36,11 +38,11 @@ def cpu_soft_nms(
maxscore = boxes[i, 4] maxscore = boxes[i, 4]
maxpos = i maxpos = i
tx1 = boxes[i,0] tx1 = boxes[i, 0]
ty1 = boxes[i,1] ty1 = boxes[i, 1]
tx2 = boxes[i,2] tx2 = boxes[i, 2]
ty2 = boxes[i,3] ty2 = boxes[i, 3]
ts = boxes[i,4] ts = boxes[i, 4]
ti = inds[i] ti = inds[i]
pos = i + 1 pos = i + 1
...@@ -52,26 +54,26 @@ def cpu_soft_nms( ...@@ -52,26 +54,26 @@ def cpu_soft_nms(
pos = pos + 1 pos = pos + 1
# add max box as a detection # add max box as a detection
boxes[i,0] = boxes[maxpos,0] boxes[i, 0] = boxes[maxpos, 0]
boxes[i,1] = boxes[maxpos,1] boxes[i, 1] = boxes[maxpos, 1]
boxes[i,2] = boxes[maxpos,2] boxes[i, 2] = boxes[maxpos, 2]
boxes[i,3] = boxes[maxpos,3] boxes[i, 3] = boxes[maxpos, 3]
boxes[i,4] = boxes[maxpos,4] boxes[i, 4] = boxes[maxpos, 4]
inds[i] = inds[maxpos] inds[i] = inds[maxpos]
# swap ith box with position of max box # swap ith box with position of max box
boxes[maxpos,0] = tx1 boxes[maxpos, 0] = tx1
boxes[maxpos,1] = ty1 boxes[maxpos, 1] = ty1
boxes[maxpos,2] = tx2 boxes[maxpos, 2] = tx2
boxes[maxpos,3] = ty2 boxes[maxpos, 3] = ty2
boxes[maxpos,4] = ts boxes[maxpos, 4] = ts
inds[maxpos] = ti inds[maxpos] = ti
tx1 = boxes[i,0] tx1 = boxes[i, 0]
ty1 = boxes[i,1] ty1 = boxes[i, 1]
tx2 = boxes[i,2] tx2 = boxes[i, 2]
ty2 = boxes[i,3] ty2 = boxes[i, 3]
ts = boxes[i,4] ts = boxes[i, 4]
pos = i + 1 pos = i + 1
# NMS iterations, note that N changes if detection boxes fall below # NMS iterations, note that N changes if detection boxes fall below
...@@ -89,35 +91,35 @@ def cpu_soft_nms( ...@@ -89,35 +91,35 @@ def cpu_soft_nms(
ih = (min(ty2, y2) - max(ty1, y1) + 1) ih = (min(ty2, y2) - max(ty1, y1) + 1)
if ih > 0: if ih > 0:
ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih) ua = float((tx2 - tx1 + 1) * (ty2 - ty1 + 1) + area - iw * ih)
ov = iw * ih / ua #iou between max box and detection box ov = iw * ih / ua # iou between max box and detection box
if method == 1: # linear if method == 1: # linear
if ov > Nt: if ov > iou_thr:
weight = 1 - ov weight = 1 - ov
else: else:
weight = 1 weight = 1
elif method == 2: # gaussian elif method == 2: # gaussian
weight = np.exp(-(ov * ov)/sigma) weight = np.exp(-(ov * ov) / sigma)
else: # original NMS else: # original NMS
if ov > Nt: if ov > iou_thr:
weight = 0 weight = 0
else: else:
weight = 1 weight = 1
boxes[pos, 4] = weight*boxes[pos, 4] boxes[pos, 4] = weight * boxes[pos, 4]
# if box score falls below threshold, discard the box by # if box score falls below threshold, discard the box by
# swapping with last box update N # swapping with last box update N
if boxes[pos, 4] < threshold: if boxes[pos, 4] < min_score:
boxes[pos,0] = boxes[N-1, 0] boxes[pos, 0] = boxes[N-1, 0]
boxes[pos,1] = boxes[N-1, 1] boxes[pos, 1] = boxes[N-1, 1]
boxes[pos,2] = boxes[N-1, 2] boxes[pos, 2] = boxes[N-1, 2]
boxes[pos,3] = boxes[N-1, 3] boxes[pos, 3] = boxes[N-1, 3]
boxes[pos,4] = boxes[N-1, 4] boxes[pos, 4] = boxes[N-1, 4]
inds[pos] = inds[N-1] inds[pos] = inds[N - 1]
N = N - 1 N = N - 1
pos = pos - 1 pos = pos - 1
pos = pos + 1 pos = pos + 1
return boxes[:N], inds[:N] return boxes[:N], inds[:N]
\ No newline at end of file
...@@ -6,43 +6,58 @@ from .cpu_nms import cpu_nms ...@@ -6,43 +6,58 @@ from .cpu_nms import cpu_nms
from .cpu_soft_nms import cpu_soft_nms from .cpu_soft_nms import cpu_soft_nms
def nms(dets, thresh, device_id=None): def nms(dets, iou_thr, device_id=None):
"""Dispatch to either CPU or GPU NMS implementations.""" """Dispatch to either CPU or GPU NMS implementations."""
tensor_device = None
if isinstance(dets, torch.Tensor): if isinstance(dets, torch.Tensor):
tensor_device = dets.device is_tensor = True
if dets.is_cuda: if dets.is_cuda:
device_id = dets.get_device() device_id = dets.get_device()
dets = dets.detach().cpu().numpy() dets_np = dets.detach().cpu().numpy()
assert isinstance(dets, np.ndarray) elif isinstance(dets, np.ndarray):
is_tensor = False
dets_np = dets
else:
raise TypeError(
'dets must be either a Tensor or numpy array, but got {}'.format(
type(dets)))
if dets.shape[0] == 0: if dets_np.shape[0] == 0:
inds = [] inds = []
else: else:
inds = (gpu_nms(dets, thresh, device_id=device_id) inds = (gpu_nms(dets_np, iou_thr, device_id=device_id)
if device_id is not None else cpu_nms(dets, thresh)) if device_id is not None else cpu_nms(dets_np, iou_thr))
if tensor_device: if is_tensor:
return torch.Tensor(inds).long().to(tensor_device) inds = dets.new_tensor(inds, dtype=torch.long)
else: else:
return np.array(inds, dtype=np.int) inds = np.array(inds, dtype=np.int64)
return dets[inds, :], inds
def soft_nms(dets, Nt=0.3, method=1, sigma=0.5, min_score=0): def soft_nms(dets, iou_thr, method='linear', sigma=0.5, min_score=1e-3):
if isinstance(dets, torch.Tensor): if isinstance(dets, torch.Tensor):
_dets = dets.detach().cpu().numpy() is_tensor = True
dets_np = dets.detach().cpu().numpy()
elif isinstance(dets, np.ndarray):
is_tensor = False
dets_np = dets
else: else:
_dets = dets.copy() raise TypeError(
assert isinstance(_dets, np.ndarray) 'dets must be either a Tensor or numpy array, but got {}'.format(
type(dets)))
method_codes = {'linear': 1, 'gaussian': 2}
if method not in method_codes:
raise ValueError('Invalid method for SoftNMS: {}'.format(method))
new_dets, inds = cpu_soft_nms( new_dets, inds = cpu_soft_nms(
_dets, Nt=Nt, method=method, sigma=sigma, threshold=min_score) dets_np,
iou_thr,
if isinstance(dets, torch.Tensor): method=method_codes[method],
return dets.new_tensor( sigma=sigma,
inds, dtype=torch.long), dets.new_tensor(new_dets) min_score=min_score)
if is_tensor:
return dets.new_tensor(new_dets), dets.new_tensor(
inds, dtype=torch.long)
else: else:
return np.array( return new_dets.astype(np.float32), inds.astype(np.int64)
inds, dtype=np.int), np.array(
new_dets, dtype=np.float32)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment