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 mapif self.with_neck: # 如果有neck,比如FPN,最终的输出就是4个stage经过FPN处理后的feature mapx = 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 = proposalsdet_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_resultselse: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 todef 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 = proposalsdet_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_resultselse:segm_results = self.simple_test_mask(x, img_meta, det_bboxes, det_labels, rescale=rescale)return bbox_results, segm_results
