anchors如何获得_【目标检测】Faster R-CNN代码解析

本文主要和大家分享一下我个人对于Faster R-CNN代码的解读,希望可以帮助到大家!整体框架train.py

faster_rcnn.py

rpn.py

generate_anchors.py

anchor_target.py

proposal.py

proposal_target.py

1.整体框架

在详细介绍代码细节之前,我们可以先理清Faster RCNN的整体框架和整个训练过程。整个过程涉及到三个文件:train.py,faster_rcnn.py和rpn.py。在这里,我们只需要理清主线,所以我简化了这3个文件里的代码,完整的代码可以参考我的GitHub:

1.1 train.py

train.py就是我们训练时运行的文件,主要作用就是调用FasterRCNN网络得到分类和检测结果,然后计算loss,再用梯度下降优化网络,大致可以总结为以下5个步骤:加载训练数据

定义模型FasterRCNN

将数据输入到模型中,并得到模型的输出

根据模型的输出,计算loss,loss就是faster_rcnn的分类loss和回归loss,以及rpn的分类loss和回归loss的均值和

进行反向传播

在这几步中,最核心的是模型,所以下一步就要去看一下FasterRCNN是如何实现的。

1.2 faster_rcnn.py

在faster_rcnn.py中主要定义了FasterRCNN这个类,在这个类中构建了Faster RCNN整个网络,也很清楚的给出了整个流程,具体包括以下步骤:首先使用backbone网络提取输入图片的特征

使用RPN网络来提取rois

如果是训练,得到proposal_target,即分类和回归的ground truth,后续计算faster rcnn的loss时需要用到

使用roi_pooling得到rois的feature map

使用classifier提取特征

使用faster_rcnn_cls得到分类结果

使用faster_rcnn_reg得到回归结果

如果是训练,计算分类loss

如果是训练,计算回归loss

在FasterRCNN中我们需要重点关注的是rpn网络提取proposal,以及proposal_target函数。其中rpn是流程的主线,所以我们接下来先讲解rpn网络,对于proposal_target,我们之后会详细讲解。

1.3 rpn.py

RPN网络的结构是在rpn.py中实现的,主要作用就是计算anchor进行分类和回归结果,然后根据分类和回归结果调用proposal函数得到proposals(rois),大致可以总结为以下几步:对于输入的feature map先用rpn_conv进行卷积

然后使用rpn_cls卷积层得到分类结果

同时使用rpn_reg卷积层得到回归结果

然后之后再调用proposal函数得到proposals(rois)

如果是训练过程,那么使用调用anchor_target产生rpn网络中分类和回归的ground truth值,之后在计算rpn的loss时会用到

如果是训练过程,那么计算分类loss

如果是训练过程,那么计算回归loss

RPN完成之后,返回rois和loss,这就相当于完成上述FasterRCNN中的第2步了,然后在FasterRCNN中接着完成剩余的步骤,然后FasterRCNN会返回结果,这就完成了上述Train中的第3步了,然后在Train中接着完成其他的步骤便结束了一个完整的流程。

流程中有几个重要的函数需要详细讲解,包括anchor_target,proposal和proposal_target。

2. generate_anchors.py

因为在anchor_target和proposal函数中都有用到这个generate_anchors函数,所以我们先介绍这个函数,从函数名字中我们就可以看出来它是用来生成anchor的,具体来讲,该函数是用来生成9种不同size,不同宽高比的base anchor的。

我们首先来看main函数,main函数中主要调用了generate_anchors()函数来生成anchors,同时也给生成anchors的过程记了个时。

2.1 main函数

2.2 generate_anchors函数

好了,接下来我们应该看generate_anchors()函数,看它是如何生成anchors。

在generate_anchors函数里,主要包括三部分:首先生成一个base_anchor = [0, 0, 15, 15],其中(0, 0)是anchor左上点的坐标,(15, 15)是anchor右下点的坐标,那么这个anchor的中心点的坐标是(7.5, 7.5)。

然后产生ratio_anchors,就是将base_anchor和ratios[0.5, 1, 2]传入到_ratio_enum()函数,ratios代表的是三种宽高比。

然后产生anchors,这是将ratio_anchors和scales=[8, 16, 32]传入到_scale_enum()函数,来产生9个不同尺寸不同宽高比的anchors,其中scales就是指三种不同的尺寸。

所以,我们接下来应该重点看_ratio_enum()和_scale_enum()函数。在介绍这两个函数之前,我们先看另外两个函数,这个需要用到,所以就先介绍。这个两个函数分别是_whctrs()和_mkanchors()函数。

2.3 _whctrs和_mkanchors

我们这个表示一个anchor的形式有两种,一种是用左上点和右下点的坐标的形式表示,另一种是使用中心点和宽高的形式表示。那_whctrs()和_mkanchors()这两个函数就是用来转换anchor的表示形式的,其中:_whctrs()是给定anchor左上点和右下点坐标求出anchor的中心点和宽高。

_mkanchors()是给定anchor的中心点和宽高求出anchor的左上点和右下点坐标。

