呕心沥血之作——RPN代码剖析

Author:哪咔吗
Date:2018-10-28
Code source:https://github.com/endernewton/tf-faster-rcnn

概述

  RPN(Region-based Proposal Network,区域提案网络)是在Faster R-CNN论文中提出的一种区域提案方法。它最大的特点就是利用了特征提取网络提取出的特征块,用于快速生成区域提案。我在详细理解Faster R-CNN代码的时候,发现RPN部分是最为复杂,最为隐晦的。因此做此专题,下定决心将RPN部分的代码进行彻底地剖析。
  tensorflow在建立graph的时候因为没有真正地feed数据,所以在用pycharm debug的时候不能看到张量的值,顶多只能看到张量的shape,其中很多shape还是类似这样的shape=(1,?,?,4)。当然我们能够仔细地看每一步操作,然后理解每一个张量的含义,但是亲身经历告诉我这是一个非常痛苦的过程。
  多亏了tfdbg这个神器,让我能够查看feed数据之后graph的运行状况,也就能够查看所有计算节点的张量。当然这一前提是在建立graph的时候有很好的名称上下文结构。这是通过 tf.variable_scope这一方法实现的。有关tfdbg的使用可以看我的另一篇博文——tensorflow tfdbg使用笔记
  下面这段代码就是RPN了,我把它分成三个部分,分别是:

  1. 生成anchors
  2. 从步骤1中生成的anchors中提取一个批次的anchors
  3. 根据步骤2中提取的anchors,从前面特征提取网络生成的特征块中提取特征向量,也就是ROI Pooling
with tf.variable_scope('hjf_rpn', 'hjf_rpn'):  # self._scope, self._scope
    # 1、生成anchors,shape=(?,4)
    self._anchor_component()
    # 2、RPN(从anchors中选取一定数量(256)的anchors,并完善这些锚的属性,如:前景背景标签、对应的gt矩形框信息等)
    rois = self._region_proposal(net_conv, training, initializer)
    # 3、ROI pooling
    if cfg.POOLING_MODE == 'crop':
        pool5 = self._crop_pool_layer(net_conv, rois, "pool5")
    else:
        raise NotImplementedError

1生成anchors

先上这一部分的全部代码,后面会给出每个步骤的详细解释,包括输出的张量结构和具体的数值。

def _anchor_component(self):
    with tf.variable_scope('anchor_component') as scope:
        # just to get the shape right
        height = tf.to_int32(tf.ceil(self._im_info[0] / np.float32(self._feat_stride[0])))   # 600/16=37.5=38
        width = tf.to_int32(tf.ceil(self._im_info[1] / np.float32(self._feat_stride[0])))    # 800/16=50
        if cfg.USE_E2E_TF:
            shift_x = tf.range(width) * self._feat_stride  # [0,16,32,48,64,80,96...784]   shape=[50]
            shift_y = tf.range(height) * self._feat_stride  # [0,16,32,48,64,80,96...592]  shape=[38]
            shift_x, shift_y = tf.meshgrid(shift_x, shift_y)  # shape=(38,50)
            sx = tf.reshape(shift_x, shape=(-1,))  # 把矩阵拉成一维向量 shape=(1900)
            sy = tf.reshape(shift_y, shape=(-1,))  # 把矩阵拉成一维向量 shape=(1900)
            shifts = tf.transpose(tf.stack([sx, sy, sx, sy]))  # shape=(1900,4) 此时的尺寸是按照800*600
            K = tf.multiply(width, height)  # 1900
            shifts = tf.transpose(tf.reshape(shifts, shape=[1, K, 4]), perm=(1, 0, 2))  # reshape to (1900,1,4)

            anchors = generate_anchors(ratios=np.array(self._anchor_ratios), scales=np.array(self._anchor_scales))  # 9个anchors
            A = anchors.shape[0]
            # 将anchors转换为tf格式
            anchor_constant = tf.constant(anchors.reshape((1, A, 4)), dtype=tf.int32)  # shape=(1,9,4)

            # TODO ??????????
            length = K * A  # 196*4=784
            anchors_tf = tf.reshape(tf.add(anchor_constant, shifts), shape=(length, 4))  # shape=(17100,4)
            anchors=tf.cast(anchors_tf, dtype=tf.float32)
            anchor_length=length

        else:
            anchors, anchor_length = tf.py_func(generate_anchors_pre,
                                                [height, width,
                                                 self._feat_stride, self._anchor_scales, self._anchor_ratios],
                                                [tf.float32, tf.int32], name="generate_anchors")
        anchors.set_shape([None, 4])
        anchor_length.set_shape([])
        self._anchors = anchors
        self._anchor_length = anchor_length

