美文网首页
Deformable Convolutional Network

Deformable Convolutional Network

作者: yanghedada | 来源:发表于2018-11-12 18:01 被阅读9次

这是一篇关于可变卷积的文章:
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解读-简书

相关文章

网友评论

      本文标题:Deformable Convolutional Network

      本文链接:https://www.haomeiwen.com/subject/tyyqfqtx.html