Faster_RCNN 代码的理解之网络结构

之前一段时间一直在搞有关于faster_rcnn的框架的研究,看了许多网上开源的各种各样的由最原始的py-faster-rcnn而再次实现的代码,这些代码让我加深了对论文的理解,也了解到了编程实现中的一些小的技巧。在此做一下记录,如果对于代码的理解有偏颇,还请您理解,不吝赐教。

代码选取:https://github.com/DetectionTeamUCAS/Faster-RCNN_Tensorflow 

选取原因:因为觉得代码写的不错,从数据集的获取,之后的网络搭建,训练,测试等等,网络的生成pb文件都比较全,模块化。

感谢这个仓库的代码贡献者,为我学习目标检测网络提供了一些更容易理解的资料。 

先放上faster rcnn的架构图,方便理解

faster-rcnn架构图

盗图,如侵权,请及时告知删除,第二幅图仅仅让理解更清楚,网络结构与文中的代码不相符。

本文针对于较为核心的一些代码的理解:

基础网络的构建

从train.py 文件开始,推本溯源

作者将整体网络的搭建封装在一个  DetectionNetwork 的类中,通过在开始调用:

faster_rcnn = build_whole_network.DetectionNetwork(base_network_name=cfgs.NET_NAME,
                                                       is_training=True)

并传递网络名称以及是否训练的参数构建基本的网络。之后构建每一个批次数据的输入结构如下,其返回值为每一次训练所需要的图片名称,每一个批次图片的矩阵,图片中的ground truth 以及对应的 label,每一幅图片中所包含的目标数。(其组成结构为[批次数目,相应的批次中每一幅图片的相关信息])  

with tf.name_scope('get_batch'):
        img_name_batch, img_batch, gtboxes_and_label_batch, num_objects_batch = \
            next_batch(dataset_name=cfgs.DATASET_NAME,  # 'pascal', 'coco'
                       batch_size=cfgs.BATCH_SIZE,
                       shortside_len=cfgs.IMG_SHORT_SIDE_LEN,  #get shortside_lens of images
                       is_training=True)
        gtboxes_and_label = tf.reshape(gtboxes_and_label_batch, [-1, 5])

网络的搭建:

with slim.arg_scope([slim.conv2d, slim.conv2d_in_plane, \
                         slim.conv2d_transpose, slim.separable_conv2d, slim.fully_connected],
                        weights_regularizer=weights_regularizer,
                        biases_regularizer=biases_regularizer,
                        biases_initializer=tf.constant_initializer(0.0)):
        #here different layers have same initializion
        final_bbox, final_scores, final_category, loss_dict = faster_rcnn.build_whole_detection_network(
            input_img_batch=img_batch,
            gtboxes_batch=gtboxes_and_label)

网络整体的构建采用了tf.slim 库,刚开始我不太喜欢用tf.slim 一直采用的 tensorlayer等更为高级一些的库,无奈很多开源代码采用tf.slim 因此也不得不学习一些。这里采用 slim.arg_scope([list], **kargs)  则表示在list中的所有元素都具有**kargs 的参数设置。这样子简化了更深层次的网络搭建,比较模块化。

网络的实例化则通过调用 faster_rcnn 实例的方法: build_whole_detection_network 的方法构建整体网络架构。整体的网络包含了特征提取网络,RPN网络,Pooling层,以及后续网络,其返回值为网络的最后的预测框,预测的类别信息,预测的概率,以及整体网络和RPN网络的损失,所有的损失被写入到一个字典中。之后的几个小节针对于核心的类做一下注解

DetectionNetwork 类

DetectionNetwork 类位于build_whole_network.py 文件中,其中的 build_whole_detection_network 函数最为关键。

其输入为批次图像,以及相关批次的ground truth。 网络首先获得批次数据的基本信息,图像的shape 等。

feature_to_cropped = self.build_base_network(input_img_batch)

1) 首先建立基础特征提取网络。

def build_base_network(self, input_img_batch):

        if self.base_network_name.startswith('resnet_v1'):
            return resnet.resnet_base(input_img_batch, scope_name=self.base_network_name, is_training=self.is_training)

        elif self.base_network_name.startswith('MobilenetV2'):
            return mobilenet_v2.mobilenetv2_base(input_img_batch, is_training=self.is_training)

        else:
            raise ValueError('Sry, we only support resnet or mobilenet_v2')

