基于深度学习的目标检测和分割

参考链接:https://www.jianshu.com/p/5056e6143ed5
目标检测技术的演进:RCNN->SppNET->Fast-RCNN->Faster-RCNN
不同于分类问题,物体检测可能会存在多个检测目标,这不仅需要我们判别出各个物体的类别,而且还要准确定位出物体的位置。
在这里插入图片描述
首先讲解几个常用的概念:BboxIoU非极大值抑制
Bounding Box(bbox)
bbox是包含物体的最小矩形,该物体应在最小矩形内部,如上图红色框蓝色框和绿色框。
物体检测中关于物体位置的信息输出是一组(x,y,w,h)数据,其中x,y代表着bbox的左上角(或者其他固定点,可自定义),对应的w,h表示bbox的宽和高.一组(x,y,w,h)可以唯一的确定一个定位框。
Intersection over Union(IoU)
对于两个区域 R R R R ′ R^{'} R,则两个区域的重叠程度overlap计算如下: O ( R , R ′ ) = R ∩ R ′ R ∪ R ′ O(R,R^{'})=\frac{R\cap R^{'}}{R\cup R^{'}} O(R,R)=RRRR
非极大值抑制(Non-Maximum Suppression又称NMS)
非极大值抑制(NMS)可以看做是局部最大值的搜索问题,把不是极大值的抑制掉,在物体检测上,就是对一个目标有多个标定框,使用极大值抑制算法滤掉多余的标定框。
在这里插入图片描述
主流的检测框架:
主要分为两阶段检测器,单阶段检测器。
在这里插入图片描述

R-CNN(Regions with CNN features)

在这里插入图片描述
如上图所示,R-CNN这个物体检查系统可以大致分为四步进行:

  • 获取输入图像
  • 提取约2000个候选区域(Region Proposal)
  • 将候选区域分别输入CNN网络(这里需要将候选图片进行缩放)
  • 将CNN的输出输入到SVM中进行类别的判定

第一步:生成Region Proposal

高度非线性的深度网络具有很强的建模能力,计算复杂度高仅生成少量Region Proposal;训练需要大量标注数据有监督预训练 +领域特定微调。比较常用的是selective search方法,具有如下特性:

  • 无监督:没有训练过程,不需要带标注的数据
  • 数据驱动:根据图像特征生成候选窗
  • 基于图像分割任务

有如下两种方式:
对图像进行分割,每个分割区域生成一个对应的外接矩形框
在这里插入图片描述
基于相似度进行层次化地区域合并
在这里插入图片描述

第二步:用CNN提取Region Proposal特征

将不同大小的Region Proposal缩放到相同大小:227x227, 进行些许扩大以包含少量上下文信息。
缩放分为两大类:
1)各向同性缩放,长宽放缩相同的倍数

  • tightest square with context: 把region proposal的边界进行扩展延伸成正方形,灰色部分用原始图片中的相应像素填补,如下图(B)所示
  • tightest square without context: 把region proposal的边界进行扩展延伸成正方形,灰色部分不填补,如下图 (C ) 所示

2)各向异性缩放, 长宽放缩的倍数不同
不管图片是否扭曲,长宽缩放的比例可能不一样,直接将长宽缩放到227*227,如下图(D)所示
在这里插入图片描述
将所有窗口送入Backbone,如预训练的 AlexNet,ResNet等提取特征。一般与训练的backbone需要进行微调finetune,
以最后一个全连接层FC的输出作为特征表示。

第三步:对Region Proposal进行分类+边框校准

分类算法:

  • SVM:对CNN输出的特征用SVM进行分类,针对每个类别单独训练。二分类问题,判断是不是属于这个类别,是就是positive,反之negative。
  • Softmax:和整个CNN一起端到端训练,所有类别一起训练,多类分类
    在这里插入图片描述