2.4 _ratio_enum

接下来,我们就能介绍_ratio_enum()函数了,它是用来生成不同宽高比的anchors的。

首先函数求出base_anchor的中心点的坐标和宽高,然后再求出anchor的面积256。之后分别求出不同ratio下的面积,分别为512,256,128,然后再开方,求出不同ratio下的3种宽。然后再让宽各自乘以对应的ratio,便得到了相应的高。为什么要这样算呢,其实是为了保证最终生成的3个不同宽高比的anchor的面积尽量接近。最后再根据中心点和三对不同的宽高比来得到最后的anchor(转成左上和右下坐标表示的形式)。

2.5 _scale_enum

接下来,我们介绍_scale_enum()函数,它是用来生成不同尺寸的anchors的。

_ratio_enum函数生成的3种不同宽高比的anchor分别输出_scale_enum函数中,对于每一个anchor都先获取它的宽高和中心点,然后让宽和高分别按照3种尺寸放大,于是每种宽高比的anchor可以得到三种尺寸的anchors,所以一共会有9个anchor。

图1所示为generate anchors的整个过程以及每个函数负责的部分。

在generate_anchors中只是生成9种anchor,并没有将anchor对应到图片上,这个对应工作将在anchor_target和proposal中实现。

3. anchor_target.py

在anchor_target.py中实现了anchor_target函数,这个函数主要是在rpn.py中使用到,它的作用是计算每个anchor的分类和回归的ground truth,在rpn中主要是在计算loss时使用。接下来,我就详细讲解一下anchor_target函数是如何实现的。

3.1 输入和输出

在介绍函数实现前,我们先了解一下函数的输入和输出值:

3.2 定义变量

3.3 生成anchor

生成anchor的过程主要包括两步:生成base anchor,即调用generate_anchors函数生成9种基本的anchor

在图片上生成对应的anchor

为了方便解释,我在代码和图例中都把RPN输出的feature map的大小设为(4, 4)。大致过程我已经在代码的注释部分写明白了,我觉得其中最难理解的就是shifts,还有shifts和anchors相加的部分。所以我用图2来给大家可视化一下过程,shifts就相当于图2中的网格,只不过shifts用4行表示了,其实shifts的前两行就代表了16个坐标,一行为x坐标,第二行为y坐标,而shifts的后两行和前两行一摸一样,表示了同样的16个坐标,这是为了方便和anchor相加,因为每个anchor也是用一个思维的向量表示,其中前两个表示左上点的x, y坐标,后两个表示右下点的x, y坐标。所以将shifts的前两行和anchor的前两行相加,就相当于将左上点平移了一定的距离,然后将shifts的后两行和anchor的前两行相加,就相当于将右下点也平移了同样的距离,因此就相当于把anchor平移到了另一个地方,具体如图2所示,例如anchor和shifts的(32, 32, 32, 32)这一列相加,就相当于把anchor移动到了原图(32, 32)的位置上。按照同样的方法移动anchor,最后在原图上就生成了

equation?tex=width%5Ctimes+height%5Ctimes9 个anchors。图2 在原图中生成anchor

3.4 过滤anchor

生成的anchor中,有些anchor的边界超过了图片的边界,那这些anchor显然是不可取的,所以在这步要把这些anchor过滤掉,只保留inside anchor。

3.5 求取IoU值

求inside anchor和gt_boxes之间的IoU值,并求取最大值,这是为之后计算label_target和bbox_target坐铺垫。

3.6 label_target

接下来就是给inside anchor赋label_target值了,赋值规则如下:对于每个anchor,如果它和gt_boxes最大IoU值小于RPN_NEGATIVE_OVERLAP(0.3)时,认为该anchor为背景,即label=0

当某个anchor和gt_boxes有最大IoU值,那该anchor被认为是正样本,即label=1

对于每个anchor,如果它和gt_boxes最大IoU值大于RPN_POSITIVE_OVERLAP(0.7)时,认为该anchor为正样本,即label=1

对于规则1,代码中写了两遍,这其实是由cfg.TRAIN.RPN_CLOBBER_POSITIVE来控制规则1是在规则2和3之前还是之后进行。如果是规则1先进行,那其中某些赋为0的anchor的label值可能会被规则2和3修改。如果规则1是后进行,那么规则1可能会修改规则2和3的赋值结果。

另外,代码中算keep的那行代码还是比较难用语言解释的,所以我用了一个图来表示,图中假设共有3个anchor和3个gt_boxes。如下图所示,对于overlaps,我们先求每个gt_boxes的最大IoU值,然后再执行expand:

expand之后和overlaps进行比较,得到比较结果矩阵,然后在求sum,便得到代码中的keep,这个keep中如果值大于0,则代表这个anchor和gt_boxes有最大IoU值,那这个anchor的label就为1,否则为0:

3.7 删选正负样本

在训练时,为了使得正负样本保持平衡,需要对正负样本进行一个初步的删选。大致过程:首先计算正样本和负样本最大的个数,如果样本数量超出这个最大数,那就在样本中随机挑选出多余的样本删掉。

