0%

目标检测篇:Faster R-CNN 系列

又来开计算机视觉的坑了,这次一定做好整理和记录,包括算法内容和程序。做好训练、保存和推理的框架,下次需要的时候直接用。做目标检测的东西,于是决定从 Fast R-CNN 重头来过了。其中,Fast R-CNN 只有理论,Faster-RCNN 部分含有代码,不过会在下一篇博客了。

既然是目标检测,就需要做两件事情,目标在哪,目标是什么。对于目标在哪的问题,可以让网络生成一个目标框,目标框有四部分组成:$(x,y,w,h)$,表示物体的起始点坐标和框的宽度与高度;对于目标是什么的问题,就是传统神经网络的多分类问题了。

Fast R-CNN

算法步骤与网络结构图2

  1. 首先使用 selective search 算法1搜索目标区域候选框,以下简称 SS 算法
  2. 将图像输入 backbone,一个超大规模的特征提取网络,得到一个特征图
  3. 将 SS 生成的候选框映射到特征图中,得到每个候选框的特征矩阵
  4. 将每个候选框的特征矩阵通过 ROI(region of interest)-pooling 层缩放到 $7\times 7$ 的矩阵
  5. 将矩阵展平,全连接,得到一系列的预测结果。包括预测概率和目标框,而后反向传播
  6. 这里需要注意的是,最后预测分类和预测目标边界框的两个全连接是并联

详解

对于算法步骤 3,特征图映射问题。是为了减少计算量,不用为每个候选框单独计算特征矩阵;而是计算一个特征矩阵后,直接选择候选框的内容。选择方式也很简单,矩阵映射就行。

正样本与负样本

训练期间,并没有训练全部候选框的特征矩阵,只使用了其中的一小部分,比如只训练 64 个。正样本,表示含有待检测目标;负样本,表示背景区域。选用负样本的原因是,要让网络能正确识别背景,并不是所有图片都含有目标。

  • 正样本:候选框与真实框的 IOU 大于 0.5,选择其中的一部分进入网络
  • 负样本:候选框与真实框的 IOU 在 0.1 到 0.5 之间

论文中还提到了对数据做增强,当然这些并不是重点。IOU(Intersection over Union)的意思是,图像的交除以图像的并。就是蓝色区域的面积除以绿色区域的面积。

ROI-pooling

ROI-pooling 层的目的在于,不限制输入图像的大小。因为 ROI 后面的全连接层肯定是固定到某一个特殊维度的,而每个候选框对应特征矩阵的大小也不一致,所以,需要使用 ROI-pooling 将不同大小的特征矩阵映射到同一大小。具体方法如下,算了如图吧,如对任意大小的特征矩阵映射为 $2\times2$的特征矩阵,取其中的最大值或平均值。$7\times7$ 也是同一个道理。

预测输出与反向传播

先来看概率预测。因为含有负样本,因此网络要识别 $N$ 的类的话,网络输出的概率就是 $N+1$,多一个背景类。ROI-pooling 后,经过两个全连接得到特征向量。特征向量经过第一个并联的全连接,softmax 后输出标签概率;经过第二个并联的全连接,输出目标框的位置参数。

  • 对于第一个并联的全连接而言,只需要和真实标签进行交叉熵3即可,分类损失记为 $L_1$;

接下来重点讲输出目标边界框的部分,如何使输出的目标框越来越接近真实的目标框呢?对于边界框回归器,也就是第二个并联的全连接,输出 N+1 个类别候选边界框的回归参数 $(d_x,d_y,d_w,d_h)$,共输出 $4(N+1)$ 个参数。我们设当前 SS 算法得到候选框的中心点坐标和宽高为 $(P_x,P_y,P_w,P_h)$,最终预测的边界框的参数 $(G_x, G_y, G_w, G_h)$ 为:

\begin{equation}
\begin{aligned}
G_x &= P_wd_x + P_x \\
G_y &= P_hd_y + P_y \\
G_w &= P_w \exp{(d_w)} \\
G_h &= P_h \exp{(d_h)}
\end{aligned}
\end{equation}

