RPN网络代码解读

1. 说在前面的话

在目标检测领域Faster RCNN可以说是无人不知无人不晓,它里面有一个网络结构RPN(Region Proposal Network)用于在特征图上产生候选预测区域。但是呢,这个网络结构具体是怎么工作的呢?网上有很多种解释,但是都是云里雾里的,还是直接撸代码来得直接,这里就直接从代码入手直接撸吧-_-||。
首先,来看一下Faster RCNN中RPN的结构是什么样子的吧。可以看到RPN直接通过一个卷积层rpn_conv/3x3直接接在了分类网络的特征层输出上面,之后接上两个卷积层rpn_clc_score与rpn_bbox_pred分别用于产生前景背景分类与预测框。之后再由python层AnchorTargetLayer产生anchor机制的分类与预测框。然后,经过ROI Proposal产生ROI区域的候选,并通过ROI Pooling规范到相同的尺寸上进行后续处理。大体的结构如下图所示:
在这里插入图片描述
虽然在上面的图中能够对RPN网络有一个比较直观但是笼统的概念,其具体内部搞了啥子,并不清楚。所以还是撸一下它里面的代码看看吧,首先来看RPN模块中各个文件说明。
(1)generate_anchors.py
在[0,0,15,15]基础anchor的基础上生成不同宽高比例以及缩放大小的anchor。
Generates a regular grid of multi-scale, multi-aspect anchor boxes.
(2)proposal_layer.py
将RPN网络的每个anchor的分类得分以及检测框回归预估转换为目标候选
Converts RPN outputs (per-anchor scores and bbox regression estimates) into object proposals.
(3)anchor_target_layer.py
为每个anchor生成训练目标或标签,分类的标签只是0(非目标)1(是目标)-1(忽略)。当分类的标签大于0的时候预测框的回归才被指定。
Generates training targets/labels for each anchor. Classification labels are 1 (object), 0 (not object) or -1 (ignore).
Bbox regression targets are specified when the classification label is > 0.
(4)proposal_target_layer.py
为每个目标候选生成训练目标或标签,分类标签从 0 − K 0-K 0K(背景0或目标类别 1 , … , K 1, \dots, K 1,,K),自然lable值大于0的才被指定预测框回归。
Generates training targets/labels for each object proposal: classification labels 0 - K (bg or object class 1, … , K)
and bbox regression targets in that case that the label is > 0.
(5)generate.py
使用RPN从IMDB输入数据上产生目标候选。
Generate object detection proposals from an imdb using an RPN.
现在对RPN网络的结构和RPN模块中文件有了一个大体的认识,那么接下来就开始阅读里面的实现代码,看看它究竟干了些什么事情。

2. RPN网络部分

这个部分使用到的文件有anchor_target_layer.py、generate_anchors.py。这里的generate_anchors.py是用来产生模型需要的anchor的,其中也包含了一些其它的辅助函数,它不是讲解说明的重点,这里不作介绍。主要来看anchor_target_layer.py文件。
首先,来看看这个层的初始化函数:

def setup(self, bottom, top):
    layer_params = yaml.load(self.param_str_)
    anchor_scales = layer_params.get('scales', (8, 16, 32)) # 尺度变化参数
    self._anchors = generate_anchors(scales=np.array(anchor_scales)) # 生成默认的9个anchor
    self._num_anchors = self._anchors.shape[0]
    self._feat_stride = layer_params['feat_stride']

    # allow boxes to sit over the edge by a small amount
	# 设为0,则取出任何超过图像边界的proposals,只要超出一点点,都要去除
    self._allowed_border = layer_params.get('allowed_border', 0)

    height, width = bottom[0].data.shape[-2:]
    if DEBUG:
        print 'AnchorTargetLayer: height', height, 'width', width

        A = self._num_anchors
    # labels 是否为目标的分类
    top[0].reshape(1, 1, A * height, width)
    # bbox_targets
    top[1].reshape(1, A * 4, height, width)
    # bbox_inside_weights
    top[2].reshape(1, A * 4, height, width)
    # bbox_outside_weights
top[3].reshape(1, A * 4, height, width)

接下来就是重头的forward函数,首先,该函数在特征图生成需要运算的总的anchor

# 1. Generate proposals from bbox deltas and shifted anchors
# x方向的偏移个数,大小为特征图的width
shift_x = np.arange(0, width) * self._feat_stride
# y方向的偏移个数,大小为特征图的height
shift_y = np.arange(0, height) * self._feat_stride
# shift_x,shift_y均为width×height的二维数组(meshgrid生成),对应位置的元素组合即构成图像上需要偏移量大小
#(偏移量大小是相对与图像最左上角的那9个anchor的偏移量大小),也就是说总共会得到width×height×9个偏移值对。
# 这些偏移值对与初始的anchor相加即可得到
# 所有的anchors,所以总共会产生width×height×9个anchors,且存储在all_anchors变量中
shift_x, shift_y = np.meshgrid(shift_x, shift_y)
shifts = np.vstack((shift_x.ravel(), shift_y.ravel(),
                    shift_x.ravel(), shift_y.ravel())).transpose() # 维度输出为(width*height)*4
# add A anchors (1, A, 4) to
# cell K shifts (K, 1, 4) to get
# shift anchors (K, A, 4)
# reshape to (K*A, 4) shifted anchors
A = self._num_anchors
K = shifts.shape[0] # K=width*height
# 在之前9个anchor的基础上产生K*A个anchor,既是总的anchor数量
all_anchors = (self._anchors.reshape((1, A, 4)) +
               shifts.reshape((1, K, 4)).transpose((1, 0, 2)))
