『計算機視覺』經典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))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM