当前位置:   article > 正文

mmdetection3.2.0之RetinaNet源码解读_retinanet mmdet

retinanet mmdet

该片解读在官网的这篇文章的基础之上对细节进行补充,仅供自己学习使用,不足之处请在评论区指出。轻松掌握 MMDetection 中常用算法(一):RetinaNet 及配置详解 - 知乎 (zhihu.com)

本文是结合上面官方提供的文章和b站噼里啪啦博主的讲解结合起来学习的。

解读总共包含两部分,第一部分是对代码含义解读,第二部分就是对代码debug进行调试,了解数据流。 

本文不同于官方提供的解读文章共有两点:debug调试3.2.0版本代码位置说明。

1.RetinaNet网络结构图

 该图片引用B站噼里啪啦博主。该网络的创新点是提出Focal Loss焦点损失函数。从而使得一阶段检测器的精度能够超过二阶段检测器。

                                                              1.retinanet整体网络结构图

                                                                   2.retinanet分类回归分支图 

2.RetinaNet代码详解 

2.1Backbone

这里我选用的配置文件位置在configs/retinanet/retinanet_r18_fpn_1x_coco.py。配置文件的含义如下。

  •   retinanet表示算法名称
  •   r18表示骨干网络名
  •   caffe和PyTorch是指Bottleneck模块的区别,省略情况下是PyTorch
  •   fpn表示Neck模块采用了FPN结构
  •   mstrain表示多尺度训练,一般对应pipeline中的resize类
  •  1X表示1倍数的epoch训练,即12个epoch
  •   coco表示在COCO数据集上训练

以Resnet18为例,骨干网络配置如下。

  1. backbone=dict(
  2. #表示使用resnet18
  3. depth=18,
  4. #表示固定stem+第一个stage的权重,不进行训练
  5. frozen_stages=1,
  6. #使用pytorch提供的在imagenet上面训练过的权重作为预训练权重
  7. init_cfg=dict(checkpoint='torchvision://resnet18', type='Pretrained'),
  8. #所有BN层的可学习参数都需要梯度更新
  9. norm_cfg=dict(requires_grad=True, type='BN'),
  10. #backbone所有的BN层的均值和方差都直接采用全局预训练值,不进行更新
  11. norm_eval=True,
  12. #Resnet系列包含stem+4个stage输出
  13. num_stages=4,
  14. #表示四个stage的输出索引
  15. out_indices=(
  16. 0,
  17. 1,
  18. 2,
  19. 3,
  20. ),
  21. #默认采用pytorch模式
  22. style='pytorch',
  23. #骨架网络名
  24. type='ResNet'),
                                                     3.retinanet骨干网络配置 

 在训练过程中stem+第一个stage不进行训练即参数不更新。BN层的均值和方差不更新,但β和γ这些可学习参数进行更新。尽量不更改上面的设置,效果更稳定。

2.1.1out_indices

resnet提出了骨架网络设计范式即stem+ n个stage+cls head,其forward流程是stem->4个stage->分类head,stem的输出stride是4,而4个stage的输出stride是4,8,16,32。这里的stride指的是下采样率。因为retinanet后面需要接FPN,故需要输出4个尺度特征图,因此四个stage的索引都需要。

以下代码位置:mmdet/models/backbones/resnet.py的641行左右。简要代码如下

  1. for i, layer_name in enumerate(self.res_layers):
  2. res_layer = getattr(self, layer_name)
  3. x = res_layer(x)
  4. #如果i在out_incices中才保留
  5. if i in self.out_indices:
  6. outs.append(x)
  7. return tuple(outs)
                                                              4.out_indices代码 

 首先由下图可以看出,该骨干网络由stem+4个layer和分类head构成,其中每个layer又包含2个basicblock,接下来debug看一下self的属性构成是否和分析一样。

                                                               5.resnet18网络结构图 

 按照以下操作可以查看backbone网络结构。

                                                                6.debug  backbone 

 操作4及可以看到backbone网络构成和分析一致。

                                                                  7.backbone 的调试结果图 

 接着继续往下debug。循环四轮后结果如下,可以看出此时已经将layer0~layer3的输出均已添加至outs列表中。

                                                              8.呼应图4所需的outs输出层 

 2.1.2frozen_stages