all_anchors = all_anchors.reshape((K * A, 4))
total_anchors = int(K * A) # 总的anchor数量

产生这么多的anchor自然有一些超出了边界,那么就需要对其进行剔除

# only keep anchors inside the image 在图像内部的anchor,即是有效anchor,边界之外的删除掉
inds_inside = np.where(
    (all_anchors[:, 0] >= -self._allowed_border) &
    (all_anchors[:, 1] >= -self._allowed_border) &
    (all_anchors[:, 2] < im_info[1] + self._allowed_border) &  # width
    (all_anchors[:, 3] < im_info[0] + self._allowed_border)    # height
    )[0]

初始化可用anchor对应的lable,分类标签的含义下面写了

# label: 1 is positive, 0 is negative, -1 is dont care
# 图像内部anchor对应的分类,是否为目标的分类,大小为符合条件anchor的数量
labels = np.empty((len(inds_inside), ), dtype=np.float32)
labels.fill(-1)

在之前生成了计算需要的anchor了那么接下来就是需要计算anchor与gt之间的关系了,也就是使用overlap area的面积来度量,每个anchor的是否为目标分类也是根据这个度量来设置的。

# overlaps between the anchors and the gt boxes
# overlaps (ex, gt)返回维度为【anchors * gt_boxes】大小的二维数组
overlaps = bbox_overlaps(
    np.ascontiguousarray(anchors, dtype=np.float),
    np.ascontiguousarray(gt_boxes, dtype=np.float))
argmax_overlaps = overlaps.argmax(axis=1) # 求取于anchor重叠最大的gt
max_overlaps = overlaps[np.arange(len(inds_inside)), argmax_overlaps] # 取出与每个anchor重叠最大gt的重叠面积
gt_argmax_overlaps = overlaps.argmax(axis=0) # 求出与每个gt重叠面积最大的anchor
gt_max_overlaps = overlaps[gt_argmax_overlaps,
                                   np.arange(overlaps.shape[1])] # 取出与每个gt重叠面积最大的
gt_argmax_overlaps = np.where(overlaps == gt_max_overlaps)[0]

# 重叠面积小于阈值0.3的标注为0
if not cfg.TRAIN.RPN_CLOBBER_POSITIVES:
    # assign bg labels first so that positive labels can clobber them
    labels[max_overlaps < cfg.TRAIN.RPN_NEGATIVE_OVERLAP] = 0

# fg label: for each gt, anchor with highest overlap 与gt图重叠最大的对应anchor分类被设置为1
labels[gt_argmax_overlaps] = 1

# fg label: above threshold IOU 将与gt重叠的面积大于阈值0.7的anchor也将其分类设置为1
labels[max_overlaps >= cfg.TRAIN.RPN_POSITIVE_OVERLAP] = 1

if cfg.TRAIN.RPN_CLOBBER_POSITIVES:
    # assign bg labels last so that negative labels can clobber positives
    labels[max_overlaps < cfg.TRAIN.RPN_NEGATIVE_OVERLAP] = 0

论文中说从所有anchor中随机选取256个anchor,前景128个,背景128个。注意:那种label为-1的不会当前景也不会当背景。
下面这两段代码是前一部分是在所有前景的anchor中选128个,后一部分是在所有的背景anchor中选128个。如果前景的个数少于了128个,就把所有的anchor选出来,差的由背景部分补。这和Fast RCNN选取ROI一样。

# subsample positive labels if we have too many 要是运行到这里得到的分类为1的太多了那就进行采样
# 从所有label为1的anchor中选择128个,剩下的anchor的label全部置为-1
num_fg = int(cfg.TRAIN.RPN_FG_FRACTION * cfg.TRAIN.RPN_BATCHSIZE) # 采样的阈值
fg_inds = np.where(labels == 1)[0]
if len(fg_inds) > num_fg:
    disable_inds = npr.choice(
        fg_inds, size=(len(fg_inds
  • 7
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
PV-RCNN(Point-Voxel-Region-based Convolutional Neural Network)是一种用于点云目标检测的神经网络模型,其主要思想是将点云数据转换为体素(Voxel)表示,通过将点云划分为不同的体素,将点云数据转换为体素特征图,然后通过卷积神经网络提取特征,最后使用区域建议网络(Region Proposal Network,RPN)和ROI池化层进行目标检测。 PV-RCNN的代码解读主要可以从数据预处理、网络模型构建和训练步骤三个方面进行说明。 首先,在数据预处理阶段,代码会先将原始的点云数据转换为体素表达。将点云数据进行体素化可以提高计算效率,同时也有利于在三维空间中建模目标。其次,代码会对体素进行特征提取,通过卷积神经网络对体素特征图进行卷积和池化操作,从而得到更具有表达能力的特征表示。 其次,在网络模型构建阶段,代码会搭建PV-RCNN的网络结构,包括体素特征提取网络和目标检测网络。体素特征提取网络通常采用多层卷积神经网络,用于提取点云体素的特征表示。目标检测网络则包括RPN和ROI池化层,用于生成目标的候选框,并对候选框进行分类和回归。 最后,在训练步骤中,代码会使用已标注的点云数据进行网络模型的训练。通常采用交叉熵损失函数进行分类损失计算,并使用边界框回归损失函数计算位置预测的误差。通过反向传播和优化算法,更新网络参数,使网络能够更好地适应目标检测的任务。 通过对PV-RCNN代码解读,我们可以更深入地理解其工作原理和实现方式,从而更好地应用于点云目标检测的研究和实践。
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值