边框校准:
使用回归器精细修正候选框位置:对于每一个类,训练一个线性回归模型去判定这个框是否框得完美。

  • 让检测框的位置更加准确,同时更加紧致(包含更少的背景区域)
    在这里插入图片描述
  • 线性回归模型
    对于检测框 P ( P x , P y , P w , P h ) P(P_{x},P_{y},P_{w},P_{h}) P(Px,Py,Pw,Ph)修正到预测框 G ^ ( G ^ x , G ^ y , G ^ w , G ^ h ) \hat{G}(\hat{G}_{x},\hat{G}_{y},\hat{G}_{w},\hat{G}_{h}) G^(G^x,G^y,G^w,G^h),首先平移中心点坐标
    G ^ x = P w d x ( P ) + P x G ^ y = P h d y ( P ) + P y \hat{G}_{x}=P_{w}d_{x}(P)+P_{x}\\ \hat{G}_{y}=P_{h}d_{y}(P)+P_{y} G^x=Pwdx(P)+PxG^y=Phdy(P)+Py
    其中 P w d x ( P ) P_{w}d_{x}(P) Pwdx(P) P h d y ( P ) P_{h}d_{y}(P) Phdy(P)是平移量, d x ( P ) d_{x}(P) dx(P) d y ( P ) d_{y}(P) dy(P)是回归的目标。
    对宽和高进行缩放:
    G ^ w = P w exp ⁡ ( d w ( P ) ) G ^ h = P h exp ⁡ ( d h ( P ) ) \hat{G}_{w}=P_{w}\exp{(d_{w}(P))}\\ \hat{G}_{h}=P_{h}\exp{(d_{h}(P))} G^w=Pwexp(dw(P))G^h=Phexp(dh(P))
    其中 exp ⁡ ( d w ( P ) ) \exp{(d_{w}(P))} exp(dw(P)) exp ⁡ ( d h ( P ) ) \exp{(d_{h}(P))} exp(dh(P))是伸缩因子, d w ( P ) d_{w}(P) dw(P) d h ( P ) d_{h}(P) dh(P)是回归的目标。

所以我们要学习的目标即为: d x ( P ) d_{x}(P) dx(P) d y ( P ) d_{y}(P) dy(P) d w ( P ) d_{w}(P) dw(P) d h ( P ) d_{h}(P) dh(P),统一为 d ∗ ( P ) d_{*}(P) d(P),可写为:
d ∗ ( P ) = w ∗ T Φ G A P ( P ) d_{*}(P)=w^{T}_{*}\Phi_{GAP}(P) d(P)=wTΦGAP(P)
其中 Φ G A P ( P ) \Phi_{GAP}(P) ΦGAP(P)表示proposal P P P经Backbone(AlexNet,VGGNet,ResNet,etc) Global Avg Pool之后的特征向量。
d x ( P ) d_{x}(P) dx(P) d y ( P ) d_{y}(P) dy(P) d w ( P ) d_{w}(P) dw(P) d h ( P ) d_{h}(P) dh(P)对应的groud truth为 t ∗ = { t x , t y , t w , t h } t_{*}=\{t_{x},t_{y},t_{w},t_{h}\} t={tx,ty,tw,th},那么误差函数写为:
w ∗ = arg ⁡ min ⁡ w ∗ 1 N ( t ∗ − w ∗ T Φ G A P ( P ) ) 2 + λ ∣ ∣ w ∗ ∣ ∣ 2 w_{*}=\arg\min_{w_{*}}\frac{1}{N}(t_{*}-w_{*}^{T}\Phi_{GAP}(P))^{2}+\lambda||w_{*}||^{2} w=argwminN1(twTΦGAP(P))2+λw2
目标框groud truth为 G ( G x , G y , G w , G h ) G(G_{x},G_{y},G_{w},G_{h}) G(Gx,Gy,Gw,Gh),所以 { t x , t y , t w , t h } \{t_{x},t_{y},t_{w},t_{h}\} {tx,ty,tw,th}确定值为:
t x = ( G x − P x ) / P w t y = ( G y − P y ) / P h t w = log ⁡ ( G w / P w ) t h = log ⁡ ( G h / P h ) t_{x}=(G_{x}-P_{x})/P_{w}\\ t_{y}=(G_{y}-P_{y})/P_{h}\\ t_{w}=\log(G_{w}/P_{w})\\ t_{h}=\log(G_{h}/P_{h}) tx=(GxPx)/Pwty=(GyPy)/Phtw=log(Gw/Pw)th=log(Gh/Ph)
Note that 只有当Proposal样本和Ground Truth比较接近时(这里取IoU>0.6),才能将其作为训练样本训练我们的线性回归模型,否则会导致训练的回归模型不work。(当Proposal跟G离得较远,就是复杂的非线性问题了,此时用线性回归建模显然不合理)

