主要参考以下文章以及视频教程,十分感谢:
Faster rcnn/Mask rcnn/FPN
逐字理解目标检测simple-faster-rcnn-pytorch-master代码
Faster_RCNN 2.模型准备(上)
faster rcnn代码来自simple-faster-rcnn-pytorch
因为Faster-RCNN也是从RCNN逐步发展过来的,这里从RCNN–> Fast RCNN --> Faster RCNN逐步讲起。
第一部分 RCNN前后
我们知道对于一些分类网络的原理比较简单,但是对于目标检测来说,是肯定需要用到这些分类网络提取特征来进行识别,那么怎么确定需要识别的位置呢?这些方法也是逐步演进过来的,这里从最初的讲起。
基于滑动窗口的目标检测
滑动窗口是最开始我们能想到的方法,缩放不同的尺寸,从左到右逐个遍历,当然这种方法是比较耗时的。
Selective Search (RCNN)
RCNN采用Selective Search方法,根据颜色或者纹理将相似的区域聚集到一起,得到外围的Bounding Box进行作为识别的候选框。
Bounding-Box regression (RCNN)
这里的Bounding-Box回归一直沿用到faster-RCNN,这里我们结合代码讲解一下
加入我们利用Selective Search得到一个Proposal(图中车身蓝色框),而我们的Ground truth为车身的红色框,得到蓝色框和红色框存在一定的偏移。
因为这里的Proposal产生的Bounding-Box一般是固定的,而和Grond truth存在偏差变换我们要从网络中学习,这里使用dx,dy,dw,dh来作为我们要学习的参数。
图左下角的Mapping表示的就是这种变换,而右下角的tx,ty,tw,th是对应的dx,dy,dw,dh就是我们要学到的目标。
我们在faster-RCNN的代码中可以找到这段的实现,在model/utils/bbox_tools.py中的loc2bbox函数中可以找到
def loc2bbox(src_bbox, loc):
if src_bbox.shape[0] == 0:
return xp.zeros((0, 4), dtype=loc.dtype)
src_bbox = src_bbox.astype(src_bbox.dtype, copy=False)
src_height = src_bbox[:, 2] - src_bbox[:, 0]
src_width = src_bbox[:, 3] - src_bbox[:, 1]
src_ctr_y = src_bbox[:, 0] + 0.5 * src_height
src_ctr_x = src_bbox[:, 1] + 0.5 * src_width
dy = loc[:, 0::4]
dx = loc[:, 1::4]
dh = loc[:, 2::4]
dw = loc[:, 3::4]
ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis]
ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis]
h = xp.exp(dh) * src_height[:, xp.newaxis]
w = xp.exp(dw) * src_width[:, xp.newaxis]
dst_bbox = xp.zeros(loc.shape, dtype=loc.dtype)
dst_bbox[:, 0::4] = ctr_y - 0.5 * h
dst_bbox[:, 1::4] = ctr_x - 0.5 * w
dst_bbox[:, 2::4] = ctr_y + 0.5 * h
dst_bbox[:, 3::4] = ctr_x + 0.5 * w
return dst_bbox
代码中loc就是需要学习的变换,在faster RCNN 的RPN中候选框的生成中,需要用到此函数。
在RCNN也是比较耗时,主要原因有:Selective Search要产生2000个Bounding Box比较耗时,产生的ROI都要送入到网络中训练,其中有很多重复计算。
第二部分 Fast RCNN
主要观点有:直接在feature map上将ROI拿出来,在进行后续的计算,将原图中ROI位置映射到feature map上,这里提取出来的ROI大小是不一样的,这里使用ROI pooling得到大小一样的,然后送入到后续分类网络中。
ROI pooling
如上图所示,假如我们拿到的feature map为Input为8×8大小,而我们的Region proposal为左下5×7大小,而我们最终需要这个proposal要输出一定大小的特征,加入我们这里定义为2×2大小,那么就将proposal切成2×2大小,再对每个格子进行maxpooling.
我们在Faster RCNN找到这部分代码,在model/roi_module.py我们可以找到RoIPooling2D类
class RoIPooling2D(t.nn.Module):
def __init__(self, outh, outw, spatial_scale):
super(RoIPooling2D, self).__init__()
self.RoI = RoI(outh, outw, spatial_scale)
def forward(self, x, rois):
return self.RoI(x, rois)
通过定义其中self.RoI我们可以在model/utils/roi_cupy.py找到ROI Pooling实现
float maxval = is_empty ? 0 : -1E+37;
// If nothing is pooled, argmax=-1 causes nothing to be backprop'd
int maxidx = -1;
const int data_offset = (roi_batch_ind * channels + c) * height * width;
for (int h = hstart; h < hend; ++h) {
for (int w = wstart; w < wend; ++w) {
int bottom_index = h * width + w;
if (bottom_data[data_offset + bottom_index] > maxval) {
maxval = bottom_data[data_offset + bottom_index];
maxidx = bottom_index;
}
}
}
top_data[idx]=maxval;
argmax_data[idx]=maxidx;
}
值得注意的是,代码是借助cuda编程实现的,其中每个ROI里面的小块单独分配了一个线程块。
fast rcnn同样需要从2000个proposal中去识别,所以还是比较耗时的
第三部分 Faster RCNN
RPN网络
anchor的概念
Faster RCNN网络中使用RPN网络去代替Fast RCNN网络中的 Region proposal网络,首先我们先要理解anchor的概念
假如我们拿到的feature map大小是8×8大小,那么这上面的每个点都进行预测,每个点实际上对应9个anchor,每个anchor对应不同的大小和长宽比,例如下图:
右图实际上是左图中心那个点对应的anchors图,右图上的9个框分别代表不同长宽比和大小的anchor,例如红色框就代表1:1、1:2、2:1不同长宽比的anchor,绿色框和蓝色框达标相同比例下不同大小的anchor. 注意的是每个size下的面积是相同的,例如所有红色框面积一样,绿色框面积一样…。
每个anchor的作用是负责预测该区域是不是存在有物体(二分类,有或者没有)以及Bounding Box的回归,不同大小的anchor面向的是不同大小的物体。所以faster RCNN是稠密预测的网络。
如果不同的框都预测到该框里面有物体,那么如何确定是哪个框呢?这里使用IOU(交并比)来进行区分。
这里关于anchor的生成,我们在model/utils/bbox_tools.py中generate_anchor_base函数中可以找到其实现:
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],
anchor_scales=[8, 16, 32]):
py = base_size / 2.
px = base_size / 2.
anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),
dtype=np.float32)
for i in six.moves.range(len(ratios)):
for j in six.moves.range(len(anchor_scales)):
h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])
index = i * len(anchor_scales) + j
anchor_base[index, 0] = py - h / 2.
anchor_base[index, 1] = px - w / 2.
anchor_base[index, 2] = py + h / 2.
anchor_base[index, 3] = px + w / 2.
return anchor_base
可以看到其生成的9个anchor.
两个bbox的IOU计算,在model/utils/bbox_tools.py中generate_anchor_base函数中可以看到:
def bbox_iou(bbox_a, bbox_b):
if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
raise IndexError
# top left
tl = xp.maximum(bbox_a[:, None, :2], bbox_b[:, :2])
# bottom right
br = xp.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])
area_i = xp.prod(br - tl, axis=2) * (tl < br).all(axis=2)
area_a = xp.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)
area_b = xp.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)
return area_i / (area_a[:, None] + area_b - area_i)
至此,Faster RCNN所有知识点,但是Faster RCNN有一个缺点,只是一个单尺度的目标检测算法,那怎么改进呢,见补充部分。
补充
如上图VGG网络图中,Faster RCNN可能从后面的7×7×512的feature map中来进行识别,其对应的224×224大小的输入图像已经损失很多了,特别对一些小目标。
FPN网络
为了解决掉此问题,FPN就设法用到一些浅层的feature map,又因为浅层的网络层提取的特征信息比较少,不利于分类识别。
我们先看一下对于多尺度识别物体一般是怎么做的,上图中,红色框代表图像数据,蓝色框代表送入网络中训练得到的feature map,首先(a)图中是利用图像金字塔去做,将不同尺度的图像送到网络中预测,这种方法需要输入多个图像,比较耗时;(b)中是直接对最后一层feature map去预测,Faster RCNN就是采取这种做法;(c)是SSD中用到的一种方法,用到低层和高层特征,但是由于低层的特征信息较弱,可能会存在分类错误;(d)FPN:特征金字塔网络,将低层特征和高层特征进行融合,网络右边采取上采样和相加连接方式做多次的特征融合。
bootom-up采用特征提取网络例如VGG;Top-down采用上采样方法,上采样方法可以采用双线性插值或者反卷积;
![RPN](https://img-blog.csdnimg.cn/202001051946229.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTMyNDE1ODM=,size_16,color_FFFFFF,t_70 = =530x400)
FPN可以用到不同的网络中去,这里以ResNet作为骨架的FPN例子,网络右侧从M2,M3,M4,M5得到不同尺度的feature map作为Faster RCNN中 RPN网络的输入,下图是结合FPN后的Faster RCNN:
FPN就是一种特殊的特征融合方法,那么对应不同尺度的feature map我们的ROI应该从那个feature map中去提取呢?
上面的公式给出了不同ROI大小对应的feature map的层数,这里的ROI是指从RPN网络中给出的proposal,所以我们就可以针对不同大小的proposal找到对应的feature map.
Faster rcnn/Mask rcnn/FPN课程中从54分钟起开始讲解Mask RCNN,感兴趣的可以去看看。