作者当前只针对 resnet_v1 和 MobileNetV2做了实现,因此只支持这两种网络。 

在resnet.py  文件中,定义了resenet_base  网络以及resnet_head  网络,一个作为基础的特征提取网络,另一个则作为RoI Pooling后的检测,分类顶层网络。

在建立base网络时,根据网络定义 not_freezed 确定,是否对特征提取网络进行再训练

not_freezed = [False] * cfgs.FIXED_BLOCKS + (4-cfgs.FIXED_BLOCKS)*[True]

RPN网络的构建

with tf.variable_scope('build_rpn',regularizer=slim.l2_regularizer(cfgs.WEIGHT_DECAY)):

    rpn_conv3x3 = slim.conv2d(
        feature_to_cropped, 512, [3, 3],
        trainable=self.is_training, weights_initializer=cfgs.INITIALIZER,
        activation_fn=tf.nn.relu,
        scope='rpn_conv/3x3')
    rpn_cls_score = slim.conv2d(rpn_conv3x3, self.num_anchors_per_location*2, [1, 1],stride=1,
                                trainable=self.is_training, weights_initializer=cfgs.INITIALIZER,
                                activation_fn=None,
                                scope='rpn_cls_score')
   
    rpn_box_pred = slim.conv2d(rpn_conv3x3, self.num_anchors_per_location*4, [1, 1], stride=1,
                               trainable=self.is_training, weights_initializer=cfgs.BBOX_INITIALIZER,
                               activation_fn=None,
                               scope='rpn_bbox_pred')
    rpn_box_pred = tf.reshape(rpn_box_pred, [-1, 4])
    rpn_cls_score = tf.reshape(rpn_cls_score, [-1, 2])
    rpn_cls_prob = slim.softmax(rpn_cls_score, scope='rpn_cls_prob')

建立RPN网络,可以看出,这是一个三层的卷积网络,第一层网络采用一个3X3的卷积核在特征图上滑动,生成一个高层的特征图,第二层在高级的特征图上,通过一个 1x1的卷积核,stride = 1 进行滑动,每一处的输出维度为锚点数*2 ,输出维度与rpn_conv3x3 的相同,第三层卷积网路为在第二层卷积特征图上用 1x1的卷积核 stride 为1 进行卷积,卷积核的深度为锚点数*4。根据原始faster-rcnn论文,这里第二层卷积输出为当前位置是否含有目标,第三层卷积输出为框回归座标,第二层卷积核通过softmax函数归一化处理。

3 ) 产生Anchors 

featuremap_height, featuremap_width = tf.shape(feature_to_cropped)[1], tf.shape(feature_to_cropped)[2]
featuremap_height = tf.cast(featuremap_height, tf.float32)
featuremap_width = tf.cast(featuremap_width, tf.float32)

anchors = anchor_utils.make_anchors(base_anchor_size=cfgs.BASE_ANCHOR_SIZE_LIST[0],
                                    anchor_scales=cfgs.ANCHOR_SCALES, anchor_ratios=cfgs.ANCHOR_RATIOS,
                                    featuremap_height=featuremap_height,
                                    featuremap_width=featuremap_width,
                                    stride=cfgs.ANCHOR_STRIDE,
                                    name="make_anchors_forRPN")

这一块则是在resnet_base 网络所获得的特征图上根据scales以及ratios产生Anchors

采用了make_anchors 函数具体解析如下: 

