这里省略了特征提取模块部分,个人感觉没什么好讲的,就是选用一个网络充当特征提取器,这个不是我们这个系列的重点,后面讲的部分都是以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
4
=
16
2^4=16
24=16,
2
5
=
32
2^5=32
25=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为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