目錄
1.背景:
論文地址:
2.結構圖:
3.相關代碼:(caffe框架)
訓練代碼:
整體python代碼實作:
1.背景:
由于全連接的引數量過大,現在越來越多的網路開始去掉全連接,R-FCN就是一個很好的例子!而且個人認為全卷積應該是未來!
Faster RCNN在RoI Pooling后采用了全連接網路來得到分類與回歸的預測,這部分全連接網路占據了整個網路結構的大部分引數,而目前越來越多的全卷積網路證明了不使用全連接網路效果會更好,以適應各種輸入尺度的圖片, 一個很自然的想法就是去掉RoI Pooling后的全連接,直接連接到分類與回歸的網路中,但通過實驗發現這種方法檢測的效果很差,其中一個原因就是基礎的卷積網路是針對分類設計的,具有平移不變性,對位置不敏感,而物體檢測則對位置敏感, 針對上述“痛點”,微軟亞洲研究院的代季峰團隊提出了R-FCN(Region-based Fully Convolutional Networks)演算法,利用一個精心設計的位置敏感得分圖(position-sensitive score maps)實作了對位置的敏感,并且采用了全卷積網路,大大減少了網路的引數量,
論文地址:
R-FCN: Object Detection via Region-based Fully Convolutional Networks (neurips.cc)
2.結構圖:


較為具體的結構:

如圖所示為R-FCN的網路結構圖,首先R-FCN采用了ResNet-101網路作為Backbone,并在原始的100個卷積層后增加了一個1×1卷積,將通道數降低為1024, 此外,為了增大后續特征圖的尺寸,R-FCN將ResNet-101的下采樣率從32降到了16,具體做法是,在第5個卷積組里將卷積的步長從2變為1,同時在這個階段的卷積使用空洞數為2的空洞卷積以擴大感受野,降低步長增加空洞卷積是一種常用的方法,可以在保持特征圖尺寸的同時,增大感受野, 在特征圖上進行1×1卷積,可以得到位置敏感得分圖,其通道數為k2(c+1),這里的c代表物體類別,一般需要再加上背景這一類別,k的含義是將RoI劃分為k2個區域,如下圖分別展示了k為1、3、5的情況,

例如當k=3時,可以將RoI分為左上、中上、右上等9個區域,每個區域對特征區域的資訊敏感,因此,位置敏感得分圖的通道包含了所有9個區域內所有類別的資訊, 對于一個位置敏感得分圖上的點,假設其坐標為m×n,通道在右上區域,類別為人,則該點表示當前位置屬于人并且在人這個“物體”的右上區域的特征,因此這樣就包含了位置資訊, 在RPN提供了一個感興趣區域后,對應到位置敏感得分圖上,首先將RoI劃分為k×k個網格,如圖所示:

