SSD源码解析

SSD网络的搭建

借鉴的代码使用的是resnet50作为backbone,具体网络结构如下所示,舍弃conv_5x结构及其之后的网络,并且改变conv_4中第一个block的卷积核的stride,从2->1

文件目录中res50_backbone.py是从resnet50网络中直接粘过来的结构

对照resnet50结构看清后,可以看到只取前7个模块,具体如下所示:

因此在backbone中,用net.children()取前7个,同时系应该conv_4中block的步距

至此,前4个卷积的结构搭建好了。

之后构建后5个网络添加结构,调用_bulid_additional_features函数

def _build_additional_features(self, input_size):
    """
    为backbone(resnet50)添加额外的一系列卷积层,得到相应的一系列特征提取器
    :param input_size:
    :return:
    """
    additional_blocks = []
    # input_size = [1024, 512, 512, 256, 256, 256] for resnet50
    #对应从Feature Map2开始每一个模块中间Conv2d的channels
    middle_channels = [256, 256, 128, 128, 128]
    #切片左闭右开,序号从0开始,input_ch代表从0开始到倒数第二个,output_ch代表从1开始到最后一个
    for i, (input_ch, output_ch, middle_ch) in enumerate(zip(input_size[:-1], input_size[1:], middle_channels)):
        #5个结构都是相同的,唯一不同的是在第二个卷积的stride和padding不一样,所以进行判断,前三个相同,后两个相同
        padding, stride = (1, 2) if i < 3 else (0, 1)
        layer = nn.Sequential(
            nn.Conv2d(input_ch, middle_ch, kernel_size=1, bias=False),
            nn.BatchNorm2d(middle_ch),
            nn.ReLU(inplace=True),
            #第二个卷积层输入channels等于第一个输出的channels即middle_ch
            nn.Conv2d(middle_ch, output_ch, kernel_size=3, padding=padding, stride=stride, bias=False),
            nn.BatchNorm2d(output_ch),
            nn.ReLU(inplace=True),
        )
        additional_blocks.append(layer)
    self.additional_blocks = nn.ModuleList(additional_blocks)

网络搭建完成,开始预测器的构建

看看它的forward函数

detection_features:存储预测feature maps的列表

用bbox_view方法得到loc参数和conf参数,

在训练模式下,需要计算损失

def forward(self, image, targets=None):
    #图片数据放入feature_extractor中生产特征矩阵,到conv4_x输出为38*38*1024
    x = self.feature_extractor(image)

    # Feature Map 38x38x1024, 19x19x512, 10x10x512, 5x5x256, 3x3x256, 1x1x256
    #detection_features:存储预测特征面的列表
    detection_features = torch.jit.annotate(List[Tensor], [])  # [x]
    detection_features.append(x)
    for layer in self.additional_blocks:
        #将上一层的输出输入到当前layer当中,得到这一层输出
        x = layer(x)
        detection_features.append(x)

    # Feature Map 38x38x4, 19x19x6, 10x10x6, 5x5x6, 3x3x4, 1x1x4
    #bbox_view方法得到loc参数和conf参数
    locs, confs = self.bbox_view(detection_features, self.loc, self.conf)

    # For SSD 300, shall return nbatch x 8732 x {nlabels, nlocs} results
    # 38x38x4 + 19x19x6 + 10x10x6 + 5x5x6 + 3x3x4 + 1x1x4 = 8732
    #训练模式下,进一步计算预测参数的损失
    if self.training:
        if targets is None:
            raise ValueError("In training mode, targets should be passed")
        # bboxes_out (Tensor 8732 x 4), labels_out (Tensor 8732)
        bboxes_out = targets['boxes']
        bboxes_out = bboxes_out.transpose(1, 2).contiguous()
        # print(bboxes_out.is_contiguous())
        labels_out = targets['labels']
        # print(labels_out.is_contiguous())

        # ploc, plabel, gloc, glabel
        loss = self.compute_loss(locs, confs, bboxes_out, labels_out)
        return {"total_losses": loss}

    # 非训练模式下,做后处理,将预测回归参数叠加到default box上得到最终预测box,并执行非极大值抑制虑除重叠框
    # results = self.encoder.decode_batch(locs, confs)
    results = self.postprocess(locs, confs)
    return results

bbox_view方法:

default box

utils.py中有defaultBox类,实际代码中的初始化和论文中优点差异。

定义好参数后,开始设置default box的scale和aspect并遍历计算

考虑后续匹配正样本,为其提供ltrb格式

用SSD网络结构中的参数实例化default box类、

 loss的计算

确定了default box后,就开始计算损失函数,

在ssd_model.py中有LOSS类

看下forward函数

_location_vec()函数如下:

得到相对于GT的回归参数后开始计算location_loss(只有正样本才需要计算损失,所以后续要把负样本的损失去掉)

再计算分类损失

之后采用论文中的方法获取负样本,具体代码编写的方法是英伟达在实现过程中编写的代码方法

看的不是很懂,但举例图示如下所示:

总的conf_loss就是将正负样本的加起来

最后得到的ret就是最后的总的损失

后处理算法

如果不是训练模式,会对我们的输出后处理

PostProcess类在utils.py中

forward中传入的参数:bboxes_in是预测的回归参数,scores_in是预测的类别参数

还有部分没看完,现在太忙了,后期再更。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值