首页>>人工智能->死磕mmdet之YOLOv3

死磕mmdet之YOLOv3

时间:2023-11-29 本站 点击:1

theme: smartblue highlight: a11y-dark

死磕mmdetection之YOLOv3

前言

本文精读的是mmdetection v2.23.0版本的YOLOv3版本,我们选用config/yolo/yolov3_d53_320_273e_coco.py 作为分析对象,后续对backbone为mobilenet的YOLO进行分析,如果错误请多多指教。

整体网络

YOLOv3参数量及FLOAPs估计

MMDet把目标检测的过程类比为人体结构,分为 Backbone、Neck和Head三个过程。YOLOv3也包含Backbone、Neck和Head三个部分,它们的参数量和计算量的情况如下。MMDet把目标检测的过程类比为人体结构,分为 Backbone、Neck和Head三个过程。YOLOv3也包含Backbone、Neck和Head三个部分,它们的参数量和计算量的情况如下。

模块名称参数量参数量占比GFLOPsFLOAPs占比YOLOv3BackBone (DarkNet53)40.585 M65.966%145.64575.134%YOLOV3Neck14.71 M23.909%33.89417.485%YOLOV3Head6.229 M10.125%14.3077.380%整个网络61.524 M100.000%193.845100.000%

我们选用宽高为1280x800三个通道的图像作为参数量和FLOAPs计算的基础。

YOLOv3的网络图

图像预处理

不详细分析,从config/yolo/yolov3_d53_320_273e_coco.py 的train_pipeline可以看到,图片经过了MinIoURandomCrop、Resize、RandomFlip、PhotoMetricDistortion、Normalize、Pad等前处理操作。

_base_ = './yolov3_d53_mstrain-608_273e_coco.py'# dataset settingsimg_norm_cfg = dict(mean=[0, 0, 0], std=[255., 255., 255.], to_rgb=True)train_pipeline = [    dict(type='LoadImageFromFile'),    dict(type='LoadAnnotations', with_bbox=True),    dict(        type='Expand',        mean=img_norm_cfg['mean'],        to_rgb=img_norm_cfg['to_rgb'],        ratio_range=(1, 2)),    dict(        type='MinIoURandomCrop',        min_ious=(0.4, 0.5, 0.6, 0.7, 0.8, 0.9),        min_crop_size=0.3),    dict(type='Resize', img_scale=(320, 320), keep_ratio=True),    dict(type='RandomFlip', flip_ratio=0.5),    dict(type='PhotoMetricDistortion'),    dict(type='Normalize', **img_norm_cfg),    dict(type='Pad', size_divisor=32),    dict(type='DefaultFormatBundle'),    dict(type='Collect', keys=['img', 'gt_bboxes', 'gt_labels'])]test_pipeline = [    dict(type='LoadImageFromFile'),    dict(        type='MultiScaleFlipAug',        img_scale=(320, 320),        flip=False,        transforms=[            dict(type='Resize', keep_ratio=True),            dict(type='RandomFlip'),            dict(type='Normalize', **img_norm_cfg),            dict(type='Pad', size_divisor=32),            dict(type='ImageToTensor', keys=['img']),            dict(type='Collect', keys=['img'])        ])]data = dict(    train=dict(pipeline=train_pipeline),    val=dict(pipeline=test_pipeline),    test=dict(pipeline=test_pipeline))

骨干网络Darknet53

mmdet/models/backbones/darknet.py

DarkNet53中的参数量及计算量估计

模块名称参数量参数量占比GFLOPsFLOAPs占比conv10.001 M0.002%0.9830.507%conv_res_block10.039 M0.064%10.0845.202%conv_res_block20.239 M0.388%15.3037.894%conv_res_block32.923 M4.751%46.82124.154%conv_res_block411.679 M18.982%46.74224.113%conv_res_block525.704 M41.780%25.71213.264%YOLOv3BackBone (DarkNet53)40.585 M65.966%145.64575.134%

Darknet53网络图