左側為將9個不同區域展開后的RoI特征,9個區域分別對應著不同的位置,在Pooling時首先選取其所在區域的對應位置的特征,例如左上區域只選取其左上角的特征,右下區域只選取右下角的特征,選取后對區域內求均值,最終可形成右側的一個c+1維的k×k特征圖, 接下來再對這個c+1維的k×k特征進行逐通道求和,即可得到c+1維的向量,最后進行Softmax即可完成這個RoI的分類預測, 至于RoI的位置回歸,則與分類很相似,只不過位置敏感得分圖的通道數為k2(c+1),而回歸的敏感回歸圖的通道數為k2×4,按照相同的方法進行Pooling,可形成通道數為4的k×k特征,求和可得到1×4的向量,即為回歸的預測, 由于R-FCN去掉了全連接層,并且整個網路都是共享計算的,因此速度很快,此外,由于位置敏感得分圖的存在,引入了位置資訊,因此R-FCN的檢測效果也更好,
3.相關代碼:(caffe框架)
訓練代碼:
import caffe
from fast_rcnn.config import cfg
import roi_data_layer.roidb as rdl_roidb
from utils.timer import Timer
import numpy as np
import os
?
from caffe.proto import caffe_pb2
import google.protobuf as pb2
?
class SolverWrapper(object):
"""A simple wrapper around Caffe's solver.
This wrapper gives us control over he snapshotting process, which we
use to unnormalize the learned bounding-box regression weights.
"""
?
def __init__(self, solver_prototxt, roidb, output_dir,
pretrained_model=None):
"""Initialize the SolverWrapper."""
self.output_dir = output_dir
?
if (cfg.TRAIN.HAS_RPN and cfg.TRAIN.BBOX_REG and
cfg.TRAIN.BBOX_NORMALIZE_TARGETS):
# RPN can only use precomputed normalization because there are no
# fixed statistics to compute a priori
assert cfg.TRAIN.BBOX_NORMALIZE_TARGETS_PRECOMPUTED
?
if cfg.TRAIN.BBOX_REG:
print 'Computing bounding-box regression targets...'
self.bbox_means, self.bbox_stds = \
rdl_roidb.add_bbox_regression_targets(roidb)
print 'done'
?
self.solver = caffe.SGDSolver(solver_prototxt)
if pretrained_model is not None:
print ('Loading pretrained model '
'weights from {:s}').format(pretrained_model)
self.solver.net.copy_from(pretrained_model)
?
self.solver_param = caffe_pb2.SolverParameter()
with open(solver_prototxt, 'rt') as f:
pb2.text_format.Merge(f.read(), self.solver_param)
?
self.solver.net.layers[0].set_roidb(roidb)
?
def snapshot(self):
"""Take a snapshot of the network after unnormalizing the learned
bounding-box regression weights. This enables easy use at test-time.
"""
net = self.solver.net
?
scale_bbox_params_faster_rcnn = (cfg.TRAIN.BBOX_REG and
cfg.TRAIN.BBOX_NORMALIZE_TARGETS and
net.params.has_key('bbox_pred'))
?
scale_bbox_params_rfcn = (cfg.TRAIN.BBOX_REG and
cfg.TRAIN.BBOX_NORMALIZE_TARGETS and
net.params.has_key('rfcn_bbox'))
?
scale_bbox_params_rpn = (cfg.TRAIN.RPN_NORMALIZE_TARGETS and
net.params.has_key('rpn_bbox_pred'))
?
if scale_bbox_params_faster_rcnn:
# save original values
orig_0 = net.params['bbox_pred'][0].data.copy()
orig_1 = net.params['bbox_pred'][1].data.copy()
?
# scale and shift with bbox reg unnormalization; then save snapshot
net.params['bbox_pred'][0].data[...] = \
(net.params['bbox_pred'][0].data *
self.bbox_stds[:, np.newaxis])
net.params['bbox_pred'][1].data[...] = \
(net.params['bbox_pred'][1].data *
self.bbox_stds + self.bbox_means)
?
if scale_bbox_params_rpn:
rpn_orig_0 = net.params['rpn_bbox_pred'][0].data.copy()
rpn_orig_1 = net.params['rpn_bbox_pred'][1].data.copy()
num_anchor = rpn_orig_0.shape[0] / 4
# scale and shift with bbox reg unnormalization; then save snapshot
self.rpn_means = np.tile(np.asarray(cfg.TRAIN.RPN_NORMALIZE_MEANS),
num_anchor)
self.rpn_stds = np.tile(np.asarray(cfg.TRAIN.RPN_NORMALIZE_STDS),
num_anchor)
net.params['rpn_bbox_pred'][0].data[...] = \
(net.params['rpn_bbox_pred'][0].data *
self.rpn_stds[:, np.newaxis, np.newaxis, np.newaxis])
net.params['rpn_bbox_pred'][1].data[...] = \
(net.params['rpn_bbox_pred'][1].data *
self.rpn_stds + self.rpn_means)
?
if scale_bbox_params_rfcn:
# save original values
orig_0 = net.params['rfcn_bbox'][0].data.copy()
orig_1 = net.params['rfcn_bbox'][1].data.copy()
repeat = orig_1.shape[0] / self.bbox_means.shape[0]
?
# scale and shift with bbox reg unnormalization; then save snapshot
net.params['rfcn_bbox'][0].data[...] = \
(net.params['rfcn_bbox'][0].data *
np.repeat(self.bbox_stds, repeat).reshape((orig_1.shape[0], 1, 1, 1)))
net.params['rfcn_bbox'][1].data[...] = \
(net.params['rfcn_bbox'][1].data *
np.repeat(self.bbox_stds, repeat) + np.repeat(self.bbox_means, repeat))
?
infix = ('_' + cfg.TRAIN.SNAPSHOT_INFIX
if cfg.TRAIN.SNAPSHOT_INFIX != '' else '')
filename = (self.solver_param.snapshot_prefix + infix +
'_iter_{:d}'.format(self.solver.iter) + '.caffemodel')
filename = os.path.join(self.output_dir, filename)
net.save(str(filename))
print 'Wrote snapshot to: {:s}'.format(filename)
?
if scale_bbox_params_faster_rcnn:
# restore net to original state
net.params['bbox_pred'][0].data[...] = orig_0
net.params['bbox_pred'][1].data[...] = orig_1
if scale_bbox_params_rfcn:
# restore net to original state
net.params['rfcn_bbox'][0].data[...] = orig_0
net.params['rfcn_bbox'][1].data[...] = orig_1
if scale_bbox_params_rpn:
# restore net to original state
net.params['rpn_bbox_pred'][0].data[...] = rpn_orig_0
net.params['rpn_bbox_pred'][1].data[...] = rpn_orig_1
?
return filename
?
def train_model(self, max_iters):
"""Network training loop."""
last_snapshot_iter = -1
timer = Timer()
model_paths = []
while self.solver.iter < max_iters:
# Make one SGD update
timer.tic()
self.solver.step(1)
timer.toc()
if self.solver.iter % (10 * self.solver_param.display) == 0:
print 'speed: {:.3f}s / iter'.format(timer.average_time)
?
if self.solver.iter % cfg.TRAIN.SNAPSHOT_ITERS == 0:
last_snapshot_iter = self.solver.iter
model_paths.append(self.snapshot())
?
if last_snapshot_iter != self.solver.iter:
model_paths.append(self.snapshot())
return model_paths
?
def get_training_roidb(imdb):
"""Returns a roidb (Region of Interest database) for use in training."""
if cfg.TRAIN.USE_FLIPPED:
print 'Appending horizontally-flipped training examples...'
imdb.append_flipped_images()
print 'done'
?
print 'Preparing training data...'
rdl_roidb.prepare_roidb(imdb)
print 'done'
?
return imdb.roidb
?
def filter_roidb(roidb):
"""Remove roidb entries that have no usable RoIs."""
?
def is_valid(entry):
# Valid images have:
# (1) At least one foreground RoI OR
# (2) At least one background RoI
overlaps = entry['max_overlaps']
# find boxes with sufficient overlap
fg_inds = np.where(overlaps >= cfg.TRAIN.FG_THRESH)[0]
# Select background RoIs as those within [BG_THRESH_LO, BG_THRESH_HI)
bg_inds = np.where((overlaps < cfg.TRAIN.BG_THRESH_HI) &
(overlaps >= cfg.TRAIN.BG_THRESH_LO))[0]
# image is only valid if such boxes exist
valid = len(fg_inds) > 0 or len(bg_inds) > 0
return valid
?
num = len(roidb)
filtered_roidb = [entry for entry in roidb if is_valid(entry)]
num_after = len(filtered_roidb)
print 'Filtered {} roidb entries: {} -> {}'.format(num - num_after,
num, num_after)
return filtered_roidb
?
def train_net(solver_prototxt, roidb, output_dir,
pretrained_model=None, max_iters=40000):
"""Train a Fast R-CNN network."""
?
roidb = filter_roidb(roidb)
sw = SolverWrapper(solver_prototxt, roidb, output_dir,
pretrained_model=pretrained_model)
?
print 'Solving...'
model_paths = sw.train_model(max_iters)
print 'done solving'
return model_paths
整體python代碼實作:
Github地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/382985.html
標籤:其他
