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的處理,以及訓練的方式,技巧則見後面幾篇文章。

 

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