可视化一下得到的最终预测的坐标 $(G_x, G_y, G_w, G_h)$ ,也就是,红色框是之前的候选框 $(P_x,P_y,P_w,P_h)$;绿色框是标注的候选框;经过神经网络的输出 $(d_x,d_y,d_w,d_h)$ 后,移动到蓝色框位置,也就是 $(G_x, G_y, G_w, G_h)$。

在计算完候选框之后,现在就要计算损失反向传播了。假设此时真实的类别标签是 $u$,第 $u$ 个类别的目标边界框的输出为 $(d_x^u,d_y^u,d_w^u,d_h^u)$,真实目标边界框的参数为 $(v_x,v_y,v_w,v_h)$。

边界框损失的损失为:

\begin{equation}
\begin{aligned}
L_2 &= \sum_{i\in{x,y,w,h}}\text{smooth}(t_i^u-v_i) \\
\text{smooth}(x) &=
\begin{cases}
0.5 x^2 & |x| < 1 \\
|x| - 0.5 & \text{other}
\end{cases}
\end{aligned}
\end{equation}

这个损失在绝对差值大于1时不求平方,可以避免梯度爆炸。大部分回归问题都可以适用,尤其是数值比较大的时候。此时,最终的损失函数为:

\begin{equation}
L = L_1 + \lambda[\mu\geq 1]L_2
\end{equation}

$[u\geq 1]$ 表示爱佛森括号,意思是,当 $u\geq 1$ 时,$[u\geq 1] = 1$,否则 $[u\geq 1] = 0$;$u=0$时,表示当前是背景,背景是没有边界框的,也就没有边界框回归损失这一项。$\lambda$ 是权重。好滴,Fast R-CNN 记录完毕。Faster RCNN 就相对简单点了,只是在 Fast R-CNN 的基础上使用 RPN 替换了 SS 算法。

Faster-RCNN

Faster-RCNN = Fast R-CNN + RPN4 结构,用 RPN 生成特征候选框,将生成的候选框投影到特征图上得到特征矩阵,即代替了之前的 SS 操作,也就是,让 RPN 网络自己去寻找哪里可能有目标。

RPN 网络,输入为 backbone 后的 features map,用于生成 region proposal,在 features map 上每个像素生成若干个 anchors(比如9个),随后通过 softmax 判断每个 anchor 是属于 foreground(目标)或者 background(背景),再利用边界框回归器修正,获得精确的 proposal 位置。(anchors 是固定的,全程不动)。

而后,Roi Pooling 层则利用 RPN 提供的 proposals 以及 backbone 后的 feature maps,提取 proposal feature 送入后续全连接网络,再之后的操作和 Fast R-CNN 就一样了。所以重点来看 RPN,网络结构如下。

RPN 网络

在 backbone 得到特征图后,使用一个卷积核继续提纯,在 features map 上进行滑动,且通过 padding 保证滑动前的特征矩阵和滑动后的特征矩阵尺寸一致。每滑动到一个位置就生成一个一维向量,图中 256 的意思是 channel,所以单个卷积核会生成一个一维向量。

而后一维向量进入两个并联的卷积层,使用 $2k$ 个卷积核输出 $2k$ 个概率得分(背景概率和前景概率);另外一边使用 $4k$ 个卷积核输出 $4k$ 个边界框参数。$k$ 是 anchor box 的数量,卷积核的大小都是 $1\times1$。注意,这是一步卷积就生成这么多参数。

anchor box

首先在特征图上进行卷积的时候,计算当前卷积窗口中心点对应原图哪个点,然后以原图为中心,生成 $k$ 个 anchor box,anchor box 是提前给定的大小和长宽比例。如下图所示,从特征图一步步映射到原图。anchor box 里可能有目标,也可能没有目标。其中,每个 anchor 的形状不一样,因为目标大小不一样,长宽比也不一样。