该参数表示你想冻结前几个stages的权重,Resnet结构包括stem+4个stage。

  • frozen_stages=-1,表示全部可学习。
  • frozen_stages=0,表示stem权重固定。
  • frozen_stages=1,表示stem+第一个stage权重固定
  • frozen_stages=2,表示stem和前两个stage权重固定

以下代码所在位置mmdet/models/backbones/resnet.py的613行左右。

  1. #固定权重,需要两个步骤:1,设置eval模式; 2.requires_grad=False
  2. def _freeze_stages(self):
  3. if self.frozen_stages >= 0:
  4. #固定stem权重
  5. if self.deep_stem:
  6. self.stem.eval()
  7. for param in self.stem.parameters():
  8. param.requires_grad = False
  9. #上面的if条件不懂,直接跳到这里
  10. else:
  11. #将stem中的BN层和卷积层都设置为eval模式
  12. self.norm1.eval()
  13. #遍历stem中的BN层和卷积层
  14. for m in [self.conv1, self.norm1]:
  15. #遍历stem中的BN层和卷积层的参数
  16. for param in m.parameters():
  17. #设置上面遍历的参数不需要梯度更新
  18. param.requires_grad = False
  19. #固定stage权重
  20. for i in range(1, self.frozen_stages + 1):
  21. m = getattr(self, f'layer{i}')
  22. m.eval()
  23. for param in m.parameters():
  24. param.requires_grad = False
                                                      9.frozen_stages代码 
(1)debug调试查看stem的权重如何固定

    由图7我们可以看出stem的构成包含conv1+bn1+relu+maxpool。这里只需要更新stem中的conv1+bn1的参数即可。

   由以下debug图得知,m参数遍历conv1+bn1两大层(不遍历里面的具体参数),当m遍历到conv1时,conv1内包含的参数如绿色箭头2所示。然后param再遍历conv1内的参数,如绿色箭头3所指。

                                                                 10.stem权重固定数据流 
 (2)debug调试查看stage权重如何固定

     由图7我们可知backbone由stem+4个layer+分类head构成,而每个layer又包含一个ResLayer,每个ResLayer又包含两个BasicBlock。理解这里的stage指的是layer,

     在627行调试,可以看到m的结果如绿色箭头1,点击右侧的“视图”如绿色箭头2所示,页面就会呈现绿色箭头3的画面。debug调试后可以看出,m取出layer1的所有属性,而param则是依次取出绿色箭头所指的conv1->bn1->conv2...。

                                                             11.stage权重固定数据流 

   (3)train处的设置

需要特别注意的是:上述函数不能仅仅在类初始化的时候调用,因为在训练模式下,运行时候会调用model.train()导致BN层又进入train模式,最终BN没有固定,故需要在resnet中重写train方法。

以下代码的位置mmdet/models/backbones/resnet.py的648行左右。

  1. def train(self, mode=True):
  2. """Convert the model into training mode while keep normalization layer
  3. freezed."""
  4. #这行代码会导致BN进入train模式
  5. super(ResNet, self).train(mode)
  6. #再次调用,固定stem和前n个stage的BN
  7. self._freeze_stages()
  8. #如果所有的BN都采用全局均值和方差,则需要对整个网络的BN都开启eval模式
  9. if mode and self.norm_eval:
  10. for m in self.modules():
  11. # trick: eval have effect on BatchNorm only
  12. if isinstance(m, _BatchNorm):
  13. m.eval()
                                                         12.train处设置BN冻结 

2.1.3norm_cfg和norm_eval

norm_cfg表示所采用的归一化算子,一般是BN或者GN,而requires_grad表示该算子是否需要梯度,也就是是否进行参数更新,而布尔参数norm_eval是 用于控制整个骨架网络的归一化算子是否需要变成eval模式。

RetinaNet中用法是norm_cfg=dict(type='BN',requires_grad=True),表示通过Registry模式实例化BN类,并且设置为参数可学习。在MMdetection中会常看到通过字典配置方式来实例化某个类的做法,底层是采用了装饰器模式进行构建,最大好处是扩展性极强,类和类之间的耦合度降低。

