从源码解读Faster-RCNN--(3)RPN网络

RPN网络是Faster-RCNN的核心,因为我们的目标检测网络就是要确定目标的位置,那么怎么生成目标候选框,而又如何对候选框筛选训练,这部分就是RPN网络的内容。
相对于传统的滑动窗口以及RCNN的Selective Search方法,RPN网络思想是:

  1. 图像的每一个点都是潜在的目标中心(实际上意思和滑动窗口类似,只不过滑动窗口有点类似是串行,而RPN类似并行,多点开花,但是都是以每个像素点为中心,进行不同大小邻域的检测)。
  2. 另一方面,如果在原始图像对每个点或者每隔几个点作为候选Box的中心,则计算量很大,RPN网络则是将重点放在了经过特征提取后的feature map上了,将feature map上每个点作为候选点,同时,相对于原始图,RPN则在每一通道上都有Bounding Box的候选框,进一步的利用了feature map在特征提取上的优势。

代码来源simple-faster-rcnn-pytorch
参考
逐字理解目标检测simple-faster-rcnn-pytorch-master代码(二)
从编程实现角度学习Faster R-CNN(附极简实现)

下面看代码,这一部分主要看region_proposal_network.py部分的代码,前后穿插其他代码。

1.得到feature map

在trainer.py中,我们的训练器FasterRCNNTrainer首先利用预训练的vgg16模型,提取到了feature map.

#trainer.py
features = self.faster_rcnn.extractor(imgs)#97

进一步的,我们看一下得到的怎样的特征图呢?在faster_rcnn_vgg16.py中,我们找到了代码。

#model/faster_rcnn_vgg16.py
extractor, classifier = decom_vgg16()#63
#model/faster_rcnn_vgg16.py
def decom_vgg16():#12
    # the 30th layer of features is relu of conv5_3
    if opt.caffe_pretrain:
        model = vgg16(pretrained=False)
        if not opt.load_path:
            model.load_state_dict(t.load(opt.caffe_pretrain_path))
    else:
        model = vgg16(not opt.load_path)

    features = list(model.features)[:30]
     ...
    return nn.Sequential(*features), classifier

通过注释,我们也看到其拿到的是conv5_3经过relu后的特征图,通过对VGG16网络的分析,我们大概知道应该是14×14×512这个大小的特征图,相对原图缩小了16倍。
vgg16
但是训练时就不是VGG16这个大小,我们从图像的输入找起。从train.py一路找起,找到data/dataset.py代码。

#train.py
dataset = Dataset(opt)#53
#data/dataset.py
class Dataset:#100
	...
	def __init__(self, opt):
		...
		self.tsf = Transform(opt.min_size, opt.max_size)
	def __getitem__(self, idx):
		ori_img, bbox, label, difficult = self.db.get_example(idx)
		img, bbox, label, scale = self.tsf((ori_img, bbox, label))
		return img.copy(), bbox.copy(), label.copy(), scale
#data/dataset.py
class Transform(object):#77
	...
	def __call__(self, in_data):
		...
		img = preprocess(img, self.min_size, self.max_size)
		...
		return img, bbox, label, scale
#data/dataset.py
def preprocess(img, min_size=600, max_size=1000):#42
    C, H, W = img.shape
    scale1 = min_size / min(H, W)
    scale2 = max_size / max(H, W)
    scale = min(scale1, scale2)
    img = img / 255.
    img = sktsf.resize(img, (C, H * scale, W * scale), mode='reflect',anti_aliasing=False)
    ...
    return normalize(img)

所以,可以看到图像大小是经过resize的,由于训练时候采用的VOC2007数据集是500×375大小,这里经过了数据预处理,送入网络的大小为800×600,那么经过VGG16部分特征提取后,得到的feature map大小为(800/16, 600/16, 512)=(50, 37, 512).

产生anchor

得到了feature map,那么我们RPN网络就拿它开刀,我们从最初trianer.py看起.

#trainer.py
rpn_locs, rpn_scores, rois, roi_indices, anchor = \
            self.faster_rcnn.rpn(features, img_size, scale)#99

可以看到RPN网络确实只对feature map感兴趣。

#model/faster_rcnn_vgg16.py
        rpn = RegionProposalNetwork(
            512, 512,
            ratios=ratios,
            anchor_scales=anchor_scales,
            feat_stride=self.feat_stride,
        )#70

终于到了model/region_proposal_network.py部分

#model/region_proposal_network.py
class RegionProposalNetwork(nn.Module):#10
	def __init__(...)
		self.anchor_base = generate_anchor_base(..)
		self.proposal_layer = ProposalCreator(self, **proposal_creator_params)
		n_anchor = self.anchor_base.shape[0]
		self.conv1 = nn.Conv2d(in_channels, mid_channels, 3, 1, 1)
		self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)
		self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)
		...

首先,我们先熟悉下init这里面的几个很重要的成员,后面都会用到。然后,我们进行过程分析,先看forward部分。

#model/region_proposal_network.py
def forward(self, x, img_size, scale=1.):#62
	n, _, hh, ww = x.shape
	anchor = _enumerate_shifted_anchor(
	            np.array(self.anchor_base),
	            self.feat_stride, hh, ww)	
	....

这里是得到anchor,利用用到了两个函数一个是self.anchor_base,这个可以通过init部分找到其来源,另一个是_enumerate_shifted_anchor,我们一个一个看:

#model/utils/bbox_tools.py
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],
                         anchor_scales=[8, 16, 32]):#195
    py = base_size / 2.
    px = base_size / 2.

    anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),
                           dtype=np.float32)
    for i in six.moves.range(len(ratios)):
        for j in six.moves.range(len(anchor_scales)):
            h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
            w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])

            index = i * len(anchor_scales) + j
            anchor_base[index, 0] = py - h / 2.
            anchor_base[index, 1] = px - w / 2.
            anchor_base[index, 2] = py + h / 2.
            anchor_base[index, 3] = px + w / 2.
    return anchor_base                         