SPPNet (Spatial Pyramid Pooling)

R-CNN要求输入图像的尺寸相同,不同尺度和长宽比的区域被变换到相同大小。但是裁剪会使信息丢失(或引入过多背景),缩放会使物体变形:
在这里插入图片描述
卷积允许任意大小的图像输入网络。原始图像通过卷积层之后,Spatial Pyramid Pooling(SPP) layer负责将不同size的检测框进行归一化地pooling,每一个pooling的filter会根据输入调整大小,而SPP的输出尺度始终是固定的。
在这里插入图片描述
具体做法是,在conv5层得到的特征图是256个channel的,先把每个特征图分割成多个不同尺寸的网格,比如网格分别为4×4、2×2、1×1,然后每个网格做max pooling,这样256层特征图就形成了16×256,4×256,1×256维特征

一般来说检测框很多都是重叠的,对检测框进行卷积操作会带来大量的重复操作,所有SPPNet对原始图像进行卷积操作去除了各个区域的重复计算。此外,对于一个proposal,需要弄清楚SPP之后的每一个像素点对应的局部感受域的中心,如下给定一个例子:
在这里插入图片描述
通常情况下,设当前特征图下某位置为 x i + 1 x_{i+1} xi+1,对应于上一个特征图的卷积核中心的位置为 x i x_{i} xi,则有对应关系:
x i = s i ∗ x i + 1 + ⌈ F i − 1 2 ⌉ − P i x_{i}=s_{i}*x_{i+1}+\left \lceil \frac{F_{i}-1}{2} \right \rceil-P_{i} xi=sixi+1+2Fi1Pi
其中 s i s_{i} si是stride, F i F_{i} Fi是卷积核的尺寸, P i P_{i} Pi是卷积核的padding。一般情况下,可以取 P i = ⌊ F i / 2 ⌋ P_{i}=\left \lfloor F_{i}/{2} \right \rfloor Pi=Fi/2,所以可以化简为: x i = s i ∗ x i + 1 x_{i}=s_{i}*x_{i+1} xi=sixi+1
对公式进行级联可以得到: x 0 = ∏ i = 0 L s i ∗ x L + 1 x_{0}=\prod _{i=0}^{L}s_{i*}x_{L+1} x0=i=0LsixL+1

Fast R-CNN