def make_anchors(base_anchor_size, anchor_scales, anchor_ratios,
                 featuremap_height, featuremap_width,
                 stride, name='make_anchors'):
    '''
    :param base_anchor_size:256
    :param anchor_scales:
    :param anchor_ratios:
    :param featuremap_height:
    :param featuremap_width:
    :param stride:
    :return:
    '''
    with tf.variable_scope(name):
        base_anchor = tf.constant([0, 0, base_anchor_size, base_anchor_size], tf.float32)  # [x_center, y_center, w, h]

        ws, hs = enum_ratios(enum_scales(base_anchor, anchor_scales),
                             anchor_ratios)  # per locations ws and hs


        #this will get the center of anothers
        # it consisant in feature map however inconstant in raw image
        x_centers = tf.range(featuremap_width, dtype=tf.float32) * stride
        y_centers = tf.range(featuremap_height, dtype=tf.float32) * stride

        x_centers, y_centers = tf.meshgrid(x_centers, y_centers)

        ws, x_centers = tf.meshgrid(ws, x_centers)
        hs, y_centers = tf.meshgrid(hs, y_centers)

        anchor_centers = tf.stack([x_centers, y_centers], 2)
        anchor_centers = tf.reshape(anchor_centers, [-1, 2])

        box_sizes = tf.stack([ws, hs], axis=2)
        box_sizes = tf.reshape(box_sizes, [-1, 2])
        # anchors = tf.concat([anchor_centers, box_sizes], axis=1)
        anchors = tf.concat([anchor_centers - 0.5*box_sizes,
                             anchor_centers + 0.5*box_sizes], axis=1)
        return anchors

在这个函数中,首先针对原始的Anchor大小做变换, 原始的Anchor为 宽和高都为256像素,根据变换的scale 以及 ratio 可以生成九个大小不同的框,具体生成框的代码分析如下

ws, hs = enum_ratios(enum_scales(base_anchor, anchor_scales),anchor_ratios) 

#以下为具体函数实现
def enum_scales(base_anchor, anchor_scales):

    anchor_scales = base_anchor * tf.constant(anchor_scales, dtype=tf.float32, shape=(len(anchor_scales), 1))

    return anchor_scales

def enum_ratios(anchors, anchor_ratios):
    '''
    ratio = h /w
    :param anchors:
    :param anchor_ratios:
    :return:
    '''
    ws = anchors[:, 2]  # for base anchor: w == h
    hs = anchors[:, 3]
    sqrt_ratios = tf.sqrt(tf.constant(anchor_ratios))

    ws = tf.reshape(ws / sqrt_ratios[:, tf.newaxis], [-1, 1])
    hs = tf.reshape(hs * sqrt_ratios[:, tf.newaxis], [-1, 1])

    return hs, ws

这样子经过两层枚举,就可以得到九种大小不同的hs和ws。经过乘以stride后所得到的x_center 与 y_center ,则为具体在原始图像上的锚点中心,经过,anchors = tf.concat([anchor_centers - 0.5*box_sizes, anchor_centers + 0.5*box_sizes], axis=1),则可以得到每一个锚点对应的九种不同anchor 的座标值分别为,(左下角,右上角)。 

4) 对于RPN 进行预处理,编码,切片,非极大值抑制(NMS)

此时生成的anchor是没有经过任何处理的,因此需要对其进行处理,减小处理的复杂度

rois, roi_scores = postprocess_rpn_proposals(rpn_bbox_pred=rpn_box_pred,
                                             rpn_cls_prob=rpn_cls_prob,
                                             img_shape=img_shape,
                                             anchors=anchors,
                                             is_training=self.is_training)

这个函数接受RPN网络的预测框位置,以及预测的类别(两类),图像的尺寸大小,以及生成的锚点作为输入。

decode_boxes = encode_and_decode.decode_boxes(encoded_boxes=rpn_bbox_pred,
                                                  reference_boxes=anchors,                                                  scale_factors=cfgs.ANCHOR_SCALE_FACTORS)
    
    