前面讲过,每个 anchor 会生成 2 个概率分数和 4 个预测框参数。如对于概率分数,[0.3, 0.7] 表示有 0.3 的概率是背景,0.7 的概率是前景。对于同一目标,有 $k$ 个 anchor,就会有 $2k$ 个概率分数和 $4k$ 个边界框参数。而 anchor 的大小和比例是提前设置的,如 $128\times 128,256\times 256,512\times 512$ 大小,对应 anchor 的面积;比例一般是三种,1:1, 1:2, 2:1,对应 anchor 的长宽比。因此,每个位置,也就是每个滑动窗口,都有 9 个 anchor。

如果感受野(也就是特征图上的$3\times 3$,可能对应原图的 $200\times 200$)的大小小于 anchor box 的大小,不要惊讶,因为目标可能没在图片内。就像根据直觉,看到一个脑袋,也能猜出剩下的身子应该是多大。意思是可以通过一个小的感受野,预测一个大的物体的边界框。

在特征图上滑动时,步长为1,padding 为 1。所以每个位置都会有 anchor,舍弃掉超出原图边界框的 anchor。利用 RPN,将 anchor 根据生成的边界框回归参数,调整称为候选框,用非极大值一致筛掉一部分候选框,得到最终的候选框。

正负样本

并不是全部 anchor 都要训练,比如可以采样 256 个样本去训练,正负样本的比例是 1:1。如果正样本不到 128,那么用负样本进行补充。

  • 正样本:候选边界框与真实框的 IOU 大于 0.7;如果没有,就取与真实边界框有最大的 IOU 作为正样本(因为可能只有一小部分相交)
  • 负样本:与所有真实边界框的 IOU 低于 0.3

RPN 损失函数

第一部分是类别损失,这里的类别只有两种,前景或背景。所以损失函数表示为:

\begin{equation}
L_1 = \frac{1}{N}\sum_i(p_i, p_i^*)
\end{equation}

当前目标为前景时,$p_i^\star$ 为1;为背景时,$p_i^\star$ 为 0。

边界框回归损失和之前的 Fast R-CNN 是一样的。如果是背景,没有边界框损失函数;如果是目标,就有边界框损失函数。

Faster-RCNN

把 RPN(目标区域选择) 与 Fast R-CNN(目标检测) 组合起来,就是最终的 Faster-RCNN。至于两个网络的 loss 如何训练,这就取决于代码了,我这就去开坑写程序。在论文中:

  • RPN 和 Fast R-CNN 可以理解为互惠共赢的东西,所以不希望分开训练,这个是我个人的观点
  • 交替训练,先训练 RPN,能找到区域后,微调 Fast R-CNN 后用于初始化 RPN 网络参数,如此循环迭代。具体过程为:
    • 训练 RPN 网络,采用 ImageNet 预训练的模型进行初始化,并进行微调
    • 利用 RPN 生成 proposal,由 Fast R-CNN 训练一个单独的检测网络,该网络同样由 ImageNet 预训练模型进行初始化。此时,两个网络还未共享卷积层
    • 用检测网络初始化 RPN 训练,固定共享的卷积层,只微调 RPN 独有的层,现在两个网络实现了卷积层共享
    • 保持共享的卷积层固定,微调 Fast R-CNN 的全连接层。这样,两个网络共享相同的卷积层,构成一个统一的网络
  • 近似联合训练,RPN 和 Fast R-CNN 整合到一个网络里一起训练。前向传递生成 region proposal,在训练 Fast R-CNN 检测器将这看作是固定的、预计算的 proposal。反向传播像往常一样进行,其中对于共享层,组合来自 RPN 损失和 Fast R-CNN 损失的反向传播信号。这个解决方案很容易实现。但是这个解决方案忽略了关于 proposal 边界框的坐标的导数,因此是近似的。由 RPN 预测的边界框也是输入的参数,Fast R-CNN 中的 RoI 池化层接受卷积特征以及预测的边界框作为输入,所以理论上有效的反向传播求解器也应该包括关于边界框坐标的梯度。在上述近似联合训练中,这些梯度被忽略。

Faster RCNN 源码阅读

网络结构

不读代码 5 怎么能说自己学会了呢。读代码建议读别人注释好的,用代码建议用经得住时间检验的,比如 torch 或 mmdetection。

