mmdetection中的backbone为了添加FPN进行多尺度的检测,backbone的输入是4个stage的feature map的tensor,详见resnet.py源码中的Example:
>>> from mmdet.models import ResNet
>>> import torch
>>> self = ResNet(depth=18)
>>> self.eval()
>>> inputs = torch.rand(1, 3, 32, 32)
>>> level_outputs = self.forward(inputs)
>>> for level_out in level_outputs:
... print(tuple(level_out.shape))
(1, 64, 8, 8)
(1, 128, 4, 4)
(1, 256, 2, 2)
(1, 512, 1, 1)
Backbone(以resnet.py为例)中的forward函数return的是一个tuple,并且包含四个不同stage的feature map:
class ResNet(nn.Module):
........
........
........
........
def forward(self, x):
x = self.conv1(x)
x = self.norm1(x)
x = self.relu(x)
x = self.maxpool(x)
outs = []
for i, layer_name in enumerate(self.res_layers):
res_layer = getattr(self, layer_name)
x = res_layer(x)
if i in self.out_indices:
outs.append(x)
return tuple(outs)
所以在测试的过程中就很好的就行特征图可视化的操作。
首先在mmdet/models/detectors/two_stage.py中找到test的函数:
class TwoStageDetector(BaseDetector, RPNTestMixin, BBoxTestMixin,
MaskTestMixin):
........
........
........
........
def extract_feat(self, img):
"""Directly extract features from the backbone+neck
"""
x = self.backbone(img)
# 此时在这里输出的就是backbone的4个stage的feature map
if self.with_neck: # 如果有neck,比如FPN,最终的输出就是4个stage经过FPN处理后的feature map
x = self.neck(x)
return x
........
........
........
........
def simple_test(self, img, img_meta, proposals=None, rescale=False):
"""Test without augmentation."""
assert self.with_bbox, "Bbox head must be implemented."
x = self.extract_feat(img)
# print(x[0].shape, len(x))
if proposals is None:
proposal_list = self.simple_test_rpn(x, img_meta,
self.test_cfg.rpn)
else:
proposal_list = proposals
det_bboxes, det_labels = self.simple_test_bboxes(
x, img_meta, proposal_list, self.test_cfg.rcnn, rescale=rescale)
bbox_results = bbox2result(det_bboxes, det_labels,
self.bbox_head.num_classes)
if not self.with_mask:
return bbox_results # modified origin : return bbox_results
# return bbox_results
else:
segm_results = self.simple_test_mask(
x, img_meta, det_bboxes, det_labels, rescale=rescale)
return bbox_results, segm_results
所以如果要将feature map输出,就要更改这两个函数的返回值,首先:
def extract_feat(self, img):
"""Directly extract features from the backbone+neck
"""
x = self.backbone(img)
if self.with_neck:
x = self.neck(x)
return x
# change the code to
def extract_feat(self, img):
"""Directly extract features from the backbone+neck
"""
x = self.backbone(img)
if self.with_neck:
_x = self.neck(x)
return x, _x
# 也可以在x = self.backbone(img)下面一行将这个元组中的tensor转变为numpy形式进行保存,
# 在通过opencv, PIL进行图像可视化
不让原来backbone中输出的feature map的变量被覆盖掉,所以可以使用不同的变量名进行赋值操作。
原来的simple_test函数是只返回图片中检测到物体的坐标,这里我们稍微做一下修改:
def simple_test(self, img, img_meta, proposals=None, rescale=False):
"""Test without augmentation."""
assert self.with_bbox, "Bbox head must be implemented."
# origin code : x = self.extract_feat(img)
x, _x = self.extract_feat(img)
'''
x : backbone中四个stage的feature map的tensor
_x : 经过FPN后4个stage的feature map的tensor
'''
# print(x[0].shape, len(x))
if proposals is None:
proposal_list = self.simple_test_rpn(x, img_meta,
self.test_cfg.rpn)
else:
proposal_list = proposals
det_bboxes, det_labels = self.simple_test_bboxes(
x, img_meta, proposal_list, self.test_cfg.rcnn, rescale=rescale)
bbox_results = bbox2result(det_bboxes, det_labels,
self.bbox_head.num_classes)
if not self.with_mask:
'''
在这里加上x, _x的返回值就行!!!!
'''
return bbox_results, x # modified origin : return bbox_results
# return bbox_results
else:
segm_results = self.simple_test_mask(
x, img_meta, det_bboxes, det_labels, rescale=rescale)
return bbox_results, segm_results