Darknet53借鉴了resnet残差模块的思想,YOLOv3网络选择conv_res_block3、conv_res_block4、conv_res_block5三个的尺度的输出,作为后续YOLOV3NECK的输入。

configs中的Darknet

configs/yolo/yolov3_d53_mstrain-608_273e_coco.py中关于Darknet的描述如下,out_indices=(3, 4, 5),所以选择3、4、5层的conv_res_block的输出作为骨干网络的输出。

backbone=dict(        type='Darknet',        depth=53,        out_indices=(3, 4, 5),        init_cfg=dict(type='Pretrained', checkpoint='open-mmlab://darknet53')),

mmdet中Darknet类

mmdetection/mmdet/models/backbones/darknet.py

Con_res_block

DarkNet53中有5个con_res_block,由一个stride为2的卷积 和 重复的ResBlock组成

在Darknet中,卷积层后面通常跟着一个ResBlock,以下的make_conv_res_block函数将实现这个功能,卷积3x3的卷积且stride为2,卷积层的个数等于ResBlock的输入和输出的通道。

/mmdet/models/backbones/darknet.py#L180 中创建conv_res_block的代码如下。

        @staticmethod    def make_conv_res_block(in_channels,                            out_channels,                            res_repeat,                            conv_cfg=None,                            norm_cfg=dict(type='BN', requires_grad=True),                            act_cfg=dict(type='LeakyReLU',                                         negative_slope=0.1)):        """In Darknet backbone, ConvLayer is usually followed by ResBlock. This        function will make that. The Conv layers always have 3x3 filters with        stride=2. The number of the filters in Conv layer is the same as the        out channels of the ResBlock.        Args:            in_channels (int): The number of input channels.            out_channels (int): The number of output channels.            res_repeat (int): The number of ResBlocks.            conv_cfg (dict): Config dict for convolution layer. Default: None.            norm_cfg (dict): Dictionary to construct and config norm layer.                Default: dict(type='BN', requires_grad=True)            act_cfg (dict): Config dict for activation layer.                Default: dict(type='LeakyReLU', negative_slope=0.1).        """        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)        model = nn.Sequential()                # 以下为conv_res_block中的conv的创建代码        model.add_module(            'conv',            ConvModule(                in_channels, out_channels, 3, stride=2, padding=1, **cfg))                # 以下为ResBlock的创建代码        for idx in range(res_repeat):            model.add_module('res{}'.format(idx),                             ResBlock(out_channels, **cfg))        return model

ResBlock

ResBlock图例

DarkNet53的ResBlock如下,先经过一个1x1卷积,然后再经过3x3的卷积然后相加,经过此ResBlock,输入输出的通道数和图片的宽高都不变。

Netron可视化残差结构 f(x) = x + g(x)

ResBlock代码

ResBlock的代码在/mmdet/models/backbones/darknet.py#L14中,每个ResBlock包含两个卷积模块,输入和输出相加作为最终的输出,每个卷积模块由卷积层、BN层和LeakyReLU激活层组成。在YoloV3论文中,第一个卷积模块的卷积核数是第二个的一半。第一个卷积是1x1的,第二个卷积是3x3的。

class ResBlock(BaseModule):    """The basic residual block used in Darknet. Each ResBlock consists of two    ConvModules and the input is added to the final output. Each ConvModule is    composed of Conv, BN, and LeakyReLU. In YoloV3 paper, the first convLayer    has half of the number of the filters as much as the second convLayer. The    first convLayer has filter size of 1x1 and the second one has the filter    size of 3x3.    Args:        in_channels (int): The input channels. Must be even.        conv_cfg (dict): Config dict for convolution layer. Default: None.        norm_cfg (dict): Dictionary to construct and config norm layer.            Default: dict(type='BN', requires_grad=True)        act_cfg (dict): Config dict for activation layer.            Default: dict(type='LeakyReLU', negative_slope=0.1).        init_cfg (dict or list[dict], optional): Initialization config dict.            Default: None    """    def __init__(self,                 in_channels,                 conv_cfg=None,                 norm_cfg=dict(type='BN', requires_grad=True),                 act_cfg=dict(type='LeakyReLU', negative_slope=0.1),                 init_cfg=None):        super(ResBlock, self).__init__(init_cfg)        assert in_channels % 2 == 0  # ensure the in_channels is even        half_in_channels = in_channels // 2        # shortcut        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)        self.conv1 = ConvModule(in_channels, half_in_channels, 1, **cfg)        self.conv2 = ConvModule(            half_in_channels, in_channels, 3, padding=1, **cfg)    def forward(self, x):        residual = x        out = self.conv1(x)        out = self.conv2(out)        out = out + residual # 残差运算        return out