首先是模型的 backbone,这个没啥好说的,就是把 resnet 这样的网络筛掉最后一个分类层就得到了 backbone。之后模型就剩下 RPN 和 Head 两部分了,先来看 RPN。

RPN 由两部分组成,一部分是卷积之类的网络模型,一部分是 anchor 相关的东西。

  1. 首先生成不同大小不同比例的 anchor,比如 3 种大小,3种比例,那么 anchor 的大小就是 $9\times 4$。
  2. 之后是卷积模型,conv1 对特征图进行卷积,产生输出。loc 对输出进行卷积,得到位置参数,$9\times 4$ 大小的输出,9 是 anchors 的数量,4 表示 4 个位置参数。score 对输出进行卷积,得到 $9\times 2$ 大小的输出,因为只有前景和背景两个类。
  3. 最后是 proposal_layer,这个不是神经网络,只是单纯的选择 roi 区域,如何选择呢?生成布满特征图的 anchor,大概一万多个,然后遍历 RPN 的每一个输出(位置和分数):基于 anchor 和 loc 生成候选框,参考公式 1。筛选掉溢出图像的候选框,过滤掉很小的候选框,选择出的分较高的候选框,在进行 nms 抑制,得到最终的候选框。

Head 由两部分组成,一个是 ROI-Pooling,另一个是网络,输出分数和定位。将 RPN 得到的 roi 区域映射到特征图上,然后对他也征途进行 ROI-Pooling,取出对应的 roi,将 Pooling 后的结果送入后面的网络,计算位置和的分就可以了。

损失函数

对于图片 RPN 生成的每一个候选区域,选出正负样本:

  1. 计算 RPN 阶段生成的那一万多个 anchor 与真实目标框的 iou,选出与 anchor iou 最大的真实框 $a$、iou 值,在选择与每个真实框 iou 最大的 anchor $b$。然后遍历 $b$,将 $a$ 对应的 anchor 设置为 $b$,这样,至少保证每个真实框都存在对应的 anchor。
  2. iou 小于阈值的设为负样本,大于阈值的设为正样本。
  3. 选择对应样本的盒子,并计算这个盒子对应 anchor 的偏移量,用于预测。
  4. 计算 RPN 的定位损失,筛选出正样本,计算位置损失;计算分类损失,单纯的判断有无目标。
  5. 将 RPN 得到的 roi 计算与真实框的 iou,选择与 roi 最对应的真实框,根据 iou 筛选正负样本
  6. 将保留下来的 roi 和 backbone 抽取的特征送入 Head,计算定位损失和分类损失

预测阶段

在得到网络的检测结果后,返回 Head 检测的框、得分以及 RPN 的检测框,利用classifier网络的预测结果对建议框进行调整获得预测框,将框归一化到 0-1 之间,筛选出分类概率大于阈值的框并进行非极大值抑制,得到最终的预测结果。

不得不说,代码量是真的大,如非必要场景,还是用别人写好的吧。

扩展到 Cascade RCNN

至于经常听闻的 Cascade RCNN,只是 Faster RCNN 的一个变体,只是在 ROI head 预测部分增加了几个串联的网络,IOU 逐步提升,从不太 mismatch 的 0.5 开始,逐步提升目标框的质量。

  1. detector 在训练阶段和推理阶段面对的数据分布是不一样的,训练阶段 proposal 都是经过 IOU 采样的,正样本含有目标;推理阶段不知道哪个 proposal 为正样本,只能认为所有 proposal 都是正样本,但这样得到的 proposal 的 IOU 都很低,所以推理结果不太好;
  2. 如果使用低 IOU 重新训练网络,对噪音的识别较大;如果提高 IOU ,又会造成对正样本的过拟合;
  3. 所以可以在网络中使用不同的 IOU 逐步提纯。

所以推荐几篇文章,就不详细解读了:

  1. https://zhuanlan.zhihu.com/p/112828052
  2. https://zhuanlan.zhihu.com/p/161530664
  3. https://zhuanlan.zhihu.com/p/42553957
  4. https://linzhenyuyuchen.github.io/2019/12/05/Cascade-R-CNN/ (个人认为写的最好)

reference

感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章