这是一篇关于可变卷积的文章:
Deformable Convolutional Networks中提出了一种可以变形的卷积核和池化核,脱离的原始的正方形的卷积核,可以把卷积核更多地聚集在目标的轮廓上。如图:

上图就是把卷积核注重点放在了这个人的身上。

上图卷积核主要集中在羊的身上。
源码目录结构:

- data: 是存放数据的主要目录
- experiments: 是存放运行脚本的文件,直接运行对应文件,可以进行对应的测试。调用faster_rcnn里面的文件。
- faster_rcnn: 这是运行与faster_rcnn相关的主函数文件,因为该论文提出一可变卷积核,所以该论文是在faster_rcnn框架上进行的。
- lib:与faster_rcnn相关的所有模块文件。
- 两个notebook文件:使用faster_rcnn下test.py文件的教程文件,可以从这里入手吧。
经过对experiments目录的分析
experiments下三个文案进
experiments|
|-faster_rcnn|
|-demo.py : 对ckpt文件测试,查看是否能够完全运行,使用的demo中的图片进行测试
|-tets_net.py:对自己的图片进行测试
|-train_net.py:使用训练数据进行训练
这次是对可变形的卷积进行源码分析,那就直接看卷积核的源码好了。
test_net.py
只看主要结构:
#test_net.py
from lib.fast_rcnn.test import test_net, load_test_net
from lib.fast_rcnn.config import cfg, cfg_from_file
from lib.datasets.factory import get_imdb
from lib.networks.factory import get_network
# 获取数据
imdb = get_imdb(args.imdb_name)
imdb.competition_mode(args.comp_mode)
device_name = '/gpu:{:d}'.format(args.gpu_id)
print(device_name)
# 构建检测网络
with tf.device(device_name):
network = get_network(args.network_name)
print(('Use network `{:s}` in training'.format(args.network_name)))
cfg.GPU_ID = args.gpu_id
# restore 预训练参数
saver = tf.train.Saver()
c = tf.ConfigProto(allow_soft_placement=True)
c.gpu_options.visible_device_list=str(args.gpu_id)
sess = tf.Session(config=c)
saver.restore(sess, tf.train.latest_checkpoint(args.model))
print((('Loading model weights from {:s}').format(args.model)))
# 对图片进行检测, 测试网络
test_net(sess, network, imdb, weights_filename, thresh=0.7)
- 1.读取数据
- 2.构建网络
- 3.restroe预训练参数
- 4.对图片进行检测
这里就是调用了from lib.fast_rcnn.test import test_net和
from lib.networks.factory import get_network构建检测模型的。
- get_network就是构建的函数了
train_net.py
# train_net.py
# 获取数据
imdb = get_imdb(args.imdb_name)
print(('Loaded dataset `{:s}` for training'.format(imdb.name)))
roidb = get_training_roidb(imdb)
output_dir = get_output_dir(imdb, None)
log_dir = get_log_dir(imdb)
print(('Output will be saved to `{:s}`'.format(output_dir)))
print(('Logs will be saved to `{:s}`'.format(log_dir)))
device_name = '/gpu:{:d}'.format(args.gpu_id)
print(device_name)
# 构建网络
with tf.device(device_name):
network = get_network(args.network_name)
print(('Use network `{:s}` in training'.format(args.network_name)))
# import os
# os.environ["CUDA_DEVICE_ORDER"]="PCI_BUS_ID" # see issue #152
# os.environ["CUDA_VISIBLE_DEVICES"]=str(args.gpu_id)
# 进行训练
train_net(network, imdb, roidb,
output_dir=output_dir,
log_dir=log_dir,
pretrained_model=args.pretrained_model,
max_iters=args.max_iters,
restore=bool(int(args.restore)))
和上面类似的结构。
这里看到其实这个get_notwork才是,比较重要。
那就看看吧.
facator.py
# facator.py
from .VGGnet_test import VGGnet_test
from .VGGnet_testold import VGGnet_testold
from .VGGnet_train import VGGnet_train
from .Resnet50_test import Resnet50_test
from .Resnet50_train import Resnet50_train
from .Resnet101_test import Resnet101_test
from .Resnet101_train import Resnet101_train
from .PVAnet_train import PVAnet_train
from .PVAnet_test import PVAnet_test
def get_network(name):
"""Get a network by name."""
if name.split('_')[0] == 'VGGnet':
if name.split('_')[1] == 'test':
return VGGnet_test()
elif name.split('_')[1] == 'train':
return VGGnet_train()
elif name.split('_')[1] == 'testold':
return VGGnet_testold()
else:
raise KeyError('Unknown dataset: {}'.format(name))
elif name.split('_')[0] == 'Resnet50':
if name.split('_')[1] == 'test':
return Resnet50_test()
elif name.split('_')[1] == 'train':
return Resnet50_train()
else:
raise KeyError('Unknown dataset: {}'.format(name))
elif name.split('_')[0] == 'Resnet101':
if name.split('_')[1] == 'test':
return Resnet101_test()
elif name.split('_')[1] == 'train':
return Resnet101_train()
else:
raise KeyError('Unknown dataset: {}'.format(name))
elif name.split('_')[0] == 'PVAnet':
if name.split('_')[1] == 'test':
return PVAnet_test()
elif name.split('_')[1] == 'train':
return PVAnet_train()
else:
raise KeyError('Unknown dataset: {}'.format(name))
else:
raise KeyError('Unknown dataset: {}'.format(name))
上面个的这含函数是不是太简单了,就是if else:
这个name,在脚本里面是怎么设置的呢??
TF_Deformable_Net-master\experiments\scripts\faster_rcnn_end2end.sh
python ./faster_rcnn/train_net.py --gpu ${GPU_ID} \
--weights data/pretrain_model/VGG_imagenet.npy \
--network VGGnet_train \
python ./faster_rcnn/test_net.py --gpu ${GPU_ID} \
--network VGGnet_test \
写成VGGnet_test或者VGGnet_train就行了。
为了方便进行解析只能从VGG开刀了。
VGGnet_test.py
VGGnet_test.py 和VGGnet_train.py
import tensorflow as tf
from .network import Network
from ..fast_rcnn.config import cfg
class VGGnet_test(Network):
def __init__(self, trainable=True):
self.inputs = []
self.data = tf.placeholder(tf.float32, shape=[None, None, None, 3])
self.im_info = tf.placeholder(tf.float32, shape=[None, 3])
self.keep_prob = tf.placeholder(tf.float32)
self.layers = dict({'data': self.data, 'im_info': self.im_info})
self.trainable = trainable
self.setup()
def setup(self):
# n_classes = 21
n_classes = cfg.NCLASSES
# anchor_scales = [8, 16, 32]
anchor_scales = cfg.ANCHOR_SCALES
_feat_stride = [16, ]
(self.feed('data')
.conv(3, 3, 64, 1, 1, name='conv1_1', trainable=False)
.conv(3, 3, 64, 1, 1, name='conv1_2', trainable=False)
.max_pool(2, 2, 2, 2, padding='VALID', name='pool1')
.conv(3, 3, 128, 1, 1, name='conv2_1', trainable=False)
.conv(3, 3, 128, 1, 1, name='conv2_2', trainable=False)
.max_pool(2, 2, 2, 2, padding='VALID', name='pool2')
.conv(3, 3, 256, 1, 1, name='conv3_1')
.conv(3, 3, 256, 1, 1, name='conv3_2')
.conv(3, 3, 256, 1, 1, name='conv3_3')
.max_pool(2, 2, 2, 2, padding='VALID', name='pool3')
.conv(3, 3, 512, 1, 1, name='conv4_1')
.conv(3, 3, 512, 1, 1, name='conv4_2')
.conv(3, 3, 512, 1, 1, name='conv4_3')
.max_pool(2, 2, 2, 2, padding='VALID', name='pool4')
.conv(3, 3, 512, 1, 1, name='conv5_1')
.conv(3, 3, 512, 1, 1, name='conv5_2')
.conv(3, 3, 512, 1, 1, name='conv5_3'))
# 这之上就是共享卷积网络,这里conv5_3就是共享网络的最后一层
#========= RPN ============
# 这是对conv5_3层上来的feature map进行背景分类
(self.feed('conv5_3')
.conv(3, 3, 512, 1, 1, name='rpn_conv/3x3')
.conv(1, 1, len(anchor_scales) * 3 * 2, 1, 1, padding='VALID', relu=False, name='rpn_cls_score'))
# 这是对conv5_3层上来的feature map进行box预测
(self.feed('rpn_conv/3x3')
.conv(1, 1, len(anchor_scales) * 3 * 4, 1, 1, padding='VALID', relu=False, name='rpn_bbox_pred'))
# shape is (1, H, W, Ax2) -> (1, H, WxA, 2)
(self.feed('rpn_cls_score')
.spatial_reshape_layer(2, name='rpn_cls_score_reshape')
.spatial_softmax(name='rpn_cls_prob'))
# shape is (1, H, WxA, 2) -> (1, H, W, Ax2)
(self.feed('rpn_cls_prob')
.spatial_reshape_layer(len(anchor_scales) * 3 * 2, name='rpn_cls_prob_reshape'))
(self.feed('rpn_cls_prob_reshape', 'rpn_bbox_pred', 'im_info')
.proposal_layer(_feat_stride, anchor_scales, 'TEST', name='rois'))
#========= RCNN ============
# 对conv5_3进行编码
(self.feed('conv5_3')
.conv(3, 3, 72, 1, 1, biased=True, rate=2, relu=False, name='conv6_1_offset', padding='SAME', initializer='zeros'))
(self.feed('conv5_3', 'conv6_1_offset')
.deform_conv(3, 3, 512, 1, 1, biased=False, rate=2, relu=True, num_deform_group=4, name='conv6_1'))
(self.feed('conv6_1')
.conv(3, 3, 72, 1, 1, biased=True, rate=2, relu=False, name='conv6_2_offset', padding='SAME', initializer='zeros'))
(self.feed('conv6_1', 'conv6_2_offset')
.deform_conv(3, 3, 512, 1, 1, biased=False, rate=2, relu=True, num_deform_group=4, name='conv6_2'))
# 对RPN网络出来的box在conv6_1截取对应的rois特征
(self.feed('conv6_2', 'rois')
.deform_psroi_pool(group_size=1, pooled_size=7, sample_per_part=4, no_trans=True, part_size=7, output_dim=256, trans_std=1e-1, spatial_scale=0.0625, name='offset_t')
.fc(num_out=7 * 7 * 2, name='offset', relu=False)
.reshape(shape=(-1,2,7,7), name='offset_reshape'))
# 对classes进行分类
(self.feed('conv6_2', 'rois', 'offset_reshape')
.deform_psroi_pool(group_size=1, pooled_size=7, sample_per_part=4, no_trans=False, part_size=7, output_dim=256, trans_std=1e-1, spatial_scale=0.0625, name='pool_6')
.fc(4096, name='fc6')
.dropout(0.5, name='drop6')
.fc(4096, name='fc7')
.dropout(0.5, name='drop7')
.fc(n_classes, relu=False, name='cls_score')
.softmax(name='cls_prob'))
# 对box坐标进行预测
(self.feed('drop7')
.fc(n_classes*4, relu=False, name='bbox_pred'))
这里的整个流程就是faster_rcnn的检测模式,仅仅在最后进行box坐标的微调使用的是deform_conv和deform_psroi_pool。
作者用C++写了deform_conv和deform_psroi_pool操作。我等根本看不懂。只能到这里了。
看一下deform_conv的示意图吧:

上图a是正常的的卷积核。b,c,d是在a基础上进行的空间上的尺寸变换和平移旋转。
- 正常的卷积操作:

对于一个3x3的卷积核,对于每个输出y(p0),都要从x(p0)上采样9个位置,这9个位置都在中心位置x(p0)向四周扩散得到的gird形状上,(-1,-1)代表x(p0)的左上角,(1,1)代表x(p0)的右下角。
- 可变形卷积:

对于一个3x3的卷积核,同样对于每个输出y(p0),都要从x上采样9个位置,这9个位置是中心位置x(p0)向四周扩散得到的,但是多了一个新的参数 ∆pn,允许采样点扩散成不规则的形状。
这的y(p0)若没有取到整数,必须要进行线性插值:

x(q)表示feature map上所有整数位置上的点的取值,x(p)=x(p0+pn+Δpn)表示加上偏移后所有小数位置的点的取值。
参考:
Deformable Convolutional Networks解读-cnds
Deformable Convolutional Networks解读-简书
网友评论