YoloV3的Neck

以下红框部署是YOLOv3的NECK部分,可以把YOLOv3的Neck看做成FPN的简化版。具有融合多尺度

YOLOv3Neck的参数量及计算量估计

模块名称参数量参数量占比GFLOPsFLOAPs占比detect111.017 M17.907%11.0215.685%conv10.132 M,0.214%0.1320.068%detect22.822 M4.586%11.2945.826%conv20.033 M0.054%0.1330.068%detect30.706 M1.148%11.3155.837%YOLOV3Neck14.71 M23.909%33.89417.485%

YOLOv3Neck的网络图

YOLOv3Neck类图

YOLOv3Neck代码

代码在mmdet/models/necks/yolo_neck.py#L65处

YOLOv3Neck可以被视为FPN的简化版本。它将从骨干网络Darknet中获取特征提取结果,并进行一些上采样和串联,进行不同尺度的特种融合,并输出给YOLOv3Head

@NECKS.register_module()class YOLOV3Neck(BaseModule):    """The neck of YOLOV3.    It can be treated as a simplified version of FPN. It    will take the result from Darknet backbone and do some upsampling and    concatenation. It will finally output the detection result.    Note:        The input feats should be from top to bottom.            i.e., from high-lvl to low-lvl        But YOLOV3Neck will process them in reversed order.            i.e., from bottom (high-lvl) to top (low-lvl)    Args:        num_scales (int): The number of scales / stages.        in_channels (List[int]): The number of input channels per scale.        out_channels (List[int]): The number of output channels  per scale.        conv_cfg (dict, optional): Config dict for convolution layer.            Default: None.        norm_cfg (dict, optional): Dictionary to construct and config norm            layer. Default: dict(type='BN', requires_grad=True)        act_cfg (dict, optional): Config dict for activation layer.            Default: dict(type='LeakyReLU', negative_slope=0.1).        init_cfg (dict or list[dict], optional): Initialization config dict.            Default: None    """    def __init__(self,                 num_scales,                 in_channels,                 out_channels,                 conv_cfg=None,                 norm_cfg=dict(type='BN', requires_grad=True),                 act_cfg=dict(type='LeakyReLU', negative_slope=0.1),                 init_cfg=None):        super(YOLOV3Neck, self).__init__(init_cfg)        assert (num_scales == len(in_channels) == len(out_channels))        self.num_scales = num_scales        self.in_channels = in_channels        self.out_channels = out_channels        # shortcut        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)        # To support arbitrary scales, the code looks awful, but it works.        # Better solution is welcomed.        self.detect1 = DetectionBlock(in_channels[0], out_channels[0], **cfg)        for i in range(1, self.num_scales):            in_c, out_c = self.in_channels[i], self.out_channels[i]            inter_c = out_channels[i - 1]            self.add_module(f'conv{i}', ConvModule(inter_c, out_c, 1, **cfg))            # in_c + out_c : High-lvl feats will be cat with low-lvl feats            self.add_module(f'detect{i+1}',                            DetectionBlock(in_c + out_c, out_c, **cfg))    def forward(self, feats):        assert len(feats) == self.num_scales        # processed from bottom (high-lvl) to top (low-lvl)        outs = []        out = self.detect1(feats[-1])        outs.append(out)        for i, x in enumerate(reversed(feats[:-1])):            conv = getattr(self, f'conv{i+1}')            tmp = conv(out)            # Cat with low-lvl feats            tmp = F.interpolate(tmp, scale_factor=2)            tmp = torch.cat((tmp, x), 1)            detect = getattr(self, f'detect{i+2}')            out = detect(tmp)            outs.append(out)        return tuple(outs)

