之前一段時間一直在搞有關於faster_rcnn的框架的研究,看了許多網上開源的各種各樣的由最原始的py-faster-rcnn而再次實現的代碼,這些代碼讓我加深了對論文的理解,也瞭解到了編程實現中的一些小的技巧。在此做一下記錄,如果對於代碼的理解有偏頗,還請您理解,不吝賜教。
代碼選取:https://github.com/DetectionTeamUCAS/Faster-RCNN_Tensorflow
選取原因:因爲覺得代碼寫的不錯,從數據集的獲取,之後的網絡搭建,訓練,測試等等,網絡的生成pb文件都比較全,模塊化。
感謝這個倉庫的代碼貢獻者,爲我學習目標檢測網絡提供了一些更容易理解的資料。
先放上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的處理,以及訓練的方式,技巧則見後面幾篇文章。