Faster RCNN是特别经典的二阶网络;FPN能有效融合高低级特征,更是公认的有利于小目标的检测(分割)。
原始的Faster RCNN中是不带FPN结构的,但torchvision中的Faster RCNN是自带FPN的,因此可以从torchvision中的源码去学习,从而仿造出自己的带fpn的Faster RCNN。

参考:
https://zhuanlan.zhihu.com/p/145842317

首先需要注意的是Faster RCNN目前代表的是一种思想,并非严格按照论文的结构和设计才有作用,而是如果你的网络中有一个网络来提供候选区域(RPN),另外一个网络进一步矫正这个候选区域的话(ROI Net),就可以称之为“Faster RCNN”。因此我们的FPN添加过程也是跟我们的数据集相关的,譬如你的数据集都是小目标,那你的FPN使用了1/64的下采样又有什么意义呢?

jittor框架Faster RCNN添加FPN模块 - 图1

首先看到加不加FPN的区别在于网络(RPN和ROI Net)的输入是单一特征图还是多个层级的特征图,以主干网络为ResNet50为例,原始的Faster RCNN输入的是[1/32 feature map],加了FPN后输入为列表[1/4 feature map,1/8 feature map,1/16 feature map,1/32 feature map],在输入时对该列表进行遍历,即可得到不尺度的输入,当然不同尺度上都会生成anchor。

首先是主干网络ResNet和FPN的结合:

  1. # 这里重点看下return p2,p3,p4 可以看到返回哪些特征图完全是自己决定的,包括你想继续做些文章,譬如在后面接软注意机制,更改可变形卷积,使用更多尺度的特征图,都是完完全全可以更改的。
  2. class FPN(nn.Module):
  3. def __init__(self, block, layers):
  4. super(FPN, self).__init__()
  5. self.in_planes = 64
  6. self.conv1 = nn.Conv(3, 64, 7, stride=2, padding=3, bias=False)
  7. self.bn1 = nn.BatchNorm(64)
  8. self.layer1 = self._make_layer(block, 64, layers[0])
  9. self.layer2 = self._make_layer(block, 128, layers[1], stride=2)
  10. self.layer3 = self._make_layer(block, 256, layers[2], stride=2)
  11. # self.layer4 = self._make_layer(block, 512, layers[3], stride=2)
  12. self.toplayer = nn.Conv(1024, 256, 1, stride=1, padding=0)
  13. self.smooth1 = nn.Conv(256, 256, 3, stride=1, padding=1)
  14. self.smooth2 = nn.Conv(256, 256, 3, stride=1, padding=1)
  15. self.smooth3 = nn.Conv(256, 256, 3, stride=1, padding=1)
  16. # self.latlayer1 = nn.Conv(1024, 256, 1, stride=1, padding=0)
  17. self.latlayer2 = nn.Conv(512, 256, 1, stride=1, padding=0)
  18. self.latlayer3 = nn.Conv(256, 256, 1, stride=1, padding=0)
  19. def _make_layer(self, block, planes, blocks, stride=1):
  20. downsample = None
  21. if (stride != 1) or (self.in_planes != (planes * block.expansion)):
  22. downsample = nn.Sequential(nn.Conv(self.in_planes, (planes * block.expansion), 1, stride=stride, bias=False), nn.BatchNorm((planes * block.expansion)))
  23. layers = []
  24. layers.append(block(self.in_planes, planes, stride, downsample))
  25. self.in_planes = (planes * block.expansion)
  26. for i in range(1, blocks):
  27. layers.append(block(self.in_planes, planes))
  28. return nn.Sequential(*layers)
  29. def _upsample_add(self, x, y):
  30. (_, _, H, W) = y.shape
  31. return nn.interpolate(x, size=(H, W), mode='bilinear', align_corners=True) + y
  32. def execute(self, x):
  33. c1 = nn.relu(self.bn1(self.conv1(x)))
  34. c1 = nn.max_pool2d(c1, kernel_size=3, stride=2, padding=1)
  35. c2 = self.layer1(c1)
  36. c3 = self.layer2(c2)
  37. c4 = self.layer3(c3)
  38. p4 = self.toplayer(c4)
  39. # c5 = self.layer4(c4)
  40. # p5 = self.toplayer(c5)
  41. # p4 = self._upsample_add(p5, self.latlayer1(c4))
  42. p3 = self._upsample_add(p4, self.latlayer2(c3))
  43. p2 = self._upsample_add(p3, self.latlayer3(c2))
  44. p4 = self.smooth1(p4)
  45. p3 = self.smooth2(p3)
  46. p2 = self.smooth3(p2)
  47. return p2, p3, p4
  48. def FPN_Resnet50(pretrained=False):
  49. model = FPN(Bottleneck, [3, 4, 6, 3])
  50. if pretrained: model.load("jittorhub://resnet50.pkl")
  51. return model

其次是RPN中FPN的使用:

  1. # todo:fpn in rpn
  2. # 不用管return回来的这些参数是什么,我们只需观察到(1)对传进去的特征图列表进行遍历;(2)对返回来的数据进行拼接
  3. class Faster_RCNN():
  4. ......
  5. def forward(self,features):
  6. rpn_locs_list,rpn_scores_list,rois_list,roi_indices_list,anchor_list = [],[],[],[],[]
  7. for i,feature in enumerate(features):
  8. rpn_locs, rpn_scores, rois, roi_indices, anchor = self.rpn(feature, img_size)
  9. rpn_locs_list.append(rpn_locs),rpn_scores_list.append(rpn_scores),rois_list.append(rois)
  10. roi_indices_list.append(roi_indices),anchor_list.append(anchor)
  11. rpn_locs = jt.concat(rpn_locs_list,dim=1)
  12. rpn_scores = jt.concat(rpn_scores_list,dim=1)
  13. rois = jt.concat(rois_list,dim=0)
  14. roi_indices = jt.concat(roi_indices_list,dim=0)
  15. anchor = jt.concat(anchor_list,dim=0)
  16. ......
  17. # todo:fpn in rpn
  18. # 可以看到fpn在RPN的内部影响的仅仅是anchor的scale,也就是不同层级(尺度)的特征图产生的anchor是不一样的。这也是为什么特征图越大(层级越低),对小目标识别效果越好的原因。
  19. def rpn(feature,img_size):
  20. ......
  21. x = feature
  22. n, _, hh, ww = x.shape
  23. feat_stride = img_size[0] // hh
  24. assert feat_stride in [4,8,16,32]
  25. scale_feat_dict = {4:32,8:64,16:128,32:256}
  26. anchor_scale = [scale_feat_dict[feat_stride]]
  27. anchor_base = generate_anchor_base(anchor_scales=anchor_scale, ratios=self.ratios)
  28. anchor = _enumerate_shifted_anchor(anchor_base, feat_stride, hh, ww)
  29. anchor = jt.array(anchor)
  30. ......