在这里插入图片描述
加入了的ROI(Region Of Interest) Pooling层,对每个region都提取一个固定维度的特征表示。相当于特殊的SPP层,RoI层是使用单个尺度的SPP层(不用多个尺度的原因是多个尺度准确率提升不高,但是计算量开销显著)。
在这里插入图片描述
RoI Pooling原理
RoI层将每一个候选区域都分为提前定义的 H × W H\times W H×W块。对每个小块做max-pooling,此时每一个将候选区的局部特征映射转变为大小统一的数据,送入下一层。
在这里插入图片描述
梯度反向传播:
x i x_{i} xi为输入层结点, y i y_{i} yi为输出层的节点.
∂ L ∂ x i = { 0 , i f   δ ( i , j ) = F a l s e ∂ L ∂ y j , i f   δ ( i , j ) = T r u e \frac{\partial L }{\partial x_{i}}=\left\{\begin{matrix} 0, if\ \delta(i,j)=False \\ \frac{\partial L }{\partial y_{j}},if\ \delta(i,j)=True \end{matrix}\right. xiL={0,if δ(i,j)=FalseyjL,if δ(i,j)=True
中判决函数 δ ( i , j ) \delta(i,j) δ(i,j)表示 i i i节点是否被 j j j节点选为最大值输出。不被选中有两种可能: x i x_{i} xi不在 y j y_{j} yj范围内,或者 x i x_{i} xi不是最大值.
一个输入节点可能和多个输出节点相连。设 x i x_{i} xi为输入层的节点, y r j y_{rj} yrj为第 r r r个候选区域的第 j j j个输出节点。
∂ L ∂ x i = ∑ r , j δ ( i , r , j ) ∂ L ∂ y r j \frac{\partial L }{\partial x_{i}}=\sum_{r,j}\delta(i,r,j)\frac{\partial L }{\partial y_{rj}} xiL=r,jδ(i,r,j)yrjL
多任务
另外,之前RCNN的处理流程是先提proposal,然后CNN提取特征,之后用SVM分类器,最后再做bbox regression,而在Fast-RCNN中,作者巧妙的把bbox regression放进了神经网络内部,与region分类和并成为了一个multi-task模型,实际实验也证明,这两个任务能够共享卷积特征。
边框校准误差:smooth L1 Loss s m o o t h L 1 ( x ) = { 0.5 x x , ∣ x ∣ < 1 ∣ x ∣ − 0.5 , o t h e r w i s e smooth_{L_{1}}(x)=\left\{\begin{matrix} 0.5x^{x},|x|<1\\ |x|-0.5,otherwise \end{matrix}\right. smoothL1(x)={0.5xx,x<1x0.5,otherwise

Mask R-CNN

论文地址:https://arxiv.org/pdf/1703.06870.pdf
实例分割(instance segmentation):对于检测到的每个物体(实例),精确地标记出其每个像素
在这里插入图片描述

RoIAlign

在Faster R-CNN中增加实例分割模块:RoIPoolRoIAlign
ROIAlign:https://www.cnblogs.com/wangyong/p/8523814.html
在这里插入图片描述
在这里插入图片描述
对一张 800 × 800 800\times 800 800×800原图,经过VGG16的处理后,一共stride=32,图片缩小为 25 × 25 25\times 25 25×25。设定原图中有一 665 × 665 665\times 665 665×665的proposal,映射到特征图中的大小:665/32=20.78,即20.78×20.78

  • 对于RoIPool:在计算的时候会进行取整操作,于是,进行所谓的第一次量化,即映射的特征图大小为20×20。设归一化的尺寸为7×7,则每一个小区域的尺寸为:20/7=2.86,即2.86×2.86。此时,进行第二次量化,故小区域大小变成2×2。每个2×2的小区域里,取出其中最大的像素值,作为这一个区域的‘代表’,这样,49个小区域就输出49个像素值,组成2.97×2.97大小的feature map
    总结: 经过两次量化,即将浮点数取整,原本在特征图上映射的20×20大小的region proposal,偏差成大小为14×14的,这样的像素偏差势必会对后层的回归定位产生影响。所以,产生了更精细的替代方案,RoiAlign。
  • 对于RoIAlign:没有像RoiPooling那样就行取整操作,保留浮点数特征图大小20.78×20.78,之后划分每个小区域:20.78/7=2.97,即2.97×2.97。假定采样点数为4,即对于每个2.97×2.97的小区域,平分4份,每一份取其中心点位置,而中心点位置的像素,采用双线性插值法进行计算,这样,就会得到四个点的像素值,如下图
    在这里插入图片描述
    上图中,四个红色叉叉‘×’的像素值是通过双线性插值算法计算得到的。最后,取四个像素值中最大值作为这个小区域(即:2.97×2.97大小的区域)的像素值,如此类推,同样是49个小区域得到49个像素值,组成7×7大小的feature map

Faster R-CNN上增加了Instance Segmentation Head:
在这里插入图片描述

FCN

首先简单介绍一下全卷积 (FCN,fully-connected networks) ,FCN将传统CNN后面的全连接层替换为卷积,这样就可以获得2维的feature map,后接softmax获得每一个像素点的分类信息,从而解决分割问题。
论文:https://arxiv.org/pdf/1411.4038.pdf
众所周知,每一次卷积都是对图像的一次缩小,每一次缩小带来的是分辨率越低,图像越模糊,而在第一部分我们知道FCN是通过像素点进行图像分割,那FCN是怎么解决的这一个问题?答案是上采样,比如我们在3次卷积后,图像分别缩小了2 4 8倍,因此在最后的输出层,我们需要进行8倍的上采样,从而得到原来的图像大小.而上采样本身就是一个反卷积实现的。
在这里插入图片描述从论文中得到的结果来看,从32倍,16倍,8倍到最终结果,结果越来越精细:
在这里插入图片描述

Instance Segmentation Head

在这里插入图片描述
具体的,Head Architecture如下所示:
在这里插入图片描述
图中,箭头表明卷积反卷积FC层(根据context可以推断,conv保护spatial信息,deconv升采样,FC作用于一维向量)。所有的conv是3×3,除了输出conv是1×1,deconvs是2×2(stride=2)。Left:‘res5’表明ResNet的第5个阶段;Right:‘×4’表明4个连续的convs。

Pytorch实现

目标检测和分割可使用mmdetection库来实现,代码参考(CUHK,MM Lab):https://github.com/open-mmlab/mmdetection

1.安装

需要首先创建anaconda的虚拟环境,在虚拟环境中进行mmdection的安装:
(1)创建虚拟环境并激活:

conda create -n open-mmlab python=3.7 -y
conda activate open-mmlab

(2)安装pytorch,torchvision以及依赖的mmcv库,版本可以随机更改:

pip install torch==1.1.0
pip install torchvision==0.3.0
pip install mmcv

(3)定位到mmdection文件夹,运行如下命令编译安装:

python setup.py develop

2.更换backbone为自己的net

open-mmlab/mmdetection/tree/master/mmdet/models/backbones里放入backbone文件,这里以ResNetSE为例,创建resnet_se.py文件:

import logging

import torch.nn as nn
from mmcv.cnn import constant_init, kaiming_init
from mmcv.runner import load_checkpoint
from torch.nn.modules.batchnorm import _BatchNorm

from ..registry import BACKBONES
from ..utils import build_conv_layer, build_norm_layer


class SELayer(nn.Module):
    def __init__(self, channel, reduction = 16):
        super(SELayer, self).__init__()
        self.avg_pool = nn.AdaptiveAvgPool2d(1)
        self.fc       = nn.Sequential(
                        nn.Linear(channel, channel // reduction),
                        nn.ReLU(inplace = True),
                        nn.Linear(channel // reduction, channel),
                        nn.Sigmoid()
                )
        print('add one SELayer!')

    def forward(self, x):
        b, c, _, _ = x.size()
        y = self.avg_pool(x).view(b, c)
        y = self.fc(y).view(b, c, 1, 1)
        return x * y

class Bottleneck(nn.Module):
    expansion = 4

    def __init__(self,
                 inplanes,
                 planes,
                 stride=1,
                 dilation=1,
                 downsample=None,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN')):
        super(Bottleneck, self).__init__()

        self.inplanes = inplanes
        self.planes = planes
        self.stride = stride
        self.dilation = dilation
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg

        self.conv1_stride = 1
        self.conv2_stride = stride

        self.norm1_name, norm1 = build_norm_layer(norm_cfg, planes, postfix=1)
        self.norm2_name, norm2 = build_norm_layer(norm_cfg, planes, postfix=2)
        self.norm3_name, norm3 = build_norm_layer(
            norm_cfg, planes * self.expansion, postfix=3)

        self.conv1 = build_conv_layer(
            conv_cfg,
            inplanes,
            planes,
            kernel_size=1,
            stride=self.conv1_stride,
            bias=False)
        self.add_module(self.norm1_name, norm1)

        self.conv2 = build_conv_layer(
            conv_cfg,
            planes,
            planes,
            kernel_size=3,
            stride=self.conv2_stride,
            padding=dilation,
            dilation=dilation,
            bias=False)
        self.add_module(self.norm2_name, norm2)

        self.conv3 = build_conv_layer(
            conv_cfg,
            planes,
            planes * self.expansion,
            kernel_size=1,
            bias=False)
        self.add_module(self.norm3_name, norm3)

        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.se = SELayer(planes * self.expansion)

    @property
    def norm1(self):
        return getattr(self, self.norm1_name)

    @property
    def norm2(self):
        return getattr(self, self.norm2_name)

    @property
    def norm3(self):
        return getattr(self, self.norm3_name)

    def forward(self, x):
        identity = x

        out = self.conv1(x)
        out = self.norm1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.norm2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.norm3(out)

        out = self.se(out)

        if self.downsample is not None:
            identity = self.downsample(x)
        out += identity
        out = self.relu(out)
        return out


def make_res_layer(block,
                   inplanes,
                   planes,
                   blocks,
                   stride=1,
                   dilation=1,
                   conv_cfg=None,
                   norm_cfg=dict(type='BN')):
    downsample = None
    if stride != 1 or inplanes != planes * block.expansion:
        downsample = nn.Sequential(
            build_conv_layer(
                conv_cfg,
                inplanes,
                planes * block.expansion,
                kernel_size=1,
                stride=stride,
                bias=False),
            build_norm_layer(norm_cfg, planes * block.expansion)[1],
        )

    layers = []
    layers.append(
        block(
            inplanes=inplanes,
            planes=planes,
            stride=stride,
            dilation=dilation,
            downsample=downsample,
            conv_cfg=conv_cfg,
            norm_cfg=norm_cfg))
    inplanes = planes * block.expansion
    for i in range(1, blocks):
        layers.append(
            block(
                inplanes=inplanes,
                planes=planes,
                stride=1,
                dilation=dilation,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg))

    return nn.Sequential(*layers)


@BACKBONES.register_module
class ResNetSE(nn.Module):

    arch_settings = {
        50: (Bottleneck, (3, 4, 6, 3)),
        101: (Bottleneck, (3, 4, 23, 3)),
        152: (Bottleneck, (3, 8, 36, 3))
    }

    def __init__(self,
                 depth,
                 in_channels=3,
                 num_stages=4,
                 strides=(1, 2, 2, 2),
                 dilations=(1, 1, 1, 1),
                 out_indices=(0, 1, 2, 3),
                 frozen_stages=-1,
                 conv_cfg=None,
                 norm_cfg=dict(type='BN', requires_grad=True),
                 norm_eval=True,
                 zero_init_residual=True):
        super(ResNetSE, self).__init__()
        self.depth = depth
        self.strides = strides
        self.dilations = dilations
        self.out_indices = out_indices
        self.frozen_stages = frozen_stages
        self.conv_cfg = conv_cfg
        self.norm_cfg = norm_cfg
        self.norm_eval = norm_eval
        self.zero_init_residual = zero_init_residual
        self.block, stage_blocks = self.arch_settings[depth]
        self.stage_blocks = stage_blocks[:num_stages]
        self.inplanes = 64

        self._make_stem_layer(in_channels)

        self.res_layers = []
        for i, num_blocks in enumerate(self.stage_blocks):
            stride = strides[i]
            dilation = dilations[i]
            planes = 64 * 2**i
            res_layer = make_res_layer(
                self.block,
                self.inplanes,
                planes,
                num_blocks,
                stride=stride,
                dilation=dilation,
                conv_cfg=conv_cfg,
                norm_cfg=norm_cfg)
            self.inplanes = planes * self.block.expansion
            layer_name = 'layer{}'.format(i + 1)
            self.add_module(layer_name, res_layer)
            self.res_layers.append(layer_name)

        self._freeze_stages()

        self.feat_dim = self.block.expansion * 64 * 2**(
            len(self.stage_blocks) - 1)

    @property
    def norm1(self):
        return getattr(self, self.norm1_name)

    def _make_stem_layer(self, in_channels):
        self.conv1 = build_conv_layer(
            self.conv_cfg,
            in_channels,
            64,
            kernel_size=7,
            stride=2,
            padding=3,
            bias=False)
        self.norm1_name, norm1 = build_norm_layer(self.norm_cfg, 64, postfix=1)
        self.add_module(self.norm1_name, norm1)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

    def _freeze_stages(self):
        if self.frozen_stages >= 0:
            self.norm1.eval()
            for m in [self.conv1, self.norm1]:
                for param in m.parameters():
                    param.requires_grad = False

        for i in range(1, self.frozen_stages + 1):
            m = getattr(self, 'layer{}'.format(i))
            m.eval()
            for param in m.parameters():
                param.requires_grad = False

    def init_weights(self, pretrained=None):
       if isinstance(pretrained, str):
            checkpoint = torch.load(pretrained)
            param_dict = {}
            for k, v in zip(self.state_dict().keys(), checkpoint['state_dict'].keys()):
                param_dict[k] = checkpoint['state_dict'][v]
            self.load_state_dict(param_dict)
        elif pretrained is None:
            for m in self.modules():
                if isinstance(m, nn.Conv2d):
                    kaiming_init(m)
                elif isinstance(m, (_BatchNorm, nn.GroupNorm)):
                    constant_init(m, 1)

            if self.zero_init_residual:
                for m in self.modules():
                    if isinstance(m, Bottleneck):
                        constant_init(m.norm3, 0)
        else:
            raise TypeError('pretrained must be a str or None')

    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)

    def train(self, mode=True):
        super(ResNetSE, self).train(mode)
        self._freeze_stages()
        if mode and self.norm_eval:
            for m in self.modules():
                # trick: eval have effect on BatchNorm only
                if isinstance(m, _BatchNorm):
                    m.eval()

Note that backbone文件会与pytorch的model文件略有不同,因为目标检测和实例分割还需要在数据集上finetune等等,结合mmdetection库,修改model文件的细节如下所示:

  • 所有创建conv的操作都由nn.Conv2d变为build_conv_layer,并且第一个参数是conv_cfg
  • 所有创建batch_norm的操作都由nn.BatchNorm2d变为build_norm_layer,并且都要使用self.add_module(self.norm_name, norm)@property来进行索引。
  • 加入参数冻结函数:_freeze_stages

3.修改配置文件

open-mmlab/mmdetection/tree/master/mmdet/models/backbones里放入配置文件,以mask_rcnn_r50_fpn_1x.py为模板,注意修改如下部分的内容:

model = dict(
    type='MaskRCNN',
    pretrained='torchvision://resnet50',  # 预训练的模型文件
    backbone=dict(
        type='ResNetSE',   # 模型名字,下面的参数与model文件参数一致
        depth=50,  # 
        num_stages=4,
        out_indices=(0, 1, 2, 3),
        frozen_stages=1),  # frozen_stages表示冻结的阶段编号
    neck=dict(
        type='FPN',
        in_channels=[256, 512, 1024, 2048],  # 这里需要修改对应out_indices每个阶段输出的channel
        out_channels=256,
        num_outs=5),
    ...

训练细节部分:
(1)对于8GPUS* 2imgs=16imgs/batch设置,初始学习率为 0.02。若每个batch处理的img数量不同,则需要调整,比如2GPUS*2imgs=4imgs/batch,则初始学习率为0.005.
(2)finetune一共有2种epoch数量设置,12epochs和24epochs。两种方法需要调整lr_config中的step参数,分别为[8,11][16,22]

optimizer = dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001)
optimizer_config = dict(grad_clip=dict(max_norm=35, norm_type=2))
# learning policy
lr_config = dict(
    policy='step',
    warmup='linear',
    warmup_iters=500,
    warmup_ratio=1.0 / 3,
    step=[8, 11])
...
total_epochs = 12
已标记关键词 清除标记
<p style="font-size:16px;"> 本课程适合具有一定深度学习基础,希望发展为深度学习之计算机视觉方向的算法工程师和研发人员的同学们。<br /> <br /> 基于深度学习的计算机视觉是目前人工智能最活跃的领域,应用非常广泛,如人脸识别和无人驾驶中的机器视觉等。该领域的发展日新月异,网络模型和算法层出不穷。如何快速入门并达到可以从事研发的高度对新手和中级水平的学生而言面临不少的挑战。精心准备的本课程希望帮助大家尽快掌握基于深度学习的计算机视觉的基本原理、核心算法和当前的领先技术,从而有望成为深度学习之计算机视觉方向的算法工程师和研发人员。<br /> <br /> 本课程系统全面地讲述基于深度学习的计算机视觉技术的原理并进行项目实践。课程涵盖计算机视觉的七大任务,包括图像分类、目标检测、图像分割(语义分割、实例分割、全景分割)、人脸识别、图像描述、图像检索、图像生成(利用生成对抗网络)。本课程注重原理和实践相结合,逐篇深入解读经典和前沿论文70余篇,图文并茂破译算法难点, 使用思维导图梳理技术要点。项目实践使用Keras框架(后端为Tensorflow),学员可快速上手。<br /> <br /> 通过本课程的学习,学员可把握基于深度学习的计算机视觉的技术发展脉络,掌握相关技术原理和算法,有助于开展该领域的研究与开发实战工作。另外,深度学习之计算机视觉方向的知识结构及学习建议请参见本人CSDN博客。<br /> <br /> 本课程提供课程资料的课件PPT(pdf格式)和项目实践代码,方便学员学习和复习。<br /> <br /> 本课程分为上下两部分,其中上部包含课程的前五章(课程介绍、深度学习基础、图像分类、目标检测、图像分割),下部包含课程的后四章(人脸识别、图像描述、图像检索、图像生成)。 </p> <div> <br /> </div> <p> <br /> </p> <p> <br /> </p> <p style="font-size:16px;"> <br /> </p> <p style="font-size:16px;"> <img src="https://img-bss.csdn.net/201902211157137641.jpg" alt="" /><img src="https://img-bss.csdn.net/201902211157578041.gif" alt="" /><img src="https://img-bss.csdn.net/201902211158173579.gif" alt="" /><img src="https://img-bss.csdn.net/201902211158498135.gif" alt="" /><img src="https://img-bss.csdn.net/201902211159093293.gif" alt="" /><img src="https://img-bss.csdn.net/201902211159209625.gif" alt="" /> </p> <p style="font-size:16px;"> <br /> </p>
<p style="font-size:16px;"> 本课程适合具有一定深度学习基础,希望发展为深度学习之计算机视觉方向的算法工程师和研发人员的同学们。<br /> <br /> 基于深度学习的计算机视觉是目前人工智能最活跃的领域,应用非常广泛,如人脸识别和无人驾驶中的机器视觉等。该领域的发展日新月异,网络模型和算法层出不穷。如何快速入门并达到可以从事研发的高度对新手和中级水平的学生而言面临不少的挑战。精心准备的本课程希望帮助大家尽快掌握基于深度学习的计算机视觉的基本原理、核心算法和当前的领先技术,从而有望成为深度学习之计算机视觉方向的算法工程师和研发人员。<br /> <br /> 本课程系统全面地讲述基于深度学习的计算机视觉技术的原理并进行项目实践。课程涵盖计算机视觉的七大任务,包括图像分类、目标检测、图像分割(语义分割、实例分割、全景分割)、人脸识别、图像描述、图像检索、图像生成(利用生成对抗网络)。本课程注重原理和实践相结合,逐篇深入解读经典和前沿论文70余篇,图文并茂破译算法难点, 使用思维导图梳理技术要点。项目实践使用Keras框架(后端为Tensorflow),学员可快速上手。<br /> <br /> 通过本课程的学习,学员可把握基于深度学习的计算机视觉的技术发展脉络,掌握相关技术原理和算法,有助于开展该领域的研究与开发实战工作。另外,深度学习之计算机视觉方向的知识结构及学习建议请参见本人CSDN博客。<br /> <br /> 本课程提供课程资料的课件PPT(pdf格式)和项目实践代码,方便学员学习和复习。<br /> <br /> 本课程分为上下两部分,其中上部包含课程的前五章(课程介绍、深度学习基础、图像分类、目标检测、图像分割),下部包含课程的后四章(人脸识别、图像描述、图像检索、图像生成)。 </p> <p style="font-size:16px;"> <br /> </p> <p style="font-size:16px;"> <img src="https://img-bss.csdn.net/201902221256508000.gif" alt="" /><img src="https://img-bss.csdn.net/201902221257045928.gif" alt="" /><img src="https://img-bss.csdn.net/201902221257156312.gif" alt="" /><img src="https://img-bss.csdn.net/201902221257252319.gif" alt="" /> </p>
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页