概述
对于目标检测算法而言,正负样本的分配,采样策略以及正负样本的数量和比例的设置等,对算法的精度有着显著的影响,了解其机制和原理便于我们加深对算法的了解和后续对算法的优化改进.
在检测算法中正负样本的设置主要包含两个关键的步骤:
1.assigner:为每一个先验框[anchor]分配属性
- 正样本
- 负样本
- 其它[即不当成正样本又不当成负样本处理,忽略]
2.sampler:采取某种策略[如随机采样]从分配好的正负样本中选出对应数量的正负样本进行训练
Faster RCNN—RPN
1.参数设置:
以下相关参数的设置和代码的讲解,来源于mmdetection
rpn=dict(
assigner=dict(
type='MaxIoUAssigner',
pos_iou_thr=0.7, #正样本阈值
neg_iou_thr=0.3, #负样本阈值
min_pos_iou=0.2, #最低正样本阈值
ignore_iof_thr=0.3),
sampler=dict(
type='RandomSampler',
num=256, #总数量
pos_fraction=0.5, #比例
neg_pos_ub=-1,
add_gt_as_proposals=False), #是否将gt添加到正样本中
allowed_border=128, #允许anchor尺寸超过图像边缘的最大像素
pos_weight=-1, #正样本的权重,如果小于0则为1,大于0则为对应设置的值
debug=False),
2. 整体步骤:
2.1.处理掉一些中心点坐标超过边界过多、宽高尺寸过大的anchor
inside_flags = anchor_inside_flags(flat_anchors, valid_flags,
img_meta['img_shape'][:2],
cfg.allowed_border) #128
anchors = flat_anchors[inside_flags.type(torch.bool), :]
def anchor_inside_flags(flat_anchors,
valid_flags,
img_shape,
allowed_border=0):
img_h, img_w = img_shape[:2]
if allowed_border >= 0:
inside_flags = valid_flags & \
(flat_anchors[:, 0] >= -allowed_border).type(torch.uint8) & \
(flat_anchors[:, 1] >= -allowed_border).type(torch.uint8) & \
(flat_anchors[:, 2] < img_w + allowed_border).type(torch.uint8) & \
(flat_anchors[:, 3] < img_h + allowed_border).type(torch.uint8)
else:
inside_flags = valid_flags
return inside_flags
2.2.assign :为每一个Anchor分配属性:
0–负样本
-1—忽略,既不是正样本也不是负样本
positive number— assigned gt[与哪一个gt_bbox对应,存放的是对应gt_bbox的索引]
计算步骤:
1)计算gt_bbox【g,4】与anchor_bbox[k,4]的iou,得到一个【g,k】的矩阵。
overlaps = bbox_overlaps(gt_bboxes, bboxes)
2)初始化属性矩阵,将所有anchor置为-1
assigned_gt_inds =overlaps.new_full((num_bboxes, ),-1,dtype=torch.long)
3)计算每一个Anchor与gt的最大iou和索引
max_overlaps, argmax_overlaps = overlaps.max(dim=0)
4)计算每一个gt与anchor的最大iou和索引
gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1)
5)遍历所有anchor,若iou<neg_iou_thr[0.3],将其置为0,当成负样本处理;若iou>pos_iou_thr[0.7],将其置为对应gt的索引,当成正样本处理;iou在两者之间的还是置为-1,忽略。
assigned_gt_inds[(max_overlaps >= 0) & (max_overlaps < self.neg_iou_thr)] = 0
pos_inds = max_overlaps >= self.pos_iou_thr
assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1 #[**此处对索引进行了加1操作]**
6)遍历所有的gt,若最大iou大于min_pos_iou[0.2],将其置为正样本(一个GT可能有多个或者一个anchor与之匹配)
if gt_max_overlaps[i] >= self.min_pos_iou:
if self.gt_max_assign_all: #true
max_iou_inds = overlaps[i, :] == gt_max_overlaps[i] #(与该GT的iou等于最大iou的bbox都分配到该gt)
assigned_gt_inds[max_iou_inds] = i + 1
else:
assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1
7)将输出结果写入AssignResult类中。
class AssignResult(object):
def __init__(self, num_gts, gt_inds, max_overlaps, labels=None):
self.num_gts = num_gts 正样本数量
self.gt_inds = gt_inds anchor属性(-1;no care 0 : 负样本 1-based:与该框匹配的gt)
self.max_overlaps = max_overlaps每一个候选框与gt的最大iou
self.labels = labels anchor的类别
2.3.samper: 在分好属性的anchor中按照一定的策略挑选出对应数量的正负样本框
计算步骤
1)计算期望的正样本数量:总数量*比例
num_expected_pos = int(self.num * self.pos_fraction)
2)sample_pos: 生成正样本,如果正样本数量 < 预期数量,则输出所有正样本索引【一般达不到预期】;如果正样本 > 期望数,则在正样本中随机选取对应数量的正样本[此处采用的随机抽样的方法选取正样本]
pos_inds = self.pos_sampler._sample_pos(assign_result, num_expected_pos, bboxes=bboxes, **kwargs)
def _sample_pos(self, assign_result, num_expected, **kwargs):
"""Randomly sample some positive samples."""
pos_inds = torch.nonzero(assign_result.gt_inds > 0) #取出正样本的索引
if pos_inds.numel() != 0:
pos_inds = pos_inds.squeeze(1)
if pos_inds.numel() <= num_expected:
return p