一、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))