2.1.4style

style='caffe'和style='pytorch'的差别就在Bottleneck模块中

                                                13.Bottleneck模块图 

Bottleneck是标准的1X1-3X3-1X1结构,考虑stride=2下采样的场景,caffe模式下,stride参数放置在第一个1X1卷积上,而Pytorch模式下,strde放在第二个3X3卷积上。

以下代码的位置:mmdet/models/backbones/resnet.py的154行左右。

  1. if self.style == 'pytorch':
  2. self.conv1_stride = 1
  3. self.conv2_stride = stride
  4. else:
  5. self.conv1_stride = stride
  6. self.conv2_stride = 1
                                                 14 .style代码

 出现两种模式的原因是因为 ResNet 本身就有不同的实现,torchvision 的 resnet 和早期 release 的 resnet 版本不一样,使得目标检测框架在使用 Backbone 的时候有两种不同的配置,不过目前新网络都是采用 PyTorch 模式。

2.2Neck

Neck模块即为FPN,其简要结构如下所示。

                                                                 15.FPN简要结构图 

MMdetection中对应的配置为:

  1. neck=dict(
  2. #额外输出层的特征图来源
  3. add_extra_convs='on_input',
  4. #resnet模块输出的4个尺度特征图通道数
  5. in_channels=[
  6. 64,
  7. 128,
  8. 256,
  9. 512,
  10. ],
  11. #FPN输出特征图个数
  12. num_outs=5,
  13. #FPN输出的每个尺度特征图通道数
  14. out_channels=256,
  15. #从输入多尺度特征图的第几个开始计算
  16. start_level=1,
  17. type='FPN')
                                                               16.neck配置文件 

 

前面说过 ResNet 输出 4 个不同尺度特征图 (c2,c3,c4,c5),stride 分别是 (4,8,16,32),通道数为 (256,512,1024,2048),通过配置文件我们可以知道:

  • start_level=1 说明虽然输入是 4 个特征图,但是实际上 FPN 中仅仅用了后面三个,可见图一,只用了C3,C4,C5。
  • num_outs=5 说明 FPN 模块虽然是接收 3 个特征图,但是输出 5 个特征图
  • add_extra_convs='on_input' 说明额外输出的 2 个特征图的来源是骨架网络输出,而不是 FPN 层本身输出又作为后面层的输入
  • out_channels=256 说明了 5 个输出特征图的通道数都是 256。

总结:FPN 模块接收 c3, c4, c5 三个特征图,输出 P3-P7 五个特征图,通道数都是 256, stride 为 (8,16,32,64,128),其中大 stride (特征图小)用于检测大物体,小 stride (特征图大)用于检测小物体。

 

2.3Head

Head的结构图可参见图2。

RetinaNet的Head包括分类和检测两个分支,且每个分支都包括4个卷积层,不进行参数共享,分类Head输出通道是num_class*K,检测head输出通道是4*K,K是anchor个数,虽然每个Head的分类和回归分支权重不共享,但5个输出特征图的Head模块权重是共享的。

其完整配置如下:

  1. bbox_head=dict(
  2. anchor_generator=dict(
  3. octave_base_scale=4,
  4. ratios=[
  5. 0.5,
  6. 1.0,
  7. 2.0,
  8. ],
  9. scales_per_octave=3,
  10. strides=[
  11. 8,
  12. 16,
  13. 32,
  14. 64,
  15. 128,
  16. ],
  17. type='AnchorGenerator'),
  18. bbox_coder=dict(
  19. target_means=[
  20. 0.0,
  21. 0.0,
  22. 0.0,
  23. 0.0,
  24. ],
  25. target_stds=[
  26. 1.0,
  27. 1.0,
  28. 1.0,
  29. 1.0,
  30. ],
  31. type='DeltaXYWHBBoxCoder'),
  32. #中间特征图通道数
  33. feat_channels=256,
  34. #FPN层输出特征图通道数
  35. in_channels=256,
  36. loss_bbox=dict(loss_weight=1.0, type='L1Loss'),
  37. loss_cls=dict(
  38. alpha=0.25,
  39. gamma=2.0,
  40. loss_weight=1.0,
  41. type='FocalLoss',
  42. use_sigmoid=True),
  43. #自己数据集类别个数
  44. num_classes=4,
  45. #每个分支堆叠4层卷积
  46. stacked_convs=4,
  47. type='RetinaHead'),
                                                           17.head配置文件 

 2.3.1  head网络构建

以下代码位置mmdet/models/dense_heads/retina_head.py的64行左右。以下代码结合图2 和debug调试即可读懂。

  1. def _init_layers(self):
  2. """Initialize layers of the head."""
  3. self.relu = nn.ReLU(inplace=True)
  4. self.cls_convs = nn.ModuleList()
  5. self.reg_convs = nn.ModuleList()
  6. in_channels = self.in_channels
  7. for i in range(self.stacked_convs):
  8. self.cls_convs.append(
  9. ConvModule(
  10. in_channels,
  11. self.feat_channels,
  12. 3,
  13. stride=1,
  14. padding=1,
  15. conv_cfg=self.conv_cfg,
  16. norm_cfg=self.norm_cfg))
  17. self.reg_convs.append(
  18. ConvModule(
  19. in_channels,
  20. self.feat_channels,
  21. 3,
  22. stride=1,
  23. padding=1,
  24. conv_cfg=self.conv_cfg,
  25. norm_cfg=self.norm_cfg))
  26. in_channels = self.feat_channels
  27. self.retina_cls = nn.Conv2d(
  28. in_channels,
  29. self.num_base_priors * self.cls_out_channels,
  30. 3,
  31. padding=1)
  32. reg_dim = self.bbox_coder.encode_size
  33. self.retina_reg = nn.Conv2d(
  34. in_channels, self.num_base_priors * reg_dim, 3, padding=1)
                                                                      18.head层构建代码 

 在下图的位置处调试,

                                                           19.head层构建代码调试点

 如图20可以看出,此时代码仅仅运行到64行,所以head内部的结构并没有堆叠的4个卷积快。

                                                           20.还未构建卷积块时head的网络结构

由图21可以看出,随着代码的一行又一行的运行,head的内部结构正在逐步增添新的子模块。

                                                          21.构建了分类和回归分支的head内部结构 

 由图22可以得知,此时的head内部已经是分类和回归分支上分别堆叠了4个卷积块。

                                                            22.构建完毕后的head内部结构

 2.3.2单张特征图的forward流程

以下代码的位置:mmdet/models/dense_heads/retina_head.py的112行左右。

  1. #x是p3-p7中的某个特征图
  2. cls_feat = x
  3. reg_feat = x
  4. #4层不共享卷积参数
  5. for cls_conv in self.cls_convs:
  6. cls_feat = cls_conv(cls_feat)
  7. for reg_conv in self.reg_convs:
  8. reg_feat = reg_conv(reg_feat)
  9. #输出特征图
  10. cls_score = self.retina_cls(cls_feat)
  11. bbox_pred = self.retina_reg(reg_feat)
  12. return cls_score, bbox_pred
                                                                    23.单张图forward代码

 debug后的得分矩阵如绿色箭头所示。

                                                                         24.单张图forward调试

2.4BBox Assigner

2.4.1AnchorGeneratior

RetinaNet属于Anchor-based算法,在运行bbox属性分配前需要得到每个输出特征图位置的anchor列表,故在分析BBox Assigner前,需要先详细说明下anchor生成过程,其配置文件如下:

  1. anchor_generator=dict(
  2. #特征图anchor的base scale,值越大,所有的anchor的尺度会越大
  3. octave_base_scale=4,
  4. #每个特征图有3个高宽比例
  5. ratios=[
  6. 0.5,
  7. 1.0,
  8. 2.0,
  9. ],
  10. #每个特征图有3个尺度,octave_base_scaleXstridesX(2**0, 2**(1/3), 2**(2/3))
  11. scales_per_octave=3,
  12. #特征图对应的stride,必须与特征图stride一致,不可以随意更改。
  13. strides=[
  14. 8,
  15. 16,
  16. 32,
  17. 64,
  18. 128,
  19. ],
  20. type='AnchorGenerator'),
                                                                25.anchor Generator配置 