好了,先看下面这部分,这就是计算出特征块的宽和高。原图是500375的尺寸,在进入网络前尺寸被调整成800600(后面我们提到的“原图”都是指经过尺寸调整后的800*600的图像)。然后本文中使用的特征提取网络是RES101网络的前3个block,特征块的尺寸缩小为输入图像的1/16(后面我们在使用由特征提取网络生成的特征块时,统一简称其为“特征块”)。因此这边计算出的height=600/16=37.5=38(有个to_int操作),width=800/16=50。

with tf.variable_scope('anchor_component') as scope:
    # just to get the shape right
    height = tf.to_int32(tf.ceil(self._im_info[0] / np.float32(self._feat_stride[0])))   # 600/16=37.5=38
    width = tf.to_int32(tf.ceil(self._im_info[1] / np.float32(self._feat_stride[0])))    # 800/16=50

接着,我们要知道一些锚生成的基本原则。锚的数量是特征块的宽×高×anchor_scales(通常,anchor_scales=9),那么在这个案例中要生成的锚的总数就是50389=17100个。也就是说我们会在特征块上的每个特征点上生成9个锚。下面这段代码,已经将特征块上的特征点对应到了原图上的坐标点,得出的shifts是一个shape=(1900,1,4)的矩阵。如:
在这里插入图片描述
我们应该这样理解这个矩阵,把它当成是特征块上的特征点(50*38=1900)到原图像素的一个映射,我们将以这1900个点为中心,分别为他们生成对应的9个锚。

if cfg.USE_E2E_TF:
     shift_x = tf.range(width) * self._feat_stride  # [0,16,32,48,64,80,96...784]   shape=[50]
     shift_y = tf.range(height) * self._feat_stride  # [0,16,32,48,64,80,96...592]  shape=[38]
     shift_x, shift_y = tf.meshgrid(shift_x, shift_y)  # shape=(38,50)
     sx = tf.reshape(shift_x, shape=(-1,))  # 把矩阵拉成一维向量 shape=(1900)
     sy = tf.reshape(shift_y, shape=(-1,))  # 把矩阵拉成一维向量 shape=(1900)
     shifts = tf.transpose(tf.stack([sx, sy, sx, sy]))  # shape=(1900,4) 此时的尺寸是按照800*600
     K = tf.multiply(width, height)  # 1900
     shifts = tf.transpose(tf.reshape(shifts, shape=[1, K, 4]), perm=(1, 0, 2))  # reshape to (1900,1,4)

我们要知道,锚的尺寸是由anchor_scales和anchor_ratios共同确定的。在本例中anchor_ratios=[0.5,1,2],anchor_scales=[8,16,32]。其中anchor_ratios表示锚框的宽高比,anchor_scales表示的是锚框的尺寸。看到这里可能会奇怪,[8,16,32]是锚的尺寸?其实这边的尺寸是针对特征块而言的,也就是说这个尺寸其实对应了原图的[128,256,512]。前面提到每个特征点都要生成9个锚,也就是说每个尺寸生成3个不同宽高比的锚。我们以尺寸=256为例,那么生成的3个锚应该是这样的:(可能与实际情况有点差别,这是由于取整的方式不同造成的,这里只是举例说明它的生成原则,实际上存在细微的差距并不会对检测结果有什么影响)

  1. ratio=0.5 -> width=362,height=181
  2. ratio=1 -> width=256,height=256
  3. ratio=2 -> width=181,height=362

下列代码中最后得到的张量anchor_constant的值如下
[[ -84. -40. 99. 55.]
[-176. -88. 191. 103.]
[-360. -184. 375. 199.]
[ -56. -56. 71. 71.]
[-120. -120. 135. 135.]
[-248. -248. 263. 263.]
[ -36. -80. 51. 95.]
[ -80. -168. 95. 183.]
[-168. -344. 183. 359.]]
如果我们给出一个原图中的点(上面的步骤已经给出了),以这个点为中心,以上面这9个矩形框为相对值(注意理解相对值的概念),那么就可以得到围绕着这个点的9个锚了。当然,有些锚会超出原图尺寸。

anchors = generate_anchors(ratios=np.array(self._anchor_ratios), scales=np.array(self._anchor_scales))  # 9个anchors
A = anchors.shape[0]
# 将anchors转换为tf格式
anchor_constant = tf.constant(anchors.reshape((1, A, 4)), dtype=tf.int32)  # shape=(1,9,4)
length = K * A  # 1900*9=17100
anchors_tf = tf.reshape(tf.add(anchor_constant, shifts), shape=(length, 4))  # shape=(17100,4)
anchors=tf.cast(anchors_tf, dtype=tf.float32)
  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值