rcnn代码实现_Faster-RCNN论文细节原理解读+代码实现gluoncv(MXNet)

ab51e94bd20eb8b6262de36754bf05b5.png

Faster-RCNN开创了基于锚框(anchors)的目标检测框架,并且提出了RPN(Region proposal network),来生成RoI,用来取代之前的selective search方法。Faster-RCNN无论是训练/测试速度,还是物体检测的精度都超过了Fast-RCNN,并且实现了end-to-end训练。

从RCNN到Fast-RCNN再到Faster-RCNN,后者无疑达到了这一系列算法的巅峰,并且后来的YOLO、SSD、Mask-RCNN、RFCN等物体检测框架都是借鉴了Faster-RCNN

Faster-RCNN作为一种two-stage的物体检测框架,流程无疑比SSD这种one-stage物体检测框架要复杂,在阅读论文,以及代码复现的过程中也理解了很多细节,在这里记录一下自己的学习过程和自己的一点体会。

背景介绍

Fast-RCNN通过共享卷积层,极大地提升了整体的运算速度。Selective Search 反倒成为了限制计算效率的瓶颈。Faster-RCNN中使用卷积神经网络取代了Selective Search,这个网络就是Region Proposal Networks(RPN),Faster-RCNN将所有的步骤都包含到一个完整的框架中,真正实现了端对端(end-to-end)的训练。

论文主要贡献

  • 提出RPN,实现了端对端的训练
  • 提出了基于anchors的物体检测方法

1、网络框架

Faster-RCNN总体流程框图如下(点击原图查看大图),通过这个框图我们比较一下Faster-RCNN和SSD的不同: SSD中每一阶段生成的特征图,每个cell都会生成锚框,并且进行类别+边界框回归。 Faster-RCNN只对basenet提取出的特征图上生成锚框,并且对该锚框进行二分类(背景 or 有物体)+边界框回归,然后会进行NMS移除相似的结果,这样RPN最后会输出一系列region proposal,将这些region proposal区域从feature map中提取出来即为RoI,之后将会通过RoI pooling,进行真正的类别预测(判断属于哪一类)+边界框回归

可以看出Faster-RCNN之所以被称为two-stage,是由于需要有RPN生成region proposal这一步骤。相比来看SSD可以看做是稠密采样,它对所有生成的锚框进行了预测,而没有进行筛选。

RPN中还有一些细节操作,比如说采样比例的设置,如何进行预测,这个在后面的部分会详细说明。

fa4852b41b589e5a040ba593bfa70725.png

2、RPN(Region Proposal Network)

处理流程

RPN在Faster-RCNN中作用为生成RoI,RPN的处理流程具体如下,一些细节将在之后介绍: 1. 输入为base_net提取出来的feature map,首先在feature map上生成锚框(anchor),其中每个cell有多个锚框。 2. 通过一个conv_3x3,stride=1,padding=1的卷积层,进一步提取特征,输出特征图的大小不变,这里称为rpn_feature。 3. 在rpn_feature上用两个1x1卷积层进行预测输出,分别为每个锚框的二分类分数、每个锚框的坐标偏移量。 4. 利用上面预测的分数以及偏移量,对锚框(anchor)进行非极大值抑制(NMS)操作,最终输出RoI候选区域

dfa88dc198e28d155e7b74f7b13f77a7.png

详细步骤及代码

在feature_map上生成锚框

这一步中,会在feature_map每个cell上生成一系列不同大小和宽高比例的锚框。生成锚框的方式如下: 1. 选定一个锚框的基准大小,记为base,比如为16 2. 选定一组宽高比例(aspect ratios),比如为【0.5、1、2】 3. 选定一组大小比例(scales),比如为【16、32、64】 4. 那么每个cell将会生成ratios*scales个锚框,而每个锚框的形状大小的计算公式如下: $$ width_{anchor} = size_{base} times scale times sqrt{ 1 / ratio}$$ $$ height_{anchor} = size_{base} times scale times sqrt{ratio}$$ 举个例子,我们按照论文中取3种大小比例以及3种长宽比例,那么每个cell生成的锚框个数为$k=9$,而假设我们的特征图大小为$Wtimes H=2400$,那么我们一共生成了$WHk$个锚框。可以看到,生成的锚框数量非常多,有大量的重复区域。RPN输出时不应该使用所有锚框,所以采用NMS 来去除大量重复的锚框,而只选择一些得分较高的锚框作为RoI输出。其实,RPN在训练时也进行了采样,这个后面具体介绍。RPN生成的锚框如下图所示:

