1. 总领过程--官方faster cnnn 调用过程
import torchvision, torch
# 导入官方faster rcnn 模型
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=False)
# For training
# 生成随机训练数据,用于测试模型跑通
boxes = torch.randint(0, 300, (4, 11, 4))
# right bottom 的值一定比left top 大
boxes[:, :, 2:4:1] = torch.randint(400, 600, (4, 11, 2))
images = torch.rand(4, 3, 600, 1200)
# 生成随机标签
# 此处是数据格式的处理,官方fast rcnn 输入数据只支持images-->list与 targets --> dict
# 后面会细讲
# 关于labele 标签,任何用户定义的标签都不能出现0,官方代码都已经设定label=0为background
labels = torch.randint(1, 91, (4, 11))
images = list(image for image in images)
targets = []
for i in range(len(images)):
d = {}
d['boxes'] = boxes[i]
d['labels'] = labels[i]
targets.append(d)
# 相当于batch_size = 4 的训练
output = model(images, targets)
# For inference
model.eval()
x = [torch.rand(3, 300, 400), torch.rand(3, 500, 400)]
predictions = model(x)
# 模型 onnx 保存 可用于前向网络 tensort
# optionally, if you want to export the model to ONNX:
torch.onnx.export(model, x, "faster_rcnn.onnx", opset_version=11)
2. 先谈一下faster rcnn 的原理白裳:一文读懂Faster RCNNzhuanlan.zhihu.com
总结而言faster rcnn 的原理概括为如下几个重点:1. faster rcnn 原理图backbone 网络整个图像特征提取,如上图所示 conv layers,pytorch 官方 backbone 采用FPN + reset50网络构建,前端将提取5层特征,代码部分细讲
将整张图片的特征做输入rpn网络,做框的分类(二分类实物或者background)和box回归,rpn:在faster rcnn 整个理论中用于产生候选框,官网代码中rpn网络主要由 rpn_head(用于分类和回归) anchor_generator(用于训练时基础框产生) 代码部分细讲
关键是rpn在训练过程中的 ground truth 如何产生,不同目标检测此处处理方式不同, faster rcnn 采用的是动态生成anchors,将anchors 与 实际框和labels做IOU、nms,动态生成训练的label 和boxes
rpn网络将产生proposals框,根据proposals框的大小,选取不同FPN输出层的特征作为特征层
roi pooling 操作后得到的特征将是固定尺度的特征
再接全连接层做实物分类和进一步框回归,同样训练过程中的ground truth也是由 proposals 和 anchor generator作用产生的
3. 结合faster rcnn 原理看faster rcnn 代码
定义一个模型:
model = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=False)
官方faster rcnn 默认采用的是resnet50网络作为backbone图像特征提取, ctrl + 双击 fasterrcnn_resnet50_fpn 查看fasterrcnn_resnet50_fpn源码
def fasterrcnn_resnet50_fpn(pretrained=False, progress=True,
num_classes=91, pretrained_backbone=True, trainable_backbone_layers=3, **kwargs):
assert trainable_backbone_layers <= 5 and trainable_backbone_layers >= 0
# dont freeze any layers if pretrained model or backbone is not used
if not (pretrained or pretrained_backbone):
# 没有pretrained model 或者是没有pretrained backbone时前端网络前5层均需要训练
trainable_backbone_layers = 5
if pretrained:
# no need to download the backbone if pretrained is set
pretrained_backbone = False
# 定义前端网络 (1)
backbone = resnet_fpn_backbone('resnet50', pretrained_backbone, trainable_layers=trainable_backbone_layers)
# 定义fast rcnn
model = FasterRCNN(backbone, num_classes, **kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls['fasterrcnn_resnet50_fpn_coco'],
progress=progress)
model.load_state_dict(state_dict)
return model
结合原理看下backbone的生成代码
1)首先简易说明FPN原理:2. FPN网络结构图3. FPN全过程
FPN:分层特征提取器
包括:下采样、横向连接、上采样
下采样过程与一般特征提取器无差异,以conv2 ~ conv5为例作为FPN的输入层:
conv5 -> 1X1卷积->M5->P5
conv4 -> 1X1卷积 + M5 上采样(此处默认stride=2) -> M4 -> P4
以次类推,最好FPN输出P5~P2的分层特征,P5~P2特征维度相同,特征尺寸不同
FPN的意义:FPN不是第一个不同层特征融合的网络,但是第一个在目标检测使用多尺度特征融合的方法。CNN的设计中,网络的深度和down sample是一对矛盾体。网络较为浅,特征提取不充分。网络较深,可以提取较大感受野,随之down sample过大,小目标的检测性能显著降低,同时由于CNN的平移不变形,丢失位置信息。而对于卷积神经网络而言,不同深度对应着不同层次的语义特征,浅层网络分辨率高,学的更多是细节特征,深层网络分辨率低,学的更多是语义特征。对于faster rcnn网络而言,深层网络更适宜于做目标的分类,检测网络更适宜于做小目标的检测
FPN接入resnet网络
backbone需要进行特征金字塔的层包含 {'layer1': '0', 'layer2': '1', 'layer3': '2', 'layer4': '3'}
in_channels_list 接入FPN层的 in_channels_list
out_channels FPN 输出层的 channel,不对特征的宽高变化
def resnet_fpn_backbone(backbone_name, pretrained, norm_layer=misc_nn_ops.FrozenBatchNorm2d, trainable_layers=3):
# resnet 网络结构
backbone = resnet.__dict__[backbone_name](
pretrained=pretrained,
norm_layer=norm_layer)
# select layers that wont be frozen
assert trainable_layers <= 5 and trainable_layers >= 0
# 全部可训练层,从后向前选择需要训练的层
layers_to_train = ['layer4', 'layer3', 'layer2', 'layer1', 'conv1'][:trainable_layers]
# freeze layers only if pretrained backbone is used
for name, parameter in backbone.named_parameters():
if all([not name.startswith(layer) for layer in layers_to_train]):
parameter.requires_grad_(False)
return_layers = {'layer1': '0', 'layer2': '1', 'layer3': '2', 'layer4': '3'}
in_channels_stage2 = backbone.inplanes // 8
in_channels_list = [
in_channels_stage2,
in_channels_stage2 * 2,
in_channels_stage2 * 4,
in_channels_stage2 * 8,
]
out_channels = 256
# 利用FPN将resnet网络接入,此处也可采用vgg,或者其他自定义网络形式
return BackboneWithFPN(backbone, return_layers, in_channels_list, out_channels)
看BackboneWithFPN的源码,注: return_layers, in_channels_list, out_channels 三者的维度关系对应做好
class BackboneWithFPN(nn.Module):
def __init__(self, backbone, return_layers, in_channels_list, out_channels):
super(BackboneWithFPN, self).__init__()
# 根据需返回层的名字return_layers返回特征
self.body = Interme