zoukankan      html  css  js  c++  java
  • 『计算机视觉』经典RCNN_其二:Faster-RCNN

    项目源码

    一、Faster-RCNN简介

    『cs231n』Faster_RCNN

    『计算机视觉』Faster-RCNN学习_其一:目标检测及RCNN谱系

    一篇讲的非常明白的文章:一文读懂Faster RCNN

    (1)输入测试图像;

    (2)将整张图片输入CNN,进行特征提取;

    (3)用RPN生成建议窗口(proposals),每张图片保留约300个建议窗口;

    (4)把建议窗口映射到CNN的最后一层卷积feature map上;

    (5)通过RoI pooling层使每个RoI生成固定尺寸的feature map;

    (6)利用Softmax Loss(探测分类概率) 和Smooth L1 Loss(探测边框回归)对分类概率和边框回归(Bounding box regression)联合训练.

    相比FAST-RCNN,主要两处不同

    (1)使用RPN(Region Proposal Network)代替原来的Selective Search方法产生建议窗口;

    (2)产生建议窗口的CNN和目标检测的CNN共享

    改进

    快速产生建议框:FASTER-RCNN创造性地采用卷积网络自行产生建议框,并且和目标检测网络共享卷积网络,使得建议框数目从原有的约2000个减少为300个,且建议框的质量也有本质的提高.

    RPN简介

    放到整体网络中如下,

    对于共享的Feature Map,RPN使用3*3的滑窗,每个滑动窗口位置生成9个候选窗口(不同尺度、不同宽高),对应36个坐标、18个分类。

    训练过程中,

    1)丢弃跨越边界的anchor;

    2)与样本重叠区域大于0.7的anchor标记为前景,重叠区域小于0.3的标定为背景;

    总结一下:

      • 在feature map上滑动窗口
      • 建一个神经网络用于物体分类+框位置的回归
      • 滑动窗口的位置提供了物体的大体位置信息
      • 框的回归提供了框更精确的位置

    这里的分类只需要区分候选框内特征为前景或者背景,这里的边框回归也是为了之后获得更精确的目标位置。

     

    损失函数

    故整个网络包含四个损失函数;
      • RPN calssification(anchor good.bad),判断anchor前景背景类别
      • RPN regression(anchor->propoasal),计算anchor和gt box的偏差,利用mask仅计算前景anchor
      • Fast R-CNN classification(over classes),判断proposal分类,包含类别数C+背景
      • Fast R-CNN regression(proposal ->box),计算proposal和gt box的偏差,利用mask仅计算类别的proposal

    注意到前两个损失函数目标是修正anchor,后两个损失函数目标是修正proposal,实际上产生了众多anchors后,会进行筛选(非极大值抑制并按照前景得分排序等等),选出特定比例的前景背景anchors作为proposal进行后面的运算。

    二、代码解读

    Faster-RCNN网络设计很是复杂,个人觉得但看论文或者其他材料并不能很好的理解这套流程,本篇我们从实际的代码出发,看看到底是这套网络系统到底是如何设计的。

    anchor和proposal

    region proposal(或者简称proposal,或者简称ROI),可以说RPN网络的目的就是为了得到proposal,这些proposal是对ground truth更好的刻画(和anchor相比,坐标更贴近ground truth,毕竟anchor的坐标都是批量地按照scale和aspect ratio复制的)。如果你还记得在系列二中关于网络结构的介绍,那么你就应该了解到RPN网络的回归支路输出的值(offset)作为smooth l1损失函数的输入之一时,其含义就是使得proposal和anchor之间的offset(RPN网络的回归支路输出)尽可能与ground truth和anchor之间的offset(RPN网络的回归支路的回归目标)接近。

    调包部分

    涉及了mx基础框架和一个作者自己创建的operate,

    import mxnet as mx from symnet import proposal_target

    特征提取器一

    如开篇的图,这里是最上面一行的网络,即提取Feature Map的部分,使用了4个pooling层,意味着原始图像:Feature Map = 16:1,

    def get_vgg_feature(data):
        # group 1
        conv1_1 = mx.symbol.Convolution(
            data=data, kernel=(3, 3), pad=(1, 1), num_filter=64, workspace=2048, name="conv1_1")
        relu1_1 = mx.symbol.Activation(data=conv1_1, act_type="relu", name="relu1_1")
        conv1_2 = mx.symbol.Convolution(
            data=relu1_1, kernel=(3, 3), pad=(1, 1), num_filter=64, workspace=2048, name="conv1_2")
        relu1_2 = mx.symbol.Activation(data=conv1_2, act_type="relu", name="relu1_2")
        pool1 = mx.symbol.Pooling(
            data=relu1_2, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool1")
        # group 2
        conv2_1 = mx.symbol.Convolution(
            data=pool1, kernel=(3, 3), pad=(1, 1), num_filter=128, workspace=2048, name="conv2_1")
        relu2_1 = mx.symbol.Activation(data=conv2_1, act_type="relu", name="relu2_1")
        conv2_2 = mx.symbol.Convolution(
            data=relu2_1, kernel=(3, 3), pad=(1, 1), num_filter=128, workspace=2048, name="conv2_2")
        relu2_2 = mx.symbol.Activation(data=conv2_2, act_type="relu", name="relu2_2")
        pool2 = mx.symbol.Pooling(
            data=relu2_2, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool2")
        # group 3
        conv3_1 = mx.symbol.Convolution(
            data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=256, workspace=2048, name="conv3_1")
        relu3_1 = mx.symbol.Activation(data=conv3_1, act_type="relu", name="relu3_1")
        conv3_2 = mx.symbol.Convolution(
            data=relu3_1, kernel=(3, 3), pad=(1, 1), num_filter=256, workspace=2048, name="conv3_2")
        relu3_2 = mx.symbol.Activation(data=conv3_2, act_type="relu", name="relu3_2")
        conv3_3 = mx.symbol.Convolution(
            data=relu3_2, kernel=(3, 3), pad=(1, 1), num_filter=256, workspace=2048, name="conv3_3")
        relu3_3 = mx.symbol.Activation(data=conv3_3, act_type="relu", name="relu3_3")
        pool3 = mx.symbol.Pooling(
            data=relu3_3, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool3")
        # group 4
        conv4_1 = mx.symbol.Convolution(
            data=pool3, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv4_1")
        relu4_1 = mx.symbol.Activation(data=conv4_1, act_type="relu", name="relu4_1")
        conv4_2 = mx.symbol.Convolution(
            data=relu4_1, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv4_2")
        relu4_2 = mx.symbol.Activation(data=conv4_2, act_type="relu", name="relu4_2")
        conv4_3 = mx.symbol.Convolution(
            data=relu4_2, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv4_3")
        relu4_3 = mx.symbol.Activation(data=conv4_3, act_type="relu", name="relu4_3")
        pool4 = mx.symbol.Pooling(
            data=relu4_3, pool_type="max", kernel=(2, 2), stride=(2, 2), name="pool4")
        # group 5
        conv5_1 = mx.symbol.Convolution(
            data=pool4, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv5_1")
        relu5_1 = mx.symbol.Activation(data=conv5_1, act_type="relu", name="relu5_1")
        conv5_2 = mx.symbol.Convolution(
            data=relu5_1, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv5_2")
        relu5_2 = mx.symbol.Activation(data=conv5_2, act_type="relu", name="relu5_2")
        conv5_3 = mx.symbol.Convolution(
            data=relu5_2, kernel=(3, 3), pad=(1, 1), num_filter=512, workspace=2048, name="conv5_3")
        relu5_3 = mx.symbol.Activation(data=conv5_3, act_type="relu", name="relu5_3")
    
        return relu5_3
    

    特征提取器二

    下面是最后的一小部分网络,我们将筛选出来的proposal作用于Feature Map,得到候选区域,然后使用ROIPooling得到大小一致的候选区域,这些区域送入本部分网络,得到用于分类、回归的特征,

    def get_vgg_top_feature(data):
        # group 6
        flatten = mx.symbol.Flatten(data=data, name="flatten")
        fc6 = mx.symbol.FullyConnected(data=flatten, num_hidden=4096, name="fc6")
        relu6 = mx.symbol.Activation(data=fc6, act_type="relu", name="relu6")
        drop6 = mx.symbol.Dropout(data=relu6, p=0.5, name="drop6")
        # group 7
        fc7 = mx.symbol.FullyConnected(data=drop6, num_hidden=4096, name="fc7")
        relu7 = mx.symbol.Activation(data=fc7, act_type="relu", name="relu7")
        drop7 = mx.symbol.Dropout(data=relu7, p=0.5, name="drop7")
        return drop7
    

    主体函数

    函数接口

    def get_vgg_train(anchor_scales, anchor_ratios, rpn_feature_stride,
                      rpn_pre_topk, rpn_post_topk, rpn_nms_thresh, rpn_min_size, rpn_batch_rois,
                      num_classes, rcnn_feature_stride, rcnn_pooled_size, rcnn_batch_size,
                      rcnn_batch_rois, rcnn_fg_fraction, rcnn_fg_overlap, rcnn_bbox_stds):
    

    首先定义了一些用于接收数据&标签信息的占位符变量,

        num_anchors = len(anchor_scales) * len(anchor_ratios)
    
        data = mx.symbol.Variable(name="data")                     # 图片信息:[batch, channel, height, width]
        im_info = mx.symbol.Variable(name="im_info")
        gt_boxes = mx.symbol.Variable(name="gt_boxes")             # 真实框信息:[obj数量, 5]
        # anchor标签:-1、0、1 (无效、背景、目标)
        rpn_label = mx.symbol.Variable(name='label')               # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        # 根据初始anchor和ground truth计算出来的offset
        rpn_bbox_target = mx.symbol.Variable(name='bbox_target')  # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
        # mask,如果anchor标签是1,则mask对应值为1,anchor标签是0或-1,则mask对应值为0
        rpn_bbox_weight = mx.symbol.Variable(name='bbox_weight')  # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
    

    获取卷积特征

       # shared convolutional layers
        conv_feat = get_vgg_feature(data)
    

    使用3×3的滑窗,进行卷积,

      # RPN layers
        rpn_conv = mx.symbol.Convolution(
            data=conv_feat, kernel=(3, 3), pad=(1, 1), num_filter=512, name="rpn_conv_3x3")
        rpn_relu = mx.symbol.Activation(data=rpn_conv, act_type="relu", name="rpn_relu")
    

    每个滑动窗口位置生成9个候选窗口(不同尺度、不同宽高),对应36个坐标、18个分类,(即根据每张图片的feat map大小,生成对应数量的中心点,以及候选框体)

    RPN calssification(anchor good.bad)

    下面是计算全部候选窗口对应2分类的部分,生成损失函数的第一部分:全部候选窗含/不含obj的二分类损失

        # rpn classification: obj / not_obj
        # [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
        rpn_cls_score = mx.symbol.Convolution(
            data=rpn_relu, kernel=(1, 1), pad=(0, 0), num_filter=2 * num_anchors, name="rpn_cls_score")
    
        # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        rpn_cls_score_reshape = mx.symbol.Reshape(
            data=rpn_cls_score, shape=(0, 2, -1, 0), name="rpn_cls_score_reshape")
        # 获取交叉熵回传梯度,对于出现了-1的图片梯度直接全部置为0
        # 在Faster RCNN算法中,anchor一共有3种标签:-1、0、1,分别表示无效、背景、目标
        rpn_cls_prob = mx.symbol.SoftmaxOutput(data=rpn_cls_score_reshape, label=rpn_label, multi_output=True,
                                               normalization='valid', use_ignore=True, ignore_label=-1, name="rpn_cls_prob")
    
        # 切换为含/不含obj两通道,对每一个anchor是否有obj进行softmax处理
        # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        rpn_cls_act = mx.symbol.softmax(
            data=rpn_cls_score_reshape, axis=1, name="rpn_cls_act")
        # [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
        # Reshpe方法不同于reshape方法,详情见此方法文档
        rpn_cls_act_reshape = mx.symbol.Reshape(
            data=rpn_cls_act, shape=(0, 2 * num_anchors, -1, 0), name='rpn_cls_act_reshape')
    

    RPN regression(anchor->propoasal)

    下面计算全部候选窗口对应4个坐标值的部分,生成损失函数第二部分:全部候选框坐标回归

        # rpn bbox regression
        # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
        rpn_bbox_pred = mx.symbol.Convolution(
            data=rpn_relu, kernel=(1, 1), pad=(0, 0), num_filter=4 * num_anchors, name="rpn_bbox_pred")
        # rpn_bbox_weight: anchor的mask,如果某个anchor的标签是1,则mask对应值为1,anchor的标签是0或-1,则mask对应值为0
        # rpn_bbox_target: 根据初始anchor和ground truth计算出来的offset
        rpn_bbox_loss_ = rpn_bbox_weight * mx.symbol.smooth_l1(name='rpn_bbox_loss_',
                                                               scalar=3.0,
                                                               data=(rpn_bbox_pred - rpn_bbox_target))
        # 获取回归梯度
        # grad_scale: 表示回归损失占RPN网络总损失比,该参数相差几个量级对结果影响也不大
        rpn_bbox_loss = mx.sym.MakeLoss(name='rpn_bbox_loss', data=rpn_bbox_loss_, grad_scale=1.0 / rpn_batch_rois)
    

    候选框生成

    我们会获取数量繁多的候选框(即上面得到的候选框含有obj的得分候选框坐标修正值),首先会根据框体含有obj的概率进行一次初筛,然后经由非极大值抑制算法筛选2000个候选框进入下一步的操作;在第二步中,我们会获取最终的128个候选框、它们各自对应的标签、它们坐标回归的目标、以及标定它们正负的坐标掩码,用于精确的训练候选框,生成目标边框。这个一步的操作使用了作者新建的操作节点,具体实现见源码,这里只简单介绍该操作的输入输出以及作用。

    另注,函数返回的候选框5个属性与后面的ROIPooling一脉相承,[候选框对应batch中图片的索引,候选框坐标×4],后面使用这个值可以直接进行ROIPooling。

        # rpn proposal
        # 获取指定数目的候选框proposal
        # [2000个候选框, 5个属性]
        # 5个属性: batch中图片index,4个坐标
        rois = mx.symbol.contrib.MultiProposal(
            cls_prob=rpn_cls_act_reshape,                  # cls_prob: [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
            bbox_pred=rpn_bbox_pred,                       # bbox_pred: [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
            im_info=im_info,                               # mx.symbol.Variable(name="im_info")
            feature_stride=rpn_feature_stride,             # 16
            scales=anchor_scales, ratios=anchor_ratios,    # (0.5, 1, 2)
            rpn_pre_nms_top_n=rpn_pre_topk,                # 12000,对RPN网络输出的anchor进行NMS操作之前的proposal最大数量
            # 输入proposal的数量大于12000时,对这些proposal的预测为foreground的概率(也叫score,
            # 换句话说就是预测标签为1的概率)从高到低排序,然后选择前面12000个
            rpn_post_nms_top_n=rpn_post_topk,              # 2000,经过NMS过滤之后得到的proposal数量
            threshold=rpn_nms_thresh,                      # 0.7,是NMS算法阈值
            rpn_min_size=rpn_min_size,                     # 16
            name = 'rois'
        )
    
        # rcnn roi proposal target
        # 根据上一步获取的proposal(即rois)和真实框(gt_boxes)进行筛选
        # 获取128个proposal,对应的128个label,对应的128个真实坐标,128个坐标掩码(构建损失函数使用)
        group = mx.symbol.Custom(rois=rois,                     # 2000*5的roi信息
                                 gt_boxes=gt_boxes,             # n*5的ground truth信息,n表示object数量
                                 op_type='proposal_target',
                                 num_classes=num_classes,       # num_classes是实际要分类的类别数加上背景类
                                 batch_images=rcnn_batch_size,  # 1
                                 batch_rois=rcnn_batch_rois,    # 128
                                 fg_fraction=rcnn_fg_fraction,  # 0.25,正样本所占的比例
                                 fg_overlap=rcnn_fg_overlap,    # 0.5
                                 box_stds=rcnn_bbox_stds        # (0.1, 0.1, 0.2, 0.2)
                                 )
    
        rois = group[0]              # rois (128, 5)
        label = group[1]             # roi对应的标签(128)
        bbox_target = group[2]       # 坐标回归的目标,维度是(128,84), 其中84来自(20+1)*4
        bbox_weight = group[3]       # 坐标回归时候的权重,维度是(128,84),对于foreground都是1,对于backgroud都是0
    

    在RPN网络得到proposal后还会经过一系列的过滤操作才会得到送入检测网络的proposal,节点proposal_target会将2000个proposal过滤成128个,且为这128个proposal分配标签、回归目标、定义正负样本的1:3比例等,这部分算是RPN网络和检测网络(Fast RCNN)的衔接,值得注意的是该节点不接受后面层数传递而来的梯度,且向前传递梯度强制为0,已即网络训练由其割裂为两个部分(尽管两部分仍然相连)。

    ROIPooling层

    将任意大小的输入层下采样为同样大小的输出层,方便后面的计算。这里我们使用候选框(128个)寻找各自对应的图片,并将各自对应的区域裁剪出来,然后pooling为同样的大小,

    本层的两条虚线输入中上面一条表示batch图像特征,下面一条表示候选框输入

        # rcnn roi pool
        # [128个候选框, 512个通道, 7行, 7列]
        roi_pool = mx.symbol.ROIPooling(
            name='roi_pool',
            data=conv_feat,
            rois=rois,
            pooled_size=rcnn_pooled_size,             # (7, 7),输出大小
            spatial_scale=1.0 / rcnn_feature_stride   # 16, feat/raw_img
        )
    

    将对应的候选区域(注意此时抠图完成,已经不是候选框了)经由vgg的分类头部分(实际上就是对候选区域再进一步抽象提取特征)进行处理,

        # rcnn top feature
        # [128, 4096],对每个roi提取最终的vgg特征
        top_feat = get_vgg_top_feature(roi_pool)
    

    最后的两个损失函数生成过程,对128个候选区域的抽象特征进行分类(此时不是含/不含obj,而是直接进行21分类),并对每一个抽象特征回归出4个坐标值

    两个损失函数标签均来自上面自定义节点的输出:

    label = group[1]                   # roi对应的标签(128)

    bbox_target = group[2]       # 坐标回归的目标,维度是(128,84), 其中84来自(20+1)*4

    bbox_weight = group[3]      # 坐标回归时候的权重,维度是(128,84),对于foreground都是1,对于backgroud都是0

        # rcnn classification
        # 对每个roi进行分类,并构建损失函数
        cls_score = mx.symbol.FullyConnected(name='cls_score', data=top_feat, num_hidden=num_classes)
        cls_prob = mx.symbol.SoftmaxOutput(name='cls_prob', data=cls_score, label=label, normalization='batch')
    
        # rcnn bbox regression
        # 对每个roi进行回归,并构建损失函数
        bbox_pred = mx.symbol.FullyConnected(name='bbox_pred', data=top_feat, num_hidden=num_classes * 4)
        bbox_loss_ = bbox_weight * mx.symbol.smooth_l1(name='bbox_loss_', scalar=1.0, data=(bbox_pred - bbox_target))
        bbox_loss = mx.sym.MakeLoss(name='bbox_loss', data=bbox_loss_, grad_scale=1.0 / rcnn_batch_rois)
    

    附、主体函数全览

    def get_vgg_train(anchor_scales, anchor_ratios, rpn_feature_stride,
                      rpn_pre_topk, rpn_post_topk, rpn_nms_thresh, rpn_min_size, rpn_batch_rois,
                      num_classes, rcnn_feature_stride, rcnn_pooled_size, rcnn_batch_size,
                      rcnn_batch_rois, rcnn_fg_fraction, rcnn_fg_overlap, rcnn_bbox_stds):
        num_anchors = len(anchor_scales) * len(anchor_ratios)
    
        data = mx.symbol.Variable(name="data")                     # 图片信息:[batch, channel, height, width]
        im_info = mx.symbol.Variable(name="im_info")
        gt_boxes = mx.symbol.Variable(name="gt_boxes")             # 真实框信息:[obj数量, 5]
        # anchor标签:-1、0、1 (无效、背景、目标)
        rpn_label = mx.symbol.Variable(name='label')               # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        # 根据初始anchor和ground truth计算出来的offset
        rpn_bbox_target = mx.symbol.Variable(name='bbox_target')  # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
        # mask,如果anchor标签是1,则mask对应值为1,anchor标签是0或-1,则mask对应值为0
        rpn_bbox_weight = mx.symbol.Variable(name='bbox_weight')  # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
    
        # shared convolutional layers
        conv_feat = get_vgg_feature(data)
    
        # RPN layers
        rpn_conv = mx.symbol.Convolution(
            data=conv_feat, kernel=(3, 3), pad=(1, 1), num_filter=512, name="rpn_conv_3x3")
        rpn_relu = mx.symbol.Activation(data=rpn_conv, act_type="relu", name="rpn_relu")
    
        # rpn classification: obj / not_obj
        # [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
        rpn_cls_score = mx.symbol.Convolution(
            data=rpn_relu, kernel=(1, 1), pad=(0, 0), num_filter=2 * num_anchors, name="rpn_cls_score")
    
        # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        rpn_cls_score_reshape = mx.symbol.Reshape(
            data=rpn_cls_score, shape=(0, 2, -1, 0), name="rpn_cls_score_reshape")
        # 获取交叉熵回传梯度,对于出现了-1的图片梯度直接全部置为0
        # 在Faster RCNN算法中,anchor一共有3种标签:-1、0、1,分别表示无效、背景、目标
        rpn_cls_prob = mx.symbol.SoftmaxOutput(data=rpn_cls_score_reshape, label=rpn_label, multi_output=True,
                                               normalization='valid', use_ignore=True, ignore_label=-1, name="rpn_cls_prob")
    
        # 切换为含/不含obj两通道,对每一个anchor是否有obj进行softmax处理
        # [batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
        rpn_cls_act = mx.symbol.softmax(
            data=rpn_cls_score_reshape, axis=1, name="rpn_cls_act")
        # [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
        # Reshpe方法不同于reshape方法,详情见此方法文档
        rpn_cls_act_reshape = mx.symbol.Reshape(
            data=rpn_cls_act, shape=(0, 2 * num_anchors, -1, 0), name='rpn_cls_act_reshape')
    
        # rpn bbox regression
        # [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
        rpn_bbox_pred = mx.symbol.Convolution(
            data=rpn_relu, kernel=(1, 1), pad=(0, 0), num_filter=4 * num_anchors, name="rpn_bbox_pred")
        # rpn_bbox_weight: anchor的mask,如果某个anchor的标签是1,则mask对应值为1,anchor的标签是0或-1,则mask对应值为0
        # rpn_bbox_target: 根据初始anchor和ground truth计算出来的offset
        rpn_bbox_loss_ = rpn_bbox_weight * mx.symbol.smooth_l1(name='rpn_bbox_loss_',
                                                               scalar=3.0,
                                                               data=(rpn_bbox_pred - rpn_bbox_target))
        # 获取回归梯度
        # grad_scale: 表示回归损失占RPN网络总损失比,该参数相差几个量级对结果影响也不大
        rpn_bbox_loss = mx.sym.MakeLoss(name='rpn_bbox_loss', data=rpn_bbox_loss_, grad_scale=1.0 / rpn_batch_rois)
    
        # rpn proposal
        # 获取指定数目的候选框proposal
        # [2000个候选框, 5个属性]
        # 5个属性: batch中图片index,4个坐标
        rois = mx.symbol.contrib.MultiProposal(
            cls_prob=rpn_cls_act_reshape,                  # cls_prob: [batch, 2*每个位置anchors数目, 中心点行数, 中心点列数]
            bbox_pred=rpn_bbox_pred,                       # bbox_pred: [batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
            im_info=im_info,                               # mx.symbol.Variable(name="im_info")
            feature_stride=rpn_feature_stride,             # 16
            scales=anchor_scales, ratios=anchor_ratios,    # (0.5, 1, 2)
            rpn_pre_nms_top_n=rpn_pre_topk,                # 12000,对RPN网络输出的anchor进行NMS操作之前的proposal最大数量
            # 输入proposal的数量大于12000时,对这些proposal的预测为foreground的概率(也叫score,
            # 换句话说就是预测标签为1的概率)从高到低排序,然后选择前面12000个
            rpn_post_nms_top_n=rpn_post_topk,              # 2000,经过NMS过滤之后得到的proposal数量
            threshold=rpn_nms_thresh,                      # 0.7,是NMS算法阈值
            rpn_min_size=rpn_min_size,                     # 16
            name = 'rois'
        )
    
        # rcnn roi proposal target
        # 根据上一步获取的proposal(即rois)和真实框(gt_boxes)进行筛选
        # 获取128个proposal,对应的128个label,对应的128个真实坐标,128个坐标掩码(构建损失函数使用)
        group = mx.symbol.Custom(rois=rois,                     # 2000*5的roi信息
                                 gt_boxes=gt_boxes,             # n*5的ground truth信息,n表示object数量
                                 op_type='proposal_target',
                                 num_classes=num_classes,       # num_classes是实际要分类的类别数加上背景类
                                 batch_images=rcnn_batch_size,  # 1
                                 batch_rois=rcnn_batch_rois,    # 128
                                 fg_fraction=rcnn_fg_fraction,  # 0.25,正样本所占的比例
                                 fg_overlap=rcnn_fg_overlap,    # 0.5
                                 box_stds=rcnn_bbox_stds        # (0.1, 0.1, 0.2, 0.2)
                                 )
    
        rois = group[0]              # rois (128, 5)
        label = group[1]             # roi对应的标签(128)
        bbox_target = group[2]       # 坐标回归的目标,维度是(128,84), 其中84来自(20+1)*4
        bbox_weight = group[3]       # 坐标回归时候的权重,维度是(128,84),对于foreground都是1,对于backgroud都是0
    
        # rcnn roi pool
        # [128个候选框, 512个通道, 7行, 7列]
        roi_pool = mx.symbol.ROIPooling(
            name='roi_pool',
            data=conv_feat,
            rois=rois,
            pooled_size=rcnn_pooled_size,             # (7, 7),输出大小
            spatial_scale=1.0 / rcnn_feature_stride   # 16, feat/raw_img
        )
    
        # rcnn top feature
        # [128, 4096],对每个roi提取最终的vgg特征
        top_feat = get_vgg_top_feature(roi_pool)
    
        # rcnn classification
        # 对每个roi进行分类,并构建损失函数
        cls_score = mx.symbol.FullyConnected(name='cls_score', data=top_feat, num_hidden=num_classes)
        cls_prob = mx.symbol.SoftmaxOutput(name='cls_prob', data=cls_score, label=label, normalization='batch')
    
        # rcnn bbox regression
        # 对每个roi进行回归,并构建损失函数
        bbox_pred = mx.symbol.FullyConnected(name='bbox_pred', data=top_feat, num_hidden=num_classes * 4)
        bbox_loss_ = bbox_weight * mx.symbol.smooth_l1(name='bbox_loss_', scalar=1.0, data=(bbox_pred - bbox_target))
        bbox_loss = mx.sym.MakeLoss(name='bbox_loss', data=bbox_loss_, grad_scale=1.0 / rcnn_batch_rois)
    
        # reshape output
        # [1, 128], [1, 128, 21], [1, 128, 84]
        label = mx.symbol.Reshape(data=label, shape=(rcnn_batch_size, -1), name='label_reshape')
        cls_prob = mx.symbol.Reshape(data=cls_prob, shape=(rcnn_batch_size, -1, num_classes), name='cls_prob_reshape')
        bbox_loss = mx.symbol.Reshape(data=bbox_loss, shape=(rcnn_batch_size, -1, 4 * num_classes), name='bbox_loss_reshape')
        print(cls_score.infer_shape(**{'data': (1, 3, 1000, 600), 'gt_boxes': (5, 5)})[1])
    
        # group output
        # mx.symbol.BlockGrad会阻止经由节点label回传的梯度
        group = mx.symbol.Group([rpn_cls_prob,      # anchors分类损失:[batch, 2, 中心点行数*每个位置anchors数目, 中心点列数]
                                 rpn_bbox_loss,     # anchors回归损失:[batch, 4*每个位置anchors数目, 中心点行数, 中心点列数]
                                 cls_prob,          # 候选框分类损失:[1, 128, 21]
                                 bbox_loss,         # 候选框回归损失:[1, 128, 84]
                                 mx.symbol.BlockGrad(label)  # 候选框标签:[1, 128]
                                 ])
        return group
    
    
    if __name__=='__main__':
        net = get_vgg_train(anchor_scales=(8, 16, 32),
                            anchor_ratios=(0.5, 1, 2),
                            rpn_feature_stride=16,
                            rpn_pre_topk=12000,
                            rpn_post_topk=2000,
                            rpn_nms_thresh=0.7,
                            rpn_min_size=16,
                            rpn_batch_rois=256,
                            num_classes=21,
                            rcnn_feature_stride=16,  # 4个pooling层
                            rcnn_pooled_size=(7, 7),
                            rcnn_batch_size=1,
                            rcnn_batch_rois=128,
                            rcnn_fg_fraction=0.25,
                            rcnn_fg_overlap=0.5,
                            rcnn_bbox_stds=(0.1, 0.1, 0.2, 0.2))
    
  • 相关阅读:
    [LeetCode] 582. Kill Process
    [LeetCode] 686. Repeated String Match
    [LeetCode] 341. Flatten Nested List Iterator
    [LeetCode] 404. Sum of Left Leaves
    [LeetCode] 366. Find Leaves of Binary Tree
    [LeetCode] 1485. Clone Binary Tree With Random Pointer
    [LeetCode] 459. Repeated Substring Pattern
    [LeetCode] 565. Array Nesting
    [LeetCode] 679. 24 Game
    [LeetCode] 364. Nested List Weight Sum II
  • 原文地址:https://www.cnblogs.com/hellcat/p/9678467.html
Copyright © 2011-2022 走看看