目标检测 Two Stage Detection(RCNN 系列)
作者:互联网
目标检测 Two Stage Detection(RCNN 系列)
-
在没有CNN的时期,人们进行目标检测一般选择Sift/Hog/Orb等传统方法提取特征,在检测的时候用SVM等传统的分类器进行分类和选框回归,然而有了CNN之后,只要有输入就可以自动得到结果,使得我们只需要关心CNN模型的搭建,不需要关心如何人工提取特征,筛选特征。最终搭建模型变成下图所示的工作。
-
RCNN开启了两阶段目标检测的新时代,目前目标检测准确率最高的模型普遍是Two Stage 模型。让我们来看看这个开山模型吧。
RCNN
总流程梳理:
- 先使用Select Search算法进行Region proposal的提取,一共提取2000个。Region proposal经过CNN之后进行ROI Pooling,最后送入FC层分别进行SVM分类和BBox回归。流程图如下:
- 在选择Region Proposal的时候,用的是SS算法,这个算法现在已经没有人用了,是一个被淘汰的算法,因为它计算起来很慢,还有一些问题,它是无监督的算法,在fast RCNN也有用到。
- RCNN采用的backbone是Alexnet,还有它的预训练权重。在训练的时候用真实的dataset进行微调。在训练的时候,作者严格控制正负样本比例。在送入CNN之前,batch_size是128,正负样本是32:96。其中负样本指的是IOU<0.5的region proposal。
- 作者在用SVM分类的时候用的负样本的IOU是小于0.3的。显然,SVM分类的时候筛选更加严格了。然而在之前的CNN用0.5的阈值训练样本,为什么要改变标准和样本个数呢?CNN样本需要特别多,所以筛选比较宽松。而SVM是比较慢的,而且只有支持向量能够参与训练,所以就严格控制样本数量。
- BBox回归的时候采用的是回归差值的方法,换句话说,BBox回归的时候是进行dx dy dw dh回归。这样对小物体会友好一些,而且偏移量会比较好拟合。
NMS:
-
Non Maximum Suppression,非极大值抑制一直是CV方向爱考的算法之一,一般来说在面试的时候可能会直接让你敲NMS,所以要注意了。
-
NMS的主要思想是,先排序BBox,找到Iou最大的BBox。之后用其他的BBox和这个Iou最大的BBox计算Iou,然后把Iou值比较大的框去掉。之后不断重复以上步骤直到没有BBox剩下来为止。
-
NMS的伪代码如下图所示。其实NMS也会有很多问题,比如无法分辨距离靠的很近的物体,还有非黑即白,会把一些比较好的选框白白删掉。所以其实还可以用softNMS解决部分的NMS的问题。
-
NMS代码实现:
import numpy as np
import matplotlib.pyplot as plt
# 自己定义的二维数组
boxes = np.array([[100, 100, 210, 210, 0.72],
[250, 250, 420, 420, 0.8],
[220, 220, 320, 330, 0.92],
[100, 100, 210, 210, 0.72],
[230, 240, 325, 330, 0.81],
[220, 230, 315, 340, 0.9]])
def my_nms(arr,thresh=0.6):
x1 = arr[:,0]
y1 = arr[:,1]
x2 = arr[:,2]
y2 = arr[:,3]
score = arr[:,4]
# 计算各个框框的面积
area = (x2-x1+1)*(y2-y1+1)
# 定义一个放置最大分数的序号的列表
score_rank_list = []
# 按分数高低把序号列表排列出来
score_rank = score.argsort()[::-1]
while score_rank.size >0:
# 把最大的分数对应序号添加到score_rank_list 中
i = score_rank[0]
score_rank_list.append(i)
# 计算每一个方框和它的重复overlap area
x11 = np.maximum(x1[i],x1[score_rank[1:]])
x22 = np.minimum(x2[i],x2[score_rank[1:]])
y11 = np.maximum(y1[i],y1[score_rank[1:]])
y22 = np.minimum(y2[i],y2[score_rank[1:]])
high = np.maximum(0,y22-y11+1)
wide = np.maximum(0,x22-x11+1)
overlap_area = high*wide
# 计算重叠的面积
ious = overlap_area/(area[i]+area[score_rank[1:]]-overlap_area)
# 把大于thresh 的ious给去掉,然后取得生成符合条件的元组的序号
# left_ious是一个列表,通过里面的列表的值如[2,3,4]才可以选出score_rank里面连续的值
left_ious = np.where(ious<=thresh)[0]
# 对score_rank进行切片,获得符合条件的序号最小值,把符合条件的score全部删除
# +1是为了把最高的那个给排除了
score_rank = score_rank[left_ious+1]
# return千万不要放在循环里面
return score_rank_list
def box_show(arr,type = 'c'):
x1 = arr[:, 0]
y1 = arr[:, 1]
x2 = arr[:, 2]
y2 = arr[:, 3]
plt.plot([x1, x2], [y1, y1], type)
plt.plot([x1, x1], [y1, y2], type)
plt.plot([x1, x2], [y2, y2], type)
plt.plot([x2, x2], [y1, y2], type)
plt.title(" nms")
plt.figure(1)
ax1 = plt.subplot(1, 2, 1)
ax2 = plt.subplot(1, 2, 2)
plt.sca(ax1)
box_show(boxes, 'k') # before nms
keep = my_nms(boxes, thresh=0.01)
plt.sca(ax2)
box_show(boxes[keep], 'r') # after nm
plt.show()
- 在RCNN中,作者也用到了ROI Pooling,这个做法在接下来fast RCNN讲
Fast RCNN
-
RCNN最让人头痛的就是超级慢,大约50-60s才能处理一张图片。远远不能达到实际要求,其实是因为它把2000个region proposal独立地送入网络中,而且还要用svm分类。真的太慢了。而且把检测分成了三部分,ss,svm ,cnn都相互独立,比较难受。
-
所以,作者提出了Fast RCNN,让检测的速度大约在2s一张图片。Fast RCNN流程如下图所示:
总体流程:
- 首先先和RCNN一样,找到region proposal,其中region proposal也还是用SS算法去找的。将整张图片送入到CNN网络中,得到feature map,通过ROI projection(就是把原图的ROI投射到中feature map上)。之后把feature map上的ROI送到pooling里面,变成长宽一样的feature map。最终通过softmax进行分类,进行BBOX的回归。
ROI projection:
- 作者把整张图片放到CNN一次性卷积,那么我们要如何才能找到我们想要的ROI对应的feature map呢?作者发现,其实对应的feature map和原图的区域其实就是一一对应的关系,就是简单的除法映射。比如1024*1024->128 * 128,就是除以8的关系。feature map的位置其实就是原图除以8。
- 找到了region proposal对应的feature map的位置,就可以把这些东西拿到网络中去了。但是网络中有全连接层,输入的size必须要一样,作者就采用了第二种方法,ROI Pooling
ROI Pooling:
-
Pooling就是把所有的特征图变成7*7,然后方便接下来的网络计算。如何变成7x7呢?就是通过打格子,对格子内部的像素进行max pooling处理。
-
然后不同size的ROI就变成一样的了。
-
我们在这个时候发现:fast RCNN其实做了两次量化的过程,一次是在ROI projection的时候,把横纵坐标对应到feature map上的时候,进行了一次量化(比如除以16除不尽就直接删了),还有ROI Pooling的时候也是一次量化过程。这两次量化对图像的精度会产生比较大的影响。如果是大物体损失的信息还不会很多,但是如果是小物体,损失的像素占原来的比例就很可怕了。
ROI Align:
-
为了减少两次量化的损失,首先何凯明大神提出了ROI Align
-
首先获得红色框,就是ROIprojection的结果,之后进行打格子,在绿色格子中打成黑色格子,在黑色格子之后取得中点, 之后再进行pooling。
-
对于粉色格点的计算是通过双线性插值完成的,这个时候就可以保住一定的量化精度。
-
然而何大神还是有一些问题没考虑到。这种差值方法参与计算的点其实就只有像素旁边的4个点而已,其实小块图像中每一个点对我们要计算的点都有作用。还有为什么要在绿色的格子里面要去取4个格子呢?不同的数据集, 这个N是比较没有道理的,是要基于特定数据集才有道理的.真正的场景中.只有部分的点参与了计算,这时候还是会抹杀了很多的信息.
-
为了改进双线性插值的不足,有人提出了Precise ROI Pooling
Precise ROI Pooling:
- 如上图所示。对每一个点进行红点的双线性插值计算,然后对红点画出来的圈圈进行ROI Pooling。这样子,所有的点都能参与运算,量化的损失也基本没有了。
Faster RCNN:
-
Fast RCNN还不够快,天下武功无快不破。Fast RCNN用了超级慢的SS算法,使得网络并不是一个端到端的模型。所以加上了一个RPN网络(region proposal network),这也是faster RCNN比较精华的部分
-
我们还是先从流程说起:
-
首先将图片送入(VGG网络)backbone进行feature map的提取,然后也送入RPN网络提取出ROI。剩下的流程和
fast RCNN基本一样,进行ROI Pooling后分类和回归。最重要的是RPN网络。
RPN:
-
RPN+BBox回归其实就是Two Stage Detection名字的由来,这也是为什么二阶段的精确度一直都比一阶段好的原因。
-
RPN的架构在train 和test的时候其实是不一样的网络大概结构如下:
-
为了在RPN中产生出Region proposal,作者引入了anchor(这个东西在Yolo模型也用到了),anchor的大小和长宽比是可以自己定制的,在开源的代码中,一般一个像素会产生9个anchor,其中有3个scale和3个ratios,这样它就更有可能贴近我们需要寻找的那个物体。
-
以下是test时候的RPN网络:
-
经过3 * 3之后的通道数作者没有标注出来,因为最重要的事情是在1*1的卷积之后,通道数要是18 和 36, 这个是必须的。上面的18 其实是用来分类的,因为我们有9个anchor,还有每个anchor都有2个向量,表示是背景和前景的概率(看到进行了softmax就知道这进行了分类操作了)。下面的36输出的是9个anchor的x,y,w,h,所以有36个channel,最后把这些东西拼接起来就得到的我们需要的ROI的信息了。
-
当IOU>0.7的时候当做正样本,IOU<0.3的时候当做负样本,IOU不包括计算和其他anchor的IOU。在训练的时候要保证正负样本比是1:3.
损失函数:
Smooth L1 Loss:
- 公式如下:
- f ( x ) = { ( σ x ) 2 2 , if x < 1 / σ 2 ∣ x ∣ − 0.5 σ 2 , otherwise f(x)=\left\{\begin{array}{ll}\frac{(\sigma x)^{2}}{2}, & \text { if } x<1 / \sigma^{2} \\ |x|-\frac{0.5}{\sigma^{2}}, & \text { otherwise }\end{array}\right. f(x)={2(σx)2,∣x∣−σ20.5, if x<1/σ2 otherwise
- 这个公式其实应该是非常熟悉了,它可以除去L1 Loss在原点不连续的问题,也可以让下降时候的梯度有所变化,在最低点的时候梯度小一些,方便训练。
残差拟合:
-
RPN网络拟合的是残差量,dx,dy,dw,dh,公式如下图所示:
f ( x ) = { t x p = x p − x a w a , t y p = y p − y a h a t w p = log ( w p w a ) , t h p = log ( h p h a ) t x g = x g − x a w a , t y g = y g − y a h a t w g = log ( w g w a ) , t h g = log ( h g h a ) f(x)=\left\{\begin{array}{c}t_{x}^{p}=\frac{x_{p}-x_{a}}{w_{a}}, t_{y}^{p}=\frac{y_{p}-y_{a}}{h_{a}} \\ t_{w}^{p}=\log \left(\frac{w_{p}}{w_{a}}\right), t_{h}^{p}=\log \left(\frac{h_{p}}{h_{a}}\right) \\ t_{x}^{g}=\frac{x_{g}-x_{a}}{w_{a}}, t_{y}^{g}=\frac{y_{g}-y_{a}}{h_{a}} \\ t_{w}^{g}=\log \left(\frac{w_{g}}{w_{a}}\right), t_{h}^{g}=\log \left(\frac{h_{g}}{h_{a}}\right)\end{array}\right. f(x)=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧txp=waxp−xa,typ=hayp−yatwp=log(wawp),thp=log(hahp)txg=waxg−xa,tyg=hayg−yatwg=log(wawg),thg=log(hahg)
predicated bbox: x p , y p , w p , h p x_{p}, y_{p}, w_{p}, h_{p} xp,yp,wp,hp
anchor bbox: x a , y a , w a , h a x_{a}, y_{a}, w_{a}, h_{a} xa,ya,wa,ha
ground truth bbox: x g , y g , w g , h g x_{g}, y_{g}, w_{g}, h_{g} xg,yg,wg,hg -
我们最想做的事情就是让 t x x g t^{g}_{xx} txxg 和 t x x p t^{p}_{xx} txxp 接近,这样我们预测的框框就比较好了。在反向传播的时候,损失函数就用Smooth L1损失去做就可以了,把 t x x g t^{g}_{xx} txxg 和 t x x p t^{p}_{xx} txxp 的损失加起来就可以了。在进行w和h的预测的时候加了log,这是为了抑制大物体的Loss,使得它和小物体比较接近,不然,网络只会学到大物体的特征,小物体就会比较忽视了。 t x x g t^{g}_{xx} txxg 和 t x x p t^{p}_{xx} txxp 相对于anchor做归一化也是这个道理。
-
只要把残差量给拟合好了,那么网络就训练完成了。
训练过程:
-
Faster RCNN是一张比较庞大的网络,在训练的时候如果暴力一起训练的话,比较难以收敛。
-
所以作者选择了比较复杂的训练过程:
- 首先选取VGG的backbone,固定参数,训练RPN的参数生成ROI。
- RPN训练好之后固定RPN的参数,结合ROI Pooling训练功能头和对Backbone进行微调
- 再用backbone重新训练RPN网络
- 把Backbone和RPN固定,训练功能头。
-
经过这些折腾之后,网络就训练得差不多了。这也是以RCNN系列的大致经典内容了。
标签:ROI,log,Two,rank,Detection,score,RCNN,RPN 来源: https://blog.csdn.net/Sakura_day/article/details/115482930