Faster RCNN原理及Pytorch代码解读——RPN(一):Anchor的生成

    技术2022-07-10  149

    这里省略了特征提取模块部分,个人感觉没什么好讲的,就是选用一个网络充当特征提取器,这个不是我们这个系列的重点,后面讲的部分都是以VGG16作为特征提取网络,需要注意一点就是由于VGG16的网络设计,经过conv层不改变特征图的尺寸,经过pool层特征图尺寸会缩小到原来的一半。VGG16一共有5个pool层,我们选用第4个pool层的输出作为提取出来的特征图,这样相比于原图就缩小了16倍,即下采样倍数是16。

    为了方便理解,假设输入图像的维度为3×600×800,那么经改特征提取网络得到的特征图大小就512×37×50。

    Anchor的生成

    Anchor(锚框)

    理解什么是Anchor对理解RPN和整个Faster RCNN都十分重要。 Anchor本质上是在原图上预先定义好(这个预先定义十分关键)的一系列大小不一的矩形框,为什么要引入Anchor呢?这是因为之前的目标检测都是模型直接回归边框的位置,而通过引入Anchor相当于加入了强先验信息,然后通过锚框再去筛选与修正,最后再得到预测框。这样做的好处在与是在Anchor的基础上做物体检测,这样要比从无到有的直接拟合物体的边框容易一些。具体的做法就是让模型去预测Anchor与真实边框的偏移值,而不是直接预测边框的坐标。 Anchor有了,那怎么将Anchor跟特征图联系在一起呢?具体做法是, 首先对feature map进行3×3的卷积操作, 得到的每一个点的维度是512维, 这512维的数据对应着原始图片上的很多不同的大小与宽高区域的特征,这些区域的中心点都相同。 如果下采样率为默认的16, 则每一个点的坐标乘以16即可得到对应的原图坐标。如下图所示 这样的话,特征图的每一个点都对应着9个锚框,因此一共有37×50×9=16650个Anchors。且由于这9个Anchors大小宽高不同, 对应到原图基本可以覆盖所有可能出现的物体。

    下面来看代码,看完代码就会知道生成Anchor了。 代码在lib/modle/rpn/generate_anchors.py中,直接运行作者demo中的generate_anchors.py可以得到以下输出,其中每一行表示锚框的左上角和右下角坐标。

    主要的函数为:generate_anchors函数

    def generate_anchors(base_size=16, ratios=[0.5, 1, 2], scales=2**np.arange(3, 6)): """ Generate anchor (reference) windows by enumerating aspect ratios X scales wrt a reference (0, 0, 15, 15) window. """ # 首先创建一个基本anchor为[0, 0, 15, 15],分别是左上角坐标和右下角坐标 base_anchor = np.array([1, 1, base_size, base_size]) - 1 # 将基本anchor进行宽高变化,生成三种宽高比的anchor ratio_anchors = _ratio_enum(base_anchor, ratios) # 将上述anchor再进行尺度变化,得到最终的9种anchors anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales) for i in xrange(ratio_anchors.shape[0])]) # 返回对应于feature map大小的anchors return anchors

    参数有三个:

    1.base_size=16 这个参数指定了最初的类似感受野的区域大小,因为经过多层卷积池化之后,feature map上一点的感受野对应到原始图像就会是一个区域,这里默认设置为16是因为使用了VGG16作为特征提取器,前面说了其下采样率为16,所以feature map上一点对应到原图的大小为16x16的区域。当然也可以根据不同的特征提取网络来设置。

    2.ratios=[0.5,1,2] 这个参数指的是要将16x16的区域,按照1:2,1:1,2:1三种比例进行变换。

    3.scales=2**np.arange(3, 6) 这个参数是要将输入的区域,的宽和高进行三种倍数,分别是 2 3 = 8 2^3=8 23=8 2 3 = 16 2^3=16 23=16 2 3 = 32 2^3=32 23=32倍的放大,如16x16的区域变成 ( 16 ∗ 8 ) ∗ ( 16 ∗ 8 ) = 128 ∗ 128 (16*8)*(16*8)=128*128 (168)(168)=128128的区域 ( 16 ∗ 16 ) ∗ ( 16 ∗ 16 ) = 256 ∗ 256 (16*16)*(16*16)=256*256 (1616)(1616)=256256的区域, ( 16 ∗ 32 ) ∗ ( 16 ∗ 32 ) = 512 ∗ 512 (16*32)*(16*32)=512*512 (1632)(1632)=512512的区域。

    宽高变化

    def _ratio_enum(anchor, ratios): """ 对设定好的Anchor进行宽高变化,得到不同宽高比的Anchor """ # 获取锚框的宽、高和中心点坐标 w, h, x_ctr, y_ctr = _whctrs(anchor) # 得到锚框尺寸,size为256 size = w * h # 对尺寸进行变换,size_ratios为[128,256,512] size_ratios = size / ratios # 将尺寸开根号作为锚框的宽 ws = np.round(np.sqrt(size_ratios)) # 将宽乘ratios得到锚框的高 hs = np.round(ws * ratios) # 还原为锚框的左上角和右下角坐标的表现形式 anchors = _mkanchors(ws, hs, x_ctr, y_ctr) return anchors def _whctrs(anchor): """ Return width, height, x center, and y center for an anchor (window). """ w = anchor[2] - anchor[0] + 1 h = anchor[3] - anchor[1] + 1 x_ctr = anchor[0] + 0.5 * (w - 1) y_ctr = anchor[1] + 0.5 * (h - 1) return w, h, x_ctr, y_ctr def _mkanchors(ws, hs, x_ctr, y_ctr): """ Given a vector of widths (ws) and heights (hs) around a center (x_ctr, y_ctr), output a set of anchors (windows). """ ws = ws[:, np.newaxis] hs = hs[:, np.newaxis] anchors = np.hstack((x_ctr - 0.5 * (ws - 1), y_ctr - 0.5 * (hs - 1), x_ctr + 0.5 * (ws - 1), y_ctr + 0.5 * (hs - 1))) return anchors

    尺寸变化

    def _scale_enum(anchor, scales): """ Enumerate a set of anchors for each scale wrt an anchor. """ w, h, x_ctr, y_ctr = _whctrs(anchor) ws = w * scales hs = h * scales anchors = _mkanchors(ws, hs, x_ctr, y_ctr) return anchors
    Processed: 0.012, SQL: 9