# NMS，SoftNMS以及ROIPooling，ROIAlign

## 一 NMS

def NMS(boxs, scores, nms_threshold):
# input boxes(N*4), scores(N,)
x1 = boxs[:,0]
y1 = boxs[:,1]
x2 = boxs[:,2]
y2 = boxs[:,3]
area = (boxs[:,2]-boxs[:,0]) * (boxs[:,3]-boxs[:,1])
_, order = scores.sort(descending=True)
keep = []
while order.numel()>0:
if order.numel() == 1:
keep.append(order[0].item())
break
i = order[-1]
keep.append(order[0].item())
order = order[:-1]
xx1 = x1[order].clamp(min=x1[i])
yy1 = y1[order].clamp(min=y1[i])
xx2 = x2[order].clamp(max=x2[i])
yy2 = y2[order].clamp(max=y2[i])
inter = (xx2-xx1).clamp(min=0) * (yy2-yy1).clamp(min=0)
iou = inter / (area[order] + area[i] -inter)
# torch版本>=1.6 以上除法不支持了，用如下除法
# iou = torch.true_divide(inter, area[order] + area[i] -inter)
idx = (iou<=nms_threshold).nonzero().squeeze()
order = order[idx]
return  keep


# 这款代码我觉得写得挺简洁，对比soft-nms官方代码更好理解
def box_soft_nms(bboxes, scores, labels, nms_threshold=0.3, soft_threshold=0.3, sigma=0.5, mode='union'):
"""
soft-nms implentation according the soft-nms paper
:param bboxes: all pred bbox
:param scores: all pred cls
:param labels: all detect class label，注：scores只是单纯的得分，需配合label才知道具体对应的是哪个类
:param nms_threshold: origin nms thres, for judging to reduce the cls score of high IoU pred bbox
:param soft_threshold: after cls score of high IoU pred bbox been reduced, soft_thres further filtering low score pred bbox
:return:
"""
unique_labels = labels.cpu().unique().cuda()    # 获取pascal voc 20类标签

box_keep = []
labels_keep = []
scores_keep = []
for c in unique_labels:             # 相当于NMS中对每一类的操作，对应step-1
c_boxes = bboxes[labels == c]   # bboxes、scores、labels一一对应，按照label == c就可以取出对应类别 c 的c_boxes、c_scores
c_scores = scores[labels == c]
weights = c_scores.clone()
x1 = c_boxes[:, 0]
y1 = c_boxes[:, 1]
x2 = c_boxes[:, 2]
y2 = c_boxes[:, 3]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)         # bbox面积
_, order = weights.sort(0, descending=True)   # bbox根据score降序排序，对应NMS中step-2
while order.numel() > 0:                      # 对应NMS中step-5
i = order[0]                              # 当前order中的top-1，保存之
box_keep.append(c_boxes[i])               # 保存bbox
labels_keep.append(c)                     # 保存cls_id
scores_keep.append(c_scores[i])           # 保存cls_score

if order.numel() == 1:  # 当前order就这么一个bbox了，那不玩了，下一个类的bbox操作吧
break

xx1 = x1[order[1:]].clamp(min=x1[i])      # 别忘了x1[i]对应x1[order[0]]，也即top-1，寻找Insection区域的坐标
yy1 = y1[order[1:]].clamp(min=y1[i])
xx2 = x2[order[1:]].clamp(max=x2[i])
yy2 = y2[order[1:]].clamp(max=y2[i])

w = (xx2 - xx1 + 1).clamp(min=0)          # Insection区域的宽、高、面积
h = (yy2 - yy1 + 1).clamp(min=0)
inter = w * h

# IoU中U的计算模式，两种union、min，比较容易理解
if mode == 'union':
ovr = inter / (areas[i] + areas[order[1:]] - inter)
elif mode == 'min':
ovr = inter / areas[order[1:]].clamp(max=areas[i])
else:
raise TypeError('Unknown nms mode: %s.' % mode)

# 经过origin NMS thres，得到高IoU的bboxes index，
# origin NMS操作就直接剔除掉这些bbox了，soft-NMS就是对这些bbox对应的score做权重降低
ids_t= (ovr>=nms_threshold).nonzero().squeeze()   # 高IoU的bbox，与inds = np.where(ovr >= nms_threshold)[0]功能类似

# torch.exp(-(ovr[ids_t] * ovr[ids_t]) / sigma)：这个比较好理解，对score做权重降低的参数，从fig 2、公式中都可以参考
# order[ids_t+1]：+1对应x1[order[0]]，也即top-1，若需映射回order中各个bbox，就必须+1
# 这样整体上就容易理解了，就是soft-nms的score抑制方式，未使用NMS中粗暴的直接score = 0的抑制方式
weights[[order[ids_t+1]]] *= torch.exp(-(ovr[ids_t] * ovr[ids_t]) / sigma)

# soft-nms对高IoU pred bbox的score调整了一次，soft_threshold仅用于对score抑制，score太小就不考虑了
ids = (weights[order[1:]] >= soft_threshold).nonzero().squeeze()   # 这一轮未被抑制的bbox
if ids.numel() == 0:   # 竟然全被干掉了，下一个类的bbox操作吧
break

c_boxes = c_boxes[order[1:]][ids]   # 先取得c_boxes[order[1:]]，再在其基础之上操作[ids]，获得这一轮未被抑制的bbox
c_scores = weights[order[1:]][ids]
_, order = c_scores.sort(0, descending=True)
if c_boxes.dim()==1:
c_boxes=c_boxes.unsqueeze(0)
c_scores=c_scores.unsqueeze(0)

x1 = c_boxes[:, 0]   # 因为bbox已经做了筛选了，areas需要重新计算一遍，抑制的bbox剔除掉
y1 = c_boxes[:, 1
x2 = c_boxes[:, 2]
y2 = c_boxes[:, 3]
areas = (x2 - x1 + 1) * (y2 - y1 + 1)

return box_keep, labels_keep, scores_keep    # scores_keep保存的是未做权重降低的score，降低权重的score仅用于soft-nms操作


## 二 ROIPooling及ROIAling

ROIPooling及ROIAling的原理参考https://www.cnblogs.com/ranjiewen/articles/8869703.html
1 主要对对于ROIPooling层，因为misalingment问题，特别对于小目标来说，feature map中一点偏差在映射到原图之后，会造成比较大的偏差。后面采用ROIAling，主要对于映射到feature map中的值采用双线性差值进行计算，减小误差。
2 对于反向传播的计算，对于传统的池化层，最大池化，梯度传播也是将梯度全部传递到最大单元上，平均池化，将梯度平均到整个bin块中。
3 对于ROIPooling层，也是对应将梯度传递给每个bin中最大的单元。而对于ROIAling，梯度传递，对于参与计算整个双线性差值的上下左右四个点都应该有梯度传递，传递系数应该和点的h，w距离相关，但是具体如何实现仍在学习。

03-20 518
06-14 422
08-24 9021
04-11 4273
10-24 95
02-14 1406