从上面配置可以看出:RetinaNet一共5个输出特征图,每个特征图上有3种尺度和3种宽高比,每个位置(每个像素)一共9个anchor,并且通过octave_base_scale参数来控制全局anchor的base scales,如果自定义数据集中普遍都是大物体或者小物体,则可以修改octave_base_scale参数。

(1)先对单个位置(0,0)生成base anchors

以下代码所在位置:mmdet/models/task_modules/prior_generators/anchor_generator.py的715行左右。

  1. w = base_size
  2. h = base_size
  3. #检查center变量是否为None,是则计算中心点,否则采用所传中心点
  4. if center is None:
  5. x_center = self.center_offset * (w - 1)
  6. y_center = self.center_offset * (h - 1)
  7. else:
  8. x_center, y_center = center
  9. #计算特征图相对于原图的高宽比例,可以理解为特征图的缩小倍数
  10. h_ratios = torch.sqrt(ratios)
  11. w_ratios = 1 / h_ratios
  12. #base_size 乘上宽高比例乘上尺度,就可以得到 n 个 anchor 的原图尺度wh值
  13. if self.scale_major:
  14. ws = (w * w_ratios[:, None] * scales[None, :]).view(-1)
  15. hs = (h * h_ratios[:, None] * scales[None, :]).view(-1)
  16. else:
  17. ws = (w * scales[:, None] * w_ratios[None, :]).view(-1)
  18. hs = (h * scales[:, None] * h_ratios[None, :]).view(-1)
  19. # use float anchor and the anchor's center is aligned with the
  20. # pixel center
  21. # 得到 x1y1(左上角坐标)x2y2(右下角坐标) 格式的 base_anchor 坐标值
  22. base_anchors = [
  23. x_center - 0.5 * (ws - 1), y_center - 0.5 * (hs - 1),
  24. x_center + 0.5 * (ws - 1), y_center + 0.5 * (hs - 1)
  25. ]
  26. base_anchors = torch.stack(base_anchors, dim=-1).round()
                                                               26.base_anchor生成代码 
 (2)利用输入特征图尺寸加上base anchors,得到每个特征图位置的对于原图的anchors

以下代码位置:mmdet/models/task_modules/prior_generators/anchor_generator.py的398行左右。

  1. #从特征图的大小中获取高度和宽度。
  2. feat_h, feat_w = featmap_size
  3. #根据特征图的宽度和高度以及步长计算出在原图上的x轴和y轴方向上的偏移量
  4. shift_x = torch.arange(0, feat_w, device=device) * stride[0]
  5. shift_y = torch.arange(0, feat_h, device=device) * stride[1]
  6. #使用_meshgrid方法生成一个网格,该网格的每个点的坐标都是其在x轴和y轴方向上的偏移量。
  7. shift_xx, shift_yy = self._meshgrid(shift_x, shift_y)
  8. #将x轴和y轴方向上的偏移量堆叠在一起,形成一个形状为(K, 1, 4)的张量,其中K是锚点的数量。
  9. shifts = torch.stack([shift_xx, shift_yy, shift_xx, shift_yy], dim=-1)
  10. #将偏移量张量的类型设置为与基础锚点相同。
  11. shifts = shifts.type_as(base_anchors)
  12. # first feat_w elements correspond to the first row of shifts
  13. # add A anchors (1, A, 4) to K shifts (K, 1, 4) to get
  14. # shifted anchors (K, A, 4), reshape to (K*A, 4)
  15. #将基础锚点和偏移量相加,得到所有可能的锚点。
  16. all_anchors = base_anchors[None, :, :] + shifts[:, None, :]
  17. #将所有可能的锚点展平成一维张量
  18. all_anchors = all_anchors.view(-1, 4)
  19. # first A rows correspond to A anchors of (0, 0) in feature map,
  20. # then (0, 1), (0, 2), ...
  21. return all_anchors
                                                                          27.映射到原图上的anchor 

 简单来说就是:假设一共m个输出特征图

  • 遍历m个输出特征图,在每个特征图的(0,0)或者说原图的(0,0)坐标位置生成base_anchors,注意base_anchors不是特征图尺寸,而是原图尺寸。
  • 遍历m个输出特征图中每个特征图上每个坐标点,将其映射到原图坐标上
  • 原图坐标加上base_anchors,就可以得到特征图每个位置的对应到原图尺度的anchor列表,anchor列表长度为m。

