这里省略了特征提取模块部分,个人感觉没什么好讲的,就是选用一个网络充当特征提取器,这个不是我们这个系列的重点,后面讲的部分都是以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.
"""
base_anchor
= np
.array
([1, 1, base_size
, base_size
]) - 1
ratio_anchors
= _ratio_enum
(base_anchor
, ratios
)
anchors
= np
.vstack
([_scale_enum
(ratio_anchors
[i
, :], scales
)
for i
in xrange(ratio_anchors
.shape
[0])])
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
(16∗8)∗(16∗8)=128∗128的区域
(
16
∗
16
)
∗
(
16
∗
16
)
=
256
∗
256
(16*16)*(16*16)=256*256
(16∗16)∗(16∗16)=256∗256的区域,
(
16
∗
32
)
∗
(
16
∗
32
)
=
512
∗
512
(16*32)*(16*32)=512*512
(16∗32)∗(16∗32)=512∗512的区域。
宽高变化
def _ratio_enum(anchor
, ratios
):
"""
对设定好的Anchor进行宽高变化,得到不同宽高比的Anchor
"""
w
, h
, x_ctr
, y_ctr
= _whctrs
(anchor
)
size
= w
* h
size_ratios
= size
/ ratios
ws
= np
.round(np
.sqrt
(size_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