def decode_boxes(encoded_boxes, reference_boxes, scale_factors=None):
    '''

    :param encoded_boxes:[N, 4]
    :param reference_boxes: [N, 4] .
    :param scale_factors: use for scale.

    in the first stage, reference_boxes  are anchors
    in the second stage, reference boxes are proposals(decode) produced by first stage
    :return:decode boxes [N, 4]
    '''

    t_xcenter, t_ycenter, t_w, t_h = tf.unstack(encoded_boxes, axis=1)
    if scale_factors:
        t_xcenter /= scale_factors[0]
        t_ycenter /= scale_factors[1]
        t_w /= scale_factors[2]
        t_h /= scale_factors[3]

    reference_xmin, reference_ymin, reference_xmax, reference_ymax = tf.unstack(reference_boxes, axis=1)
    # reference boxes are anchors in the first stage

    # reference_xcenter = (reference_xmin + reference_xmax) / 2.
    # reference_ycenter = (reference_ymin + reference_ymax) / 2.
    reference_w = reference_xmax - reference_xmin
    reference_h = reference_ymax - reference_ymin
    reference_xcenter = reference_xmin + reference_w/2.0
    reference_ycenter = reference_ymin + reference_h/2.0

    predict_xcenter = t_xcenter * reference_w + reference_xcenter
    predict_ycenter = t_ycenter * reference_h + reference_ycenter
    predict_w = tf.exp(t_w) * reference_w
    predict_h = tf.exp(t_h) * reference_h

    predict_xmin = predict_xcenter - predict_w / 2.
    predict_xmax = predict_xcenter + predict_w / 2.
    predict_ymin = predict_ycenter - predict_h / 2.
    predict_ymax = predict_ycenter + predict_h / 2.

    return tf.transpose(tf.stack([predict_xmin, predict_ymin,
                                  predict_xmax, predict_ymax]))

 这段代码中,建立一个参考的框与预测的框中心座标之间的线性关系,以及预测的框与参考的框宽高之间的对数关系,最后得到预测的框。这里采用的就是原始的论文里面所采用的那个因子,t_xcenter,t_ycenter,t_w,t_h, 网络就是要不断的学习这些因子,让预测框更加准确。

得出一个初步的框之后,然后先得到,没有进行非极大值抑制之前的前TopK 个 是前景的框,之后再进行极大值抑制。


    decode_boxes = boxes_utils.clip_boxes_to_img_boundaries(decode_boxes=decode_boxes,
                                                            img_shape=img_shape)

    if pre_nms_topN > 0:
        pre_nms_topN = tf.minimum(pre_nms_topN, tf.shape(decode_boxes)[0], name='avoid_unenough_boxes')
        cls_prob, top_k_indices = tf.nn.top_k(cls_prob, k=pre_nms_topN)
        decode_boxes = tf.gather(decode_boxes, top_k_indices)

    keep = tf.image.non_max_suppression(
        boxes=decode_boxes,
        scores=cls_prob,
        max_output_size=post_nms_topN,
        iou_threshold=nms_thresh)

    final_boxes = tf.gather(decode_boxes, keep)
    final_probs = tf.gather(cls_prob, keep)

    return final_boxes, final_probs

经过解码后,得到的是真实的预测框的位置,因为有可能预测的框比设定的选取前N个框的个数还小,因此在预测框的数目以及设定的数目之间取最小值,之后再采用 tf.image.non_max_suppression 抑制,选取最终的非极大值抑制后的Top K  个框,原论文中 未采用NMS之前为12000个,NMS后为2000个。这里还没有具体的分类那个框是那个目标,只是选出了前K个可能存在目标的框。

在模型训练过程中,需要对RPN网络不断的进行训练,因此以下则是在训练中针对于anchor产生层的训练设计方法:从所有的anchor中取一部分,计算其与ground truth之间的准确性

计算之前得出的Anchors与ground truth的重叠率

rpn_labels, rpn_bbox_targets = \
                    tf.py_func(
                        anchor_target_layer,
                        [gtboxes_batch, img_shape, anchors],
                        [tf.float32, tf.float32])



以下为这个函数的具体内部调用
首先将所有的label都定义为 -1 认为不考虑,其label长度为在图像内部的Anchor的数目值
    labels = np.empty((len(inds_inside),), dtype=np.float32)
    labels.fill(-1)


    #Here is the key code in faster rcnn
    # overlaps between the anchors and the gt boxes
    overlaps = bbox_overlaps(
        np.ascontiguousarray(anchors, dtype=np.float),
        np.ascontiguousarray(gt_boxes, dtype=np.float))


计算每一行的重叠率最大的值所在的索引,行数则为在图像大小范围内的所有Anchors数目(每一个Anchor与哪一个ground truth 框重叠最大)
    argmax_overlaps = overlaps.argmax(axis=1)

取出与相关的Anchors重叠最大的ground truth的那个值
    max_overlaps = overlaps[np.arange(len(inds_inside)), argmax_overlaps]

计算出每一列的最大值的索引,一共有ground truth 目标数目个列(每一个ground truth与哪一个Anchor重叠最大)
    gt_argmax_overlaps = overlaps.argmax(axis=0)
取出与ground truth最大重叠的Anchor 的重叠率的数值
    gt_max_overlaps = overlaps[
        gt_argmax_overlaps, np.arange(overlaps.shape[1])]


    gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0]

