前言
本篇是MMdet逐行解读第二篇,代码地址:mmdet/bbox/assigners/max_iou_assigner.py。由于源码assigner的输入参数多而且不易理解,因此本文是从由简到难的顺序逐步分析各个参数的作用。历史文章如下:
AnchorGenerator解读
1、关闭match_low_quality参数
我们首先构造了一个match_low_quality的assigner,该参数含义后面会讲。这一节主要分析pos_iou_thr和neg_iou_thr两个参数。另外,我又人为的分别构建了四个预测框和四个GT。
import torch
from mmdet.core.bbox import build_assigner
# maxIOU模块调试
config = dict(
# 最大 IoU 原则分配器
type='MaxIoUAssigner',
# 正样本阈值
pos_iou_thr=0.5,
# 负样本阈值
neg_iou_thr=0.4,
# 正样本阈值下限
min_pos_iou=0., # 仅当开启匹配低质量时该阈值才发挥作用
match_low_quality = False, #先为FALSE
# 忽略 bboes 的阈值,-1表示不忽略
ignore_iof_thr=-1)
assigner = build_assigner(config) # 构建一个分配器
bboxes = torch.Tensor([[0, 0, 10, 10], [10, 10, 20, 20],
[3, 3, 6, 6],[2, 2, 3, 3]])
gt_bboxes = torch.Tensor([[0, 0, 10, 9], [10,10,19,19],
[10,10,15,15],[3,3,4,4]])
res = assigner.assign(bboxes, gt_bboxes)
print(res.gt_inds) # tensor([1, 2, 0, 0])
首先从运行结果来看,bboxes和gt_bboxes的匹配结果为[1,2,0,0]。其含义是指:第一个bbox匹配第一个gt,第二个bbox匹配第二个gt,第三四个bbox匹配背景(0表示背景)。接下来,我们分析源码:
overlaps = self.iou_calculator(gt_bboxes, bboxes)
# 创建一个-1的tensor,用来保存匹配的结果。-1表示当前bbox为忽略样本
assigned_gt_inds = overlaps.new_full((num_bboxes, ),
-1,
dtype=torch.long)
# 从列方向看分别得到每个bbox和哪个gtbox的iou最大
max_overlaps, argmax_overlaps = overlaps.max(dim=0)
# 将iou低于阈值[0,0.4]的置为0,即处于该范围阈值的bbox为背景。
if isinstance(self.neg_iou_thr, float):
assigned_gt_inds[(max_overlaps >= 0)
& (max_overlaps < self.neg_iou_thr)] = 0
#若iou处于[0.5,1]范围的,将bbox置为ind+1,表示bbox和该gt匹配
pos_inds = max_overlaps >= self.pos_iou_thr
assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1
其实实际执行的代码只有上述几行,注释我差不多已经注明了,我这里画个图阐述下,读者可以结合注释和图自行理解。首先计算每个gt和所有bbox的iou,得到一个4*4的iou矩阵overlaps。
之后再dim=0上执行max操作,得到的max_overlaps=[0.9,0.81,0.11,0.01],argmax_overlaps=[0,1,3,0]。也就是图中带颜色的字体。之后,绿色字体由于<0.4故被划分为背景;红色的两个bbox>0.5故分为正样本,但是最终的结果向量assigned_gt_inds需要存储每个bbox和第几个gt匹配,以bbox1为例,是和index=0的gt匹配,但0已被背景占用了,故需要+1。即bbox1和第一个gt1匹配。故最终结果向量就是代码执行出的[1,2,0,0]。
2、开启match_low_quality参数
上节我们是关闭了该参数,从结果上来说,总共有四个gt,但是最终只有两个gt匹配成功。有两个gt未进行匹配。从保证召回率角度讲,这样肯定不合理。因此,assigner又设置了match_low_quality参数,即尽量要使所有gt都有一个anchor。这次我们开启该参数来学习该参数的作用,需要额外注意,该参数需要和min_pos_iou参数搭配使用。同理,先看执行效果:
import torch
from mmdet.core.bbox import build_assigner
# maxIOU模块调试
config = dict(
# 最大 IoU 原则分配器
type='MaxIoUAssigner',
# 正样本阈值
pos_iou_thr=0.5,
# 负样本阈值
neg_iou_thr=0.4,
# 正样本阈值下限
min_pos_iou=0., # 仅当开启匹配低质量时该阈值才发挥作用
match_low_quality = True, # 开启该参数
# 忽略 bboes 的阈值,-1表示不忽略
ignore_iof_thr=-1)
assigner = build_assigner(config) # 构建一个分配器
bboxes = torch.Tensor([[0, 0, 10, 10], [10, 10, 20, 20],
[3, 3, 6, 6],[2, 2, 3, 3]])
gt_bboxes = torch.Tensor([[0, 0, 10, 9], [10,10,19,19],
[10,10,15,15],[3,3,4,4]])
res = assigner.assign(bboxes, gt_bboxes)
print(res.gt_inds) # tensor([1, 3,4,0])
开启后执行结果成了[1,3,4,0],即第二个bbox和第三个gt匹配,第三个bbox和第四个匹配,让人一头雾水。下面分析下代码:
'''
overlaps = self.iou_calculator(gt_bboxes, bboxes)
# 创建一个-1的tensor,用来保存匹配的结果。-1表示当前bbox为忽略样本
assigned_gt_inds = overlaps.new_full((num_bboxes, ),
-1,
dtype=torch.long)
# 从列方向看分别得到每个bbox和哪个gtbox的iou最大
max_overlaps, argmax_overlaps = overlaps.max(dim=0)
# 将iou低于阈值[0,0.4]的置为0,即处于该范围阈值的bbox为背景。
if isinstance(self.neg_iou_thr, float):
assigned_gt_inds[(max_overlaps >= 0)
& (max_overlaps < self.neg_iou_thr)] = 0
#若iou处于[0.5,1]范围的,将bbox置为ind+1,表示bbox和该gt匹配
pos_inds = max_overlaps >= self.pos_iou_thr
assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1
'''
#获取每个gt和哪个bbox的iou最大。
gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1)
if self.match_low_quality:
# 遍历每个gt
for i in range(num_gts):
# 若阈值超过0.
if gt_max_overlaps[i] >= self.min_pos_iou:
# 将所有gt均进行匹配
if self.gt_max_assign_all:
# 找到和gt最大iou相等的overlaps的位置
max_iou_inds = overlaps[i, :]==gt_max_overlaps[i]
# 同理+1
assigned_gt_inds[max_iou_inds] = i + 1
上述标绿代码就是未开启参数的效果,即执行完注释代码后匹配结果为[1,2,0,0]。下面分析未注释部分:
首先标红的字体就是gt_max_overlaps。即每个gt和哪个bbox的iou最大。然后遍历每个gt,若最大iou>min_pos_iou,则将该bbox和gt进行匹配。故bbox1显然还和gt1匹配,bbox2还和gt2匹配,但是遍历到第三个gt3时候,由于其还是和bbox2的iou最高,故将该bbox2匹配改了gt3,把gt2给覆盖掉了。即此时的assigned_gt_inds由[1,2,0,0]变成了[1,3,0,0]。即Bbox2匹配给了一个低质量gt。 之后,遍历gt4的时候,将其匹配给bbox3,也就得到了最终匹配结果assigned_gt_inds = [1,3,4,0]。从最终效果上看,确实能够保证将所有gt匹配上,但是有可能后匹配的把前面iou高的匹配给覆盖掉,导致低质量匹配产生。
总结
在一阶段算法或者RPN阶段,可以开启低质量匹配,保证最大的召回率。但是在第二阶段RoI Head中要确保准确率,就不开启低质量匹配了。若有问题欢迎+vx:wulele2541612007,拉你进群探讨交流。