c214f8857c0a03596f579e8530528ec3.png

MXNet中,生成锚框的类源码如下所示:

class RPNAnchorGenerator(gluon.Block):
    """
    @输入参数
    stride:int              
        特征图的每个像素感受野大小,通常为原图和特征图尺寸比例
    base_size:int           
        默认大小
    ratios:int              
        宽高比
    scales:int              
        大小比例

        每个锚框为   width = base_size*size/sqrt(ratio)  
                    height = base_size*size*sqrt(ratio)

    alloc_size:(int,int)          
        默认的特征图大小(H,W),以后每次生成直接索引切片
    """

    def __init__(self, stride, base_size, ratios, scales, alloc_size, **kwargs):
        super(RPNAnchorGenerator, self).__init__(**kwargs)
        if not base_size:
            raise ValueError("Invalid base_size: {}".format(base_size))
        # 防止非法输入
        if not isinstance(ratios, (tuple, list)):
            ratios = [ratios]
        if not isinstance(scales, (tuple, list)):
            scales = [scales]

        # 每个像素的锚框数
        self._num_depth = len(ratios) * len(scales)
        # 预生成锚框
        anchors = self._generate_anchors(stride, base_size, ratios, scales, alloc_size)
        self.anchors = self.params.get_constant('anchor_', anchors)

    @property
    def num_depth(self):
        return self._num_depth

    def _generate_anchors(self, stride, base_size, ratios, scales, alloc_size):
        # 计算中心点坐标
        px, py = (base_size - 1) * 0.5, (base_size - 1) * 0.5
        base_sizes = []
        for r in ratios:
            for s in scales:
                size = base_size * base_size / r
                ws = np.round(np.sqrt(size))
                w = (ws * s - 1) * 0.5
                h = (np.round(ws * r) * s - 1) * 0.5
                base_sizes.append([px - w, py - h, px + w, py + h])
        # 每个像素的锚框
        base_sizes = np.array(base_sizes)

        # 下面进行偏移量的生成
        width, height = alloc_size
        offset_x = np.arange(0, width * stride, stride)
        offset_y = np.arange(0, height * stride, stride)
        offset_x, offset_y = np.meshgrid(offset_x, offset_x)
        # 生成(H*W,4)
        offset = np.stack((offset_x.ravel(), offset_y.ravel(),
                           offset_x.ravel(), offset_y.ravel()), axis=1)

        # 下面广播到每一个anchor中    (1,N,4) + (M,1,4)
        anchors = base_sizes.reshape((1, -1, 4)) + offset.reshape((-1, 1, 4))
        anchors = anchors.reshape((1, 1, width, height, -1)).astype(np.float32)
        return anchors

    # 对原始生成的锚框进行切片操作
    def forward(self, x):
        # 切片索引
        anchors = self.anchors.value
        a = nd.slice_like(anchors, x * 0, axes=(2, 3))
        return a.reshape((1, -1, 4))

用conv3x3卷积进一步提取特征图

这一步中就是RPN进一步抽取特征,生成的RPN-feature map提供给之后的类别预测和回归预测。该步骤中使用的是kernel_size=3x3,strides=1,padding=1,Activation='relu'的卷积层,不改变特征图的尺寸,这也是为了之后的1x1卷积层预测时,空间位置能够一一对应,而用通道数来表示预测的类别分数和偏移量。这一步的代码很简单,就是单独的构建了一个3x3 Conv2D的卷积层。

# 第一个提取特征的3x3卷积
self.conv1 = nn.Sequential()
self.conv1.add(nn.Conv2D(channels, kernel_size=3, strides=1, padding&#
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值