3.8 bbox_target

接下来就是计算bbox_target了,即将anchor移动到它对应的gt_box的位置变化deltas,而每个anchor对应的gt_box就是和这个anchor有最大IoU值的gt_box。

同时,每个还要计算bbox_inside_weights和bbox_outside_weights,这主要是计算loss时要用到的,其中bbox_inside_weights相当于loss公式中的

equation?tex=p_%7Bi%7D%5E%7B%2A%7D ,用来控制只让正样本参与loss计算,而bbox_outside_weights相当于公式中的

equation?tex=%5Clambda+%5Cfrac%7B1%7D%7BN_%7Breg%7D%7D ,用来控制和分类loss之间的平衡。

3.9 unmap

因为rpn网络会输出H*W*A个anchor的分类和回归信息,但是我们之前求的ground truth都是针对于inside anchor求的,所以在这一步,我们需要把那些outside anchor的ground truth target也补回来。

主要是调用_unmap函数实现的,_unmap函数的定义如下:

3.10 Resize并返回

将bbox_target,bbox_inside_weights和bbox_outside_weights resize为规定大小,然后返回,至此,整个anchor_target就讲解完啦!

4. proposal.py

在proposal.py中实现了proposal函数,这个函数主要是在rpn.py中使用到,它的作用是根据rpn的分类和回归结果,对anchor进行位置调整,然后得到proposal,得到的proposal将在faster_rcnn.py中用到。接下来,我就详细讲解一下proposal函数是如何实现的。

4.1 输入和输出

在介绍函数实现前,我们先了解一下函数的输入和输出值:

4.2 定义变量

4.3 生成anchor

生成anchor的这部分代码和anchor_target.py中的相同,可以参考之前的讲解。

4.4 生成proposals

生成anchor之后,然后bbox_transform_inv函数根据rpn网络的回归结果对anchor的坐标进行调整,得到proposal,之后再对超出图片边界的proposal进行裁剪。

4.5 过滤proposal

我们要过滤掉一些size比较小的proposal,主要是用_filter_proposal来实现的。

_filter_proposal函数的定义如下:

4.6 分配概率值

因为之后需要根据proposal的概率值进行nms,所以在这一步我们要先为proposal分配rpn网络预测的概率值。

4.7 nms

我们要对proposal进行非极大值抑制,去掉那些重复度高的proposal,具体过程如下:

4.8 得到rois并返回

将保留下来的proposal赋给rois并返回,至此proposal的过程就讲解完啦!

5. proposal_target.py

在proposal_target.py中实现了proposal_target函数,这个函数主要是在faster_rcnn.py中使用到,它的作用是计算每个rois的分类和回归的ground truth,在FasterRCNN中主要是在计算loss时使用。接下来,我就详细讲解一下proposal_target函数是如何实现的。

5.1 输入和输出

在介绍函数实现前,我们先了解一下函数的输入和输出值:

5.2 定义变量

注意,在这段代码里还把rois和gt_boxes拼接在一起了,这样做是为了增加正样本的个数。

5.3 计算IoU

求merged_rois和gt_boxes之间的IoU值,并求取最大值,这是为之后计算label_target和bbox_target坐铺垫。

5.4 label_target

计算rois的label_target值,这个计算规则比较简单。首先,计算每个rois和gt_boxes的最大IoU值,然后如果一个rois和某个gt_box有最大的IoU,那么这个gt_box的label就是该rois的label_targets,然后根据最大IoU值大大小来区分正负样本:当roi和gt_boxes的最大IoU大于等于FG_THRESH(0.5)时,则认为这个roi是正样本

当roi和gt_boxes的最大IoU在[BG_THRESH_LO(0.1), BG_THRESH_HI(0.5))区间内时,则认为这个roi是负样本

5.5 删选正负样本

在训练时,为了使得正负样本保持平衡,需要对正负样本进行一个删选。大致过程:首先计算正样本和负样本最大的个数,如果样本数量超出这个最大数,那就在样本中随机挑选出多余的样本删掉。

5.6 bbox_target

接下来就是计算bbox_target了,即将rois移动到它对应的gt_box的位置变化deltas,而每个rois对应的gt_box就是和这个anchor有最大IoU值的gt_box。值得注意的是,rois的bbox_target是与类相关的,就是对于每一类都会预测4个回归值,所以bbox_target的赋值也是和类相关的,对于某个roi,如果这个roi属于第3类,那就将这个ground truth值赋给第3类对应的回归值,而其他类对应的回归值的ground truth为0。

最后将得到的rois, label_targets, bbox_target, bbox_inside_weights, bbox_outside_weights返回便可以啦!

关于Faster RCNN的代码就先解析到这里啦,完整的代码已经放到我的GitHub上啦(Jacqueline121/Faster_RCNN_pytorch),大家记得star哦!

本文都是根据个人理解编写的,希望可以帮到大家。此外,如有误,烦请指正。如果喜欢,请点赞哦,谢谢~

我将持续更新目标检测领域的经典paper,欢迎大家订阅哦!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值