如果每一个最大重叠框与其最大的ground truth框的重叠率小于RPN_IOU_NEG 的重叠率,则这个框的label为背景
    if not cfgs.TRAIN_RPN_CLOOBER_POSITIVES:
        labels[max_overlaps < cfgs.RPN_IOU_NEGATIVE_THRESHOLD] = 0

如果每一个ground truth框对应的anchor的重叠率大于RPN_IOU_POS 的重叠率,则这个框的label为目标
    labels[gt_argmax_overlaps] = 1
如果每一个anchor对应的最大重叠框的重叠率大于RPN_POS的重叠率阈值,则也认为其为目标
    labels[max_overlaps >= cfgs.RPN_IOU_POSITIVE_THRESHOLD] = 1

    if cfgs.TRAIN_RPN_CLOOBER_POSITIVES:
        labels[max_overlaps < cfgs.RPN_IOU_NEGATIVE_THRESHOLD] = 0

预先设定的前景的目标数目 
    num_fg = int(cfgs.RPN_MINIBATCH_SIZE * cfgs.RPN_POSITIVE_RATE)
    fg_inds = np.where(labels == 1)[0] 所有label为1的包含目标的点
    if len(fg_inds) > num_fg:
        disable_inds = npr.choice(
            fg_inds, size=(len(fg_inds) - num_fg), replace=False)
        labels[disable_inds] = -1
如果label等于目标的数目大于所预先设定的目标数目的值,就随机的将部分label设定为-1,不参与计算
    num_bg = cfgs.RPN_MINIBATCH_SIZE - np.sum(labels == 1)
    if is_restrict_bg:
        num_bg = max(num_bg, num_fg * 1.5)
    bg_inds = np.where(labels == 0)[0] 认为所有label为0的为背景
    if len(bg_inds) > num_bg:
        disable_inds = npr.choice(
            bg_inds, size=(len(bg_inds) - num_bg), replace=False)
        labels[disable_inds] = -1

如果背景的label数目大于所设定的背景数目,则将部分的背景标签设置为-1,不参与计算。如果小于,则不做任何改变,保留所有背景的相关标签为0 

这一段代码主要是根据重叠率来得出那些anchor包含目标,那些anchor认为是背景,哪些anchor不参与计算,之后则需要将所对应的Anchors进行编码,转变为在特征图上的映射,具体的代码如下

这一块输入的参数为所有的Anchors以及与每一个anchor对应的重叠率最大的那个ground truth目标框所对应的座标
bbox_targets = _compute_targets(anchors, gt_boxes[argmax_overlaps, :])
其返回值为每一个在图像内的anchor与其对应的具有最大重叠率的ground truth框之间的映射关系,也就是对其进行编码的过程


因为一直在计算中都是针对于所有在图像内的框进行运算,并没有考虑到在图像外的框,但是在最终的计算中,针对的是所有的anchor,因此需要将处理过的与原始的进行融合

labels = _unmap(labels, total_anchors, inds_inside, fill=-1)
bbox_targets = _unmap(bbox_targets, total_anchors, inds_inside, fill=0)

# labels = labels.reshape((1, height, width, A))
rpn_labels = labels.reshape((-1, 1))


bbox_targets = bbox_targets.reshape((-1, 4))
rpn_bbox_targets = bbox_targets

最后返回的为编码后的label,以及映射因子矩阵
return rpn_labels, rpn_bbox_targets

计算RPN分类的准确度,这里主要针对的是RPN网络是否预测出了尽可能多正确的背景框以及含有目标的框,这里不考虑那些label为-1 的框,只考虑 label为0 或者label为1的框,判断其准确度

rpn_cls_category = tf.argmax(rpn_cls_prob, axis=1)
            kept_rpppn = tf.reshape(tf.where(tf.not_equal(rpn_labels, -1)), [-1])
            rpn_cls_category = tf.gather(rpn_cls_category, kept_rpppn)
            acc = tf.reduce_mean(tf.to_float(tf.equal(rpn_cls_category, tf.to_int64(tf.gather(rpn_labels, kept_rpppn)))))

