1、YOLOv3
使用MaxIoUAssigner策略来给gt分配样本,基本上保证每个gt都有唯一的anchor对应,匹配的原则是该anchor与gt的IOU最大且大于FG_THRESH,这种分配制度会导致正样本比较少,cls和bbox分支训练起来可能比较慢。在剩余的anchor中,如果有anchor跟所有gt的IOU都小于BG_THRESH,则将此类anchor设为负样本,如果有anchor跟所有gt的IOU大于BG_THRESH且小于FG_THRESH,则忽视掉此类anchor。
下面以Towards-Realtime-MOT/utils/utils.py中的代码为例:
def build_targets_thres(target, anchor_wh, nA, nC, nGh, nGw):
ID_THRESH = 0.5
FG_THRESH = 0.5
BG_THRESH = 0.4
nB = len(target) # number of images in batch
assert(len(anchor_wh)==nA)
tbox = torch.zeros(nB, nA, nGh, nGw, 4).cuda() # batch size, anchors, grid size
tconf = torch.LongTensor(nB, nA, nGh, nGw).fill_(0).cuda()
tid = torch.LongTensor(nB, nA, nGh, nGw, 1).fill_(-1).cuda()
for b in range(nB):
t = target[b]
t_id = t[:, 1].clone().long().cuda()
t = t[:,[0,2,3,4,5]]
nTb = len(t) # number of targets
if nTb == 0:
continue
gxy, gwh = t[: , 1:3].clone() , t[:, 3:5].clone()
gxy[:, 0] = gxy[:, 0] * nGw
gxy[:, 1] = gxy[:, 1] * nGh
gwh[:, 0] = gwh[:, 0] * nGw
gwh[:, 1] = gwh[:, 1] * nGh
gxy[:, 0] = torch.clamp(gxy[:, 0], min=0, max=nGw -1)
gxy[:, 1] = torch.clamp(gxy[:, 1], min=0, max=nGh -1)
gt_boxes = torch.cat([gxy, gwh], dim=1)
anchor_mesh = generate_anchor(nGh, nGw, anchor_wh)
anchor_list = anchor_mesh.permute(0,2,3,1).contiguous().view(-1, 4)
iou_pdist = bbox_iou(anchor_list, gt_boxes)
iou_max, max_gt_index = torch.max(iou_pdist, dim=1) ## 取出每个pre与gt的IOU最大值
iou_map = iou_max.view(nA, nGh, nGw)
gt_index_map = max_gt_index.view(nA, nGh, nGw)
id_index = iou_map > ID_THRESH
fg_index = iou_map > FG_THRESH ## 若IOU大于FG_THRESH,则为foreground
bg_index = iou_map < BG_THRESH ## 若IOU小于BG_THRESH,则为background
ign_index = (iou_map < FG_THRESH) * (iou_map > BG_THRESH) ## 若IOU大于BG_THRESH并小于FG_THRESH,则ignore
tconf[b][fg_index] = 1
tconf[b][bg_index] = 0
tconf[b][ign_index] = -1
gt_index = gt_index_map[fg_index]
gt_box_list = gt_boxes[gt_index]
gt_id_list = t_id[gt_index_map[id_index]]
if torch.sum(fg_index) > 0:
tid[b][id_index] = gt_id_list.unsqueeze(1)
fg_anchor_list = anchor_list.view(nA, nGh, nGw, 4)[fg_index]
delta_target = encode_delta(gt_box_list, fg_anchor_list)
tbox[b][fg_index] = delta_target
return tconf, tbox, tid
2、YOLOv4
yolov4为了增加正样本,采用multi anchor策略,只要大于IoU阈值的anchor,都视为正样本
3、YOLOv5
确定gt是否匹配当前特征图的anchors
因为yolov5是多尺度预测,所以首先需要确定gt应该跟哪个尺度的特征图上的anchor进行匹配。规则为:gt的宽高分别与当前尺度下的anchor的宽高进行比较,如果它们的比例在[1/4,4]之间,则当前gt可以与当前尺度下的anchor进行匹配。
下面以yolov5/utils/loss.py代码为例:
# wh ratio
r = t[..., 4:6] / anchors[:, None]
# compare
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']
# filter
t = t[j]
将与gt中心点邻近的两个点也作为正样本点(即总共有3个正样本点)
将gt所在的中心点视作一个ceil,并将该ceil划分成4个象限,如果gt的中心点位于该ceil中的第四象限,则将该ceil右边的单元格以及下边的单元格也视为正样本点(如下图的黄色单元格)
anchors, shape = self.anchors[i], p[i].shape
gain[2:6] = torch.tensor(shape)[[3, 2, 3, 2]] ## 取当前尺度特征图的w、h,如果原图尺寸为[640,640],下采样8倍后,特征图的尺寸就变成[80,80],则gain[2:6]=[80,80,80,80]
t = targets * gain ## 将[0,1]之间的坐标映射到[0,80]上
g = 0.5 ## 偏移量,用于判断gt的x、y坐标在单元格的哪个象限
if nt:
## 求gt的宽高与anchor的宽高的比值
r = t[..., 4:6] / anchors[:, None]
## 判断比值是否在[1/4,4]这个范围内
j = torch.max(r, 1 / r).max(2)[0] < self.hyp['anchor_t']
## 挑选符合条件的gt
t = t[j]
gxy = t[:, 2:4] ## gt的x、y坐标
gxi = gain[[2, 3]] - gxy ## gt的x、y坐标到w、h的距离
## 由于gxy+gxi=[80,80],因此j和l互斥,k和m互斥
j, k = ((gxy % 1 < g) & (gxy > 1)).T
l, m = ((gxi % 1 < g) & (gxi > 1)).T
j = torch.stack((torch.ones_like(j), j, k, l, m))
## 将t复制成5份(gt中心点所在单元格加上该单元格的左、上、右、下的单元格)
t = t.repeat((5, 1, 1))[j]
offsets = (torch.zeros_like(gxy)[None] + off[:, None])[j]
4、YOLOX
yolox的正负样本分配策略的代码可以参考:YOLOX中的SimOTA_Cassiel_cx的博客-CSDN博客
这里简单记录下SimOTA(simple optimal transport assignment)的步骤:
- 确定候选正样本(计算anchor_box的中心点,若anchor_box的中心点落在gt内(或者在以gt的中心点为圆心,以center_radius为半径的圆内),就将该anchor视为候选正样本。如下图所示,红色框为gt,绿色框为蓝点预测的anchor_box,假设该anchor_box的中心点为绿点,由于绿点在红框内,因此该anchor_box便作为候选正样本)
- 计算候选正样本跟gt之间的cls loss和iou loss并以一定比例加权求和,作为cost,计算公式如下(如果anchor_box的中心点不在gt内,该anchor_box与gt的cost就会很大,对应代码中的100000.0 * (~is_in_boxes_and_center))
cost = ( pair_wise_cls_loss + 3.0 * pair_wise_ious_loss + 100000.0 * (~is_in_boxes_and_center) )
- 对候选正样本与gt之间的iou进行大小排序,挑选前10个(可自己调整)最大iou,对这些iou求和并取整,该数值作为当前gt的dynamic_k
- 对于该gt,取前dynamic_k个最小的cost的候选正样本,作为正样本
- 如果存在一个正样本匹配多个gt的情况,则选cost较小的gt来匹配(如,某anchor_box与gt_1和gt_2的cost分别为0.8和0.2,则取消该anchor_box与gt_1的匹配,只匹配gt_2)
5、YOLOv7
yolov7的正负样本分配策略为yolov5和yolox的结合体。首先使用yolov5的策略去挑选候选正样本,再使用yolox中的SimOTA策略去从候选正样本中挑选正样本。
【参考文章】
目标检测正负样本区分策略和平衡策略总结(一) - 知乎
yolov7正负样本分配详解 - 知乎
深入浅出Yolo系列之Yolox核心基础完整讲解 - 知乎