是不是很熟悉这个函数,没错,这个就是我们第一篇博客里面强调的anchor生成函数,就是生成9个不同比例大小的box,接下来:
这部分详细可参考逐字理解目标检测simple-faster-rcnn-pytorch-master代码(二)

#model/region_proposal_network.py
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):#137
	###利用anchor_base生成所有对应feature map的anchor
    import numpy as xp
    shift_y = xp.arange(0, height * feat_stride, feat_stride) #纵向偏移量(0,16,32,...)
    shift_x = xp.arange(0, width * feat_stride, feat_stride)# 横向偏移量(0,16,32,...)
    shift_x, shift_y = xp.meshgrid(shift_x, shift_y)
    #shift_x = [[0,16,32,..],[0,16,32,..],[0,16,32,..]...]
    #shift_y = [[0,0,0,..],[16,16,16,..],[32,32,32,..]...]
    shift = xp.stack((shift_y.ravel(), shift_x.ravel(),
                      shift_y.ravel(), shift_x.ravel()), axis=1)

    A = anchor_base.shape[0]#A=9
    K = shift.shape[0]#读取特征图中元素的总个数
    anchor = anchor_base.reshape((1, A, 4)) + \
             shift.reshape((1, K, 4)).transpose((1, 0, 2))
    #用基础的9个anchor的坐标分别和偏移量相加,最后得出了所有的anchor的坐标
    anchor = anchor.reshape((K * A, 4)).astype(np.float32)
    return anchor

这个分析起来就比较复杂,但是一步一步跟着代码走也并不麻烦,这里的height和width就是特征图的大小,按照我们前面分析,特征图的大小应该为50×37那样,代码的中间部分就是生成网格,每个网格交叉点都有大小不同的9个anchor,那么整体的所有的anchor就是返回量,最后的anchor的数量为50×37×9=16650个。于是就产生了所有的anchor,这么多的anchor不能都用于训练吧,下面就开始对anchor进行操作了。

anchor的处理

在对anchor筛选之前,我们首先需要找到了一个依据对anchors预判,包括重要的Bound Box回归,那么怎么做呢,就要对feature map进行操作啦,之前只用了其大小…首先看对feature map的处理代码

#model/region_proposal_network.py
        anchor = _enumerate_shifted_anchor(
            np.array(self.anchor_base),
            self.feat_stride, hh, ww)#102
        h = F.relu(self.conv1(x))
        rpn_locs = self.loc(h)  
        rpn_scores = self.score(h)          

RPN网络在通过特征图中的每个anchor的可能所属的box以及所属前景或者背景做了预测,其中self.loc做box的预测,为后面做bound box回归提供依据,self.score则为当前点为前景或者背景做了预测,为后面的anchor筛选提供依据。

#model/region_proposal_network.py
        rois = list()#120
        roi_indices = list()
        for i in range(n):
            roi = self.proposal_layer(
                rpn_locs[i].cpu().data.numpy(),
                rpn_fg_scores[i].cpu().data.numpy(),
                anchor, img_size,
                scale=scale)
            batch_index = i * np.ones((len(roi),), dtype=np.int32)
            rois.append(roi)
            roi_indices.append(batch_index)

其中rpn_fg_scores为特征图某点的为前景(物体)的概率图。找到self.proposal_layer的实现

#model/utils/creator_tool.py
class ProposalCreator:#291
    def __init__(self,
                 parent_model,
                 nms_thresh=0.7,
                 n_train_pre_nms=12000,
                 n_train_post_nms=2000,
                 ...
                 ):
                ...
    def __call__(self, loc, score,
         anchor, img_size, scale=1.):
         roi = loc2bbox(anchor, loc)
         ...
         keep = np.where((hs >= min_size) & (ws >= min_size))[0]
         roi = roi[keep, :]
         score = score[keep]
         order = score.ravel().argsort()[::-1]
         if n_pre_nms > 0:
             order = order[:n_pre_nms]
         roi = roi[order, :]
         ...
         keep = non_maximum_suppression(...)
	     if n_post_nms > 0:
            keep = keep[:n_post_nms]
        roi = roi[keep]
        return roi                       

这几段代码就很清晰了首先是loc2bbox做Bound box回归,然后,按照前景概率做过滤,最后对过滤后的框做非极大值抑制,得到n_post_nms=n_train_post_nms=2000个候选的框区域。
到此,我们通过RegionProposalNetwork的前向传播,就得到了,

  • rpn_locs:所用的anchor的在特征图上的预测位置
  • rpn_scores:所有的anchor的在特征图上的前景
  • rois:筛选后的候选区域
  • roi_indices:筛选后的候选区域的下标
  • anchor:所有的anchor

其中,rpn_locs和rpn_scores可帮助我们训练RegionProposalNetwork中的self.conv1()、 self.loc()、self.score()卷积网络, rois可为我们下一步分类网络训练提供来源。

RPN网络的结构如下:
RPN
这里的
输入的feature map,
3×3 conv,512为我们的self.conv1(),
1×1 conv ,18为我们的self.score(),辅助进行候选框的筛选,
1×1 conv,36为我们的self.loc(),辅助anchor定位
RPN通过generate_anchor生成的anchors单独靠计算生成,不涉及网络结构,但是RPN中的网络会辅助对这些anchor的回归和筛选,回归和筛选的好坏就全靠这些网络,因此需要训练。
至此,RPN网络的分析就结束了,这里得到了RPN loss的来源,下一篇,我们开始分析分类网络以及损失函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值