刚才的设计都是针对于RPN网络的,并没有设计到真正的类别信息,以下则采用RCNN部分获得其相关的roi,target等信息句

 with tf.variable_scope('sample_RCNN_minibatch'):
                    rois, labels, bbox_targets = \
                    tf.py_func(proposal_target_layer,
                    [rois, gtboxes_batch],
                    [tf.float32, tf.float32, tf.float32])

其内部函数调用为:

 

其函数的输入为所有的由RPN所产生的RoIs,以及所有的gt_boxs,每一幅图需要产生的目标roi数目,没幅图的roi,真实的类别数目)

def _sample_rois(all_rois, gt_boxes, fg_rois_per_image,
                 rois_per_image, num_classes):
    """Generate a random sample of RoIs comprising foreground and background
    examples.

    all_rois shape is [-1, 4]
    gt_boxes shape is [-1, 5]. that is [x1, y1, x2, y2, label]
    """
    # overlaps: (rois x gt_boxes)

计算所有的RPN产生的ROI与所有的ground truth的目标框的重叠率
    overlaps = bbox_overlaps(
        np.ascontiguousarray(all_rois, dtype=np.float),
        np.ascontiguousarray(gt_boxes[:, :-1], dtype=np.float))

得到与每一个roi最大重叠的gt_box 的框的索引 以及 重叠率
    gt_assignment = overlaps.argmax(axis=1)
    max_overlaps = overlaps.max(axis=1)
获得相对应的类别标签
    labels = gt_boxes[gt_assignment, -1]

    # Select foreground RoIs as those with >= FG_THRESH overlap
    fg_inds = np.where(max_overlaps >= cfgs.FAST_RCNN_IOU_POSITIVE_THRESHOLD)[0]

    # Guard against the case when an image has fewer than fg_rois_per_image
    # Select background RoIs as those within [BG_THRESH_LO, BG_THRESH_HI)
    bg_inds = np.where((max_overlaps < cfgs.FAST_RCNN_IOU_POSITIVE_THRESHOLD) &
                       (max_overlaps >= cfgs.FAST_RCNN_IOU_NEGATIVE_THRESHOLD))[0]
    # print("first fileter, fg_size: {} || bg_size: {}".format(fg_inds.shape, bg_inds.shape))

    # Guard against the case when an image has fewer than fg_rois_per_image
    # foreground RoIs
    fg_rois_per_this_image = min(fg_rois_per_image, fg_inds.size)
    以最小的 fg_size 作为fg_rois_per_this_image 

    # Sample foreground regions without replacement
    if fg_inds.size > 0:   如果有目标
        fg_inds = npr.choice(fg_inds, size=int(fg_rois_per_this_image), replace=False) 

    # Compute number of background RoIs to take from this image (guarding
    # against there being fewer than desired)
    bg_rois_per_this_image = rois_per_image - fg_rois_per_this_image
    bg_rois_per_this_image = min(bg_rois_per_this_image, bg_inds.size)
    # Sample background regions without replacement
    if bg_inds.size > 0:
        bg_inds = npr.choice(bg_inds, size=int(bg_rois_per_this_image), replace=False)

    # print("second fileter, fg_size: {} || bg_size: {}".format(fg_inds.shape, bg_inds.shape))

    # The indices that we're selecting (both fg and bg)
    keep_inds = np.append(fg_inds, bg_inds) 
选择出来的fg以及bg是在相关的阈值基础上得到的,bg的选取有一个最低的阈值


    # Select sampled values from various arrays:
    labels = labels[keep_inds]

    # Clamp labels for the background RoIs to 0
    labels[int(fg_rois_per_this_image):] = 0
    rois = all_rois[keep_inds]
    
计算bbox目标数据,输入都是对应的keep_inds所对应的roi,gt_box,labels
    bbox_target_data = _compute_targets(
        rois, gt_boxes[gt_assignment[keep_inds], :-1], labels)
其返回值为 roi与gt_box 之间映射的因子矩阵以及对应的类别信息,下面的函数将为每一个非background的类写入相关的四个座标因此t,这里,由于num_classes是从tf-record 中直接得到的,因此类数量是包含background的,因此比真实的要多出一类
    bbox_targets = \
        _get_bbox_regression_labels(bbox_target_data, num_classes)

    return labels, rois, bbox_targets
返回值后期计算的labels(这里为具体的类),rois为要保留的roi,bbox_targets 为每一个具体的类(一共的NUM_CLASS个类,每一个类对应四个座标点)对应的座标映射矩阵