2.4.2 BBox Assigner

计算得到输出特征图上面每个点对应原图anchor坐标后,就可以和gt信息计算每个anchor的正负样本属性,对应配置如下

  1. assigner=dict(
  2. #忽略bboxes的阈值,-1表示不忽略
  3. ignore_iof_thr=-1,
  4. #正样本阈值下限
  5. min_pos_iou=0,
  6. #负样本阈值
  7. neg_iou_thr=0.4,
  8. #正样本阈值
  9. pos_iou_thr=0.5,
  10. #最大iou原则分配器
  11. type='MaxIoUAssigner'),
                                                              28.bbox assigner配置 
 (1)初始化所有anchor为忽略样本

假设所有输出特征的所有anchor总数一共n个,对应某张图片中gt bbox个数为m,首先初始化长度为n的assigned_gt_inds,全部赋值为-1,表示当前全部设置为忽略样本。

以下代码位置:mmdet/models/task_modules/assigners/max_iou_assigner.py的257行左右。

  1. # 1. assign -1 by default
  2. assigned_gt_inds = overlaps.new_full((num_bboxes, ),
  3. -1,
  4. dtype=torch.long)
                                                                 29.初始anchor索引代码

由下图可以看出,该张图片的所有特征图的anchor一共有193374个,gt一共有两个。

                                                                 30.debug图片anchor数量

由下图可以看出已经将该张图片的193374个anchor的对应索引全部赋值为-1。

                                                                 31.初始anchor索引值为-1
(2)计算背景样本

将每个anchor与所有gt bbox计算iou,找到最大iou,如果该iou小于neg_iou_thr或者在背景样本阈值范围内,则该anchor对应索引位置的assigned_gt_inds设置为0,表示负样本(背景样本)

以下代码位置:mmdet/models/task_modules/assigners/max_iou_assigner.py的278行左右。

  1. # for each anchor, which gt best overlaps with it
  2. # for each anchor, the max iou of all gts
  3. max_overlaps, argmax_overlaps = overlaps.max(dim=0)
  4. # for each gt, which anchor best overlaps with it
  5. # for each gt, the max iou of all proposals
  6. gt_max_overlaps, gt_argmax_overlaps = overlaps.max(dim=1)
  7. # 2. assign negative: below
  8. # the negative inds are set to be 0
  9. if isinstance(self.neg_iou_thr, float):
  10. assigned_gt_inds[(max_overlaps >= 0)
  11. & (max_overlaps < self.neg_iou_thr)] = 0
  12. elif isinstance(self.neg_iou_thr, tuple):
  13. assert len(self.neg_iou_thr) == 2
  14. #可以设置一个范围
  15. assigned_gt_inds[(max_overlaps >= self.neg_iou_thr[0])
  16. & (max_overlaps < self.neg_iou_thr[1])] = 0
                                                         32.计算背景样本代码实现
(3)计算高质量正样本

将每个anchor和所有的gt bbox计算iou,找出最大iou,如果当前iou大于等于pos_iou_thr,则设置该anchor对应所有的assigner_gt_inds设置为当前匹配gt bbox的编号+1(后面会减掉1),表示该anchor负责预测该gt bbox,且是高质量anchor,之所以要加一,是为了区分背景样本(背景样本的assigned_gt_inds值为0),这里建议看b站霹雳巴拉博主讲的faster rcnn的那一块。

以下代码位置:mmdet/models/task_modules/assigners/max_iou_assigner.py的294行左右。

  1. pos_inds = max_overlaps >= self.pos_iou_thr
  2. assigned_gt_inds[pos_inds] = argmax_overlaps[pos_inds] + 1
(4)适当增加更多正样本 

在第三步计算高质量正样本中可能会出现某些 gt bbox 没有分配给任何一个 anchor (由于 iou 低于 pos_iou_thr),导致该 gt bbox 不被认为是前景物体,此时可以通过 self.match_low_quality=True 配置进行补充正样本。