DetectionBlock

DetectionBlock由六个卷积层组成1x1xn, 3x3x2n, 1x1xn, 3x3x2n, 1x1xn, 3x3x2n,第一个DetectionBlock的第一个卷积层是1x1x256。

class DetectionBlock(BaseModule):    """Detection block in YOLO neck.    Let out_channels = n, the DetectionBlock contains:    Six ConvLayers, 1 Conv2D Layer and 1 YoloLayer.    The first 6 ConvLayers are formed the following way:        1x1xn, 3x3x2n, 1x1xn, 3x3x2n, 1x1xn, 3x3x2n.    The Conv2D layer is 1x1x255.    Some block will have branch after the fifth ConvLayer.    The input channel is arbitrary (in_channels)    Args:        in_channels (int): The number of input channels.        out_channels (int): The number of output channels.        conv_cfg (dict): Config dict for convolution layer. Default: None.        norm_cfg (dict): Dictionary to construct and config norm layer.            Default: dict(type='BN', requires_grad=True)        act_cfg (dict): Config dict for activation layer.            Default: dict(type='LeakyReLU', negative_slope=0.1).        init_cfg (dict or list[dict], optional): Initialization config dict.            Default: None    """    def __init__(self,                 in_channels,                 out_channels,                 conv_cfg=None,                 norm_cfg=dict(type='BN', requires_grad=True),                 act_cfg=dict(type='LeakyReLU', negative_slope=0.1),                 init_cfg=None):        super(DetectionBlock, self).__init__(init_cfg)        double_out_channels = out_channels * 2        # shortcut        cfg = dict(conv_cfg=conv_cfg, norm_cfg=norm_cfg, act_cfg=act_cfg)        self.conv1 = ConvModule(in_channels, out_channels, 1, **cfg)        self.conv2 = ConvModule(            out_channels, double_out_channels, 3, padding=1, **cfg)        self.conv3 = ConvModule(double_out_channels, out_channels, 1, **cfg)        self.conv4 = ConvModule(            out_channels, double_out_channels, 3, padding=1, **cfg)        self.conv5 = ConvModule(double_out_channels, out_channels, 1, **cfg)    def forward(self, x):        tmp = self.conv1(x)        tmp = self.conv2(tmp)        tmp = self.conv3(tmp)        tmp = self.conv4(tmp)        out = self.conv5(tmp)        return out

YOLOv3的Head

YOLOv3的Head定义了真实值的编码,输出的是一个4维的Tensor (batch_size, 5+num_classes, height, width),在本文中num_classes=1。

YOLOV3Head的参数量及计算量估计

模块名称参数量参数量占比GFLOPsFLOAPs占比convs_bridge6.197 M10.072%14.1777.314%convs_pred0.032 M0.053%0.129FLOPsYOLOV3Head6.229 M10.125%14.3077.380%

YOLOv3Head网络图

YOLOv3Head类图

YOLOv3Head继承关系

可以看到YOLOV3Head同时继承于BaseDenseHead和BBoxTestMixin

YOLOv3Head属性及方法

YOLOv3Head代码

mmdet/models/dense_heads/yolof_head.py#L43 中是YOLOv3Head的代码,代码过长就补贴代码了。

Anchor_generator

anchor_generator=dict(                     type='YOLOAnchorGenerator',                     base_sizes=[[(116, 90), (156, 198), (373, 326)],                                 [(30, 61), (62, 45), (59, 119)],                                 [(10, 13), (16, 30), (33, 23)]],                     strides=[32, 16, 8]),

参考


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/AI/1117.html