后面则是之前的建立Fast_RCNN网络,建立后需要将每一个RoI进行Pooling,获得Pooling特征图,在在用后端网络进行处理。

针对于每一个RoI进行Pooling的过程如下:

def roi_pooling(self, feature_maps, rois, img_shape):
    '''
    Here use roi warping as roi_pooling

    :param featuremaps_dict: feature map to crop
    :param rois: shape is [-1, 4]. [x1, y1, x2, y2]
    :return:
    '''

    with tf.variable_scope('ROI_Warping'):
        img_h, img_w = tf.cast(img_shape[1], tf.float32), tf.cast(img_shape[2], tf.float32)

        N = tf.shape(rois)[0]
        x1, y1, x2, y2 = tf.unstack(rois, axis=1)

        normalized_x1 = x1 / img_w
        normalized_x2 = x2 / img_w
        normalized_y1 = y1 / img_h
        normalized_y2 = y2 / img_h

获得一个正则化的roi范围
        normalized_rois = tf.transpose(tf.stack([normalized_y1, normalized_x1, normalized_y2, normalized_x2]), name='get_normalized_rois')
        normalized_rois = tf.stop_gradient(normalized_rois)
        #这一块还不太理解,是对正则化的roi进行梯度裁剪吗?

        cropped_roi_features = tf.image.crop_and_resize(feature_maps,normalized_rois,box_ind=tf.zeros(shape=[N, ],                                                dtype=tf.int32),crop_size=[cfgs.ROI_SIZE, cfgs.ROI_SIZE],name='CROP_AND_RESIZE')
在特征图上获得与原始的图像想对应的特征图切片,并将特征图切片resize为规整的大小,以便于后期的特征图池化
roi_features = slim.max_pool2d(cropped_roi_features,[cfgs.ROI_POOL_KERNEL_SIZE, cfgs.ROI_POOL_KERNEL_SIZE],stride=cfgs.ROI_POOL_KERNEL_SIZE)

    return roi_features
返回池化后的特征图

 之后则是对特征图通过ResNet前向传播,并通过一个两个全连接网络,一个全连接网络负责做分类,另一个全连接输出的回归的座标信息。

顶层网络构建:

顶层的网络线通过的了一个ResNet的残差网络,最后将残差网络的输出分别接上两FC网络作为分类和回归的输出,此时回归的输出依然为映射因子,后期需要对其进行decode才能转变为正常的在图像中的区域。一下为两层FC网络。

with slim.arg_scope([slim.fully_connected], weights_regularizer=slim.l2_regularizer(cfgs.WEIGHT_DECAY)):
    
    分类值
    cls_score = slim.fully_connected(fc_flatten,
                                     num_outputs=cfgs.CLASS_NUM+1,
                                     weights_initializer=slim.variance_scaling_initializer(factor=1.0,
                                                                                           mode='FAN_AVG',
                                                                                           uniform=True),
                                     activation_fn=None, trainable=self.is_training,
                                     scope='cls_fc')
    预测目标框值,输出为类数目*4
    bbox_pred = slim.fully_connected(fc_flatten,
                                     num_outputs=(cfgs.CLASS_NUM+1)*4,
                                     weights_initializer=slim.variance_scaling_initializer(factor=1.0,
                                                                                           mode='FAN_AVG',
                                                                                           uniform=True),
                                     activation_fn=None, trainable=self.is_training,
                                     scope='reg_fc')
    # for convient. It also produce (cls_num +1) bboxes

    cls_score = tf.reshape(cls_score, [-1, cfgs.CLASS_NUM+1])
    bbox_pred = tf.reshape(bbox_pred, [-1, 4*(cfgs.CLASS_NUM+1)])

如果不进行训练,则此时直接进行解码就可以得到最终的bbox,每一个对应的分值,以及相关的box对应的类别信息。

final_bbox, final_scores, final_category = self.postprocess_fastrcnn(rois=rois,bbox_ppred=bbox_pred,scores=cls_prob,img_shape=img_shape)

如果需要进行训练,则我们还需要计算出loss,进而采用优化方法来降低loss,此讲则主要针对网络进行学习,关于loss的处理,以及训练的方式,技巧则见后面几篇文章。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章