对于每个 gt bbox 需要找出和其最大 iou 的 anchor 索引,如果其 iou 大于 min_pos_iou,则将该 anchor 对应索引的 assigned_gt_inds 设置为正样本,表示该 anchor 负责预测对应的 gt bbox。通过本步骤,可以最大程度保证每个 gt bbox 都有相应的 anchor 负责预测,但是如果其最大 iou 值还是小于 min_pos_iou,则依然不被认为是前景物体。

代码位置:mmdet/models/task_modules/assigners/max_iou_assigner.py的297行。

  1. if self.match_low_quality:
  2. # Low-quality matching will overwrite the assigned_gt_inds assigned
  3. # in Step 3. Thus, the assigned gt might not be the best one for
  4. # prediction.
  5. # For example, if bbox A has 0.9 and 0.8 iou with GT bbox 1 & 2,
  6. # bbox 1 will be assigned as the best target for bbox A in step 3.
  7. # However, if GT bbox 2's gt_argmax_overlaps = A, bbox A's
  8. # assigned_gt_inds will be overwritten to be bbox 2.
  9. # This might be the reason that it is not used in ROI Heads.
  10. for i in range(num_gts):
  11. if gt_max_overlaps[i] >= self.min_pos_iou:
  12. if self.gt_max_assign_all:
  13. max_iou_inds = overlaps[i, :] == gt_max_overlaps[i]
  14. assigned_gt_inds[max_iou_inds] = i + 1
  15. else:
  16. assigned_gt_inds[gt_argmax_overlaps[i]] = i + 1

 

此时可以可以得到如下总结:

  • 如果 anchor 和所有 gt bbox 的最大 iou 值小于 0.4,那么该 anchor 就是背景样本
  • 如果 anchor 和所有 gt bbox 的最大 iou 值大于等于 0.5,那么该 anchor 就是高质量正样本
  • 如果 gt bbox 和所有 anchor 的最大 iou 值大于等于 0(可以看出每个 gt bbox 都一定有至少一个 anchor 匹配),那么该 gt bbox 所对应的 anchor 也是正样本
  • 其余样本全部为忽略样本即 anchor 和所有 gt bbox 的最大 iou 值处于 [0.4,0.5) 区间的 anchor 为忽略样本,不计算 loss

2.5BBox Encoder Decoder

在anchor-based算法中,为了利用anchor信息进行更快更好的收敛,一般会对head输出的 bbox分支4个值进行编解码操作,作用有两个:

1.更好的平衡分类和回归loss,以及平衡bbox四个预测值的loss

2.训练过程中引入anchor信息,加快收敛。

RetinaNet采用的编解码函数是主流的DeltaXYWHBBoxCoder,其配置如下:

  1. bbox_coder=dict(
  2. target_means=[
  3. 0.0,
  4. 0.0,
  5. 0.0,
  6. 0.0,
  7. ],
  8. target_stds=[
  9. 1.0,
  10. 1.0,
  11. 1.0,
  12. 1.0,
  13. ],
  14. type='DeltaXYWHBBoxCoder'),

编解码具体步骤和公式参考官网轻松掌握 MMDetection 中常用算法(一):RetinaNet 及配置详解 - 知乎 (zhihu.com)

这段设计的代码位置:mmdet/models/task_modules/coders/delta_xywh_bbox_coder.py

2.6Loss

依然是参考原文。同时推荐去b站噼里啪啦博主的yolov3第三小节的讲解处观看理解。

2.7测试流程

对应的配置如下所示。更多调试过程和faster-rcnn差不多。mmdetection3.2.0之faster-rcnn源码解读-CSDN博客

  1. test_cfg=dict(
  2. #最终输出的每张图片最多bbox个数
  3. max_per_img=100,
  4. #过滤掉最小的bbpx尺寸
  5. min_bbox_size=0,
  6. #nms方法和nms阈值
  7. nms=dict(iou_threshold=0.5, type='nms'),
  8. #nms前每个输出层最多保留1K个proposal
  9. nms_pre=1000,
  10. #分值阈值
  11. score_thr=0.05),

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/848127
推荐阅读
相关标签
  

闽ICP备14008679号