在R-CNN和Fast RCNN的基础上,在2016年提出了Faster RCNN网络模型,在结构上,Faster RCNN已经将候选区域的生成,特征提取,目标分类及目标框的回归都整合在了一个网络中,综合性能有较大提高,在检测速度方面尤为明显。接下来我们给大家详细介绍fasterRCNN网络模型。网络基本结构如下图所示:
Faster RCNN可以看成是区域生成网络(RPN)与Fast RCNN的组合,其中区域生成网络(RPN)替代选择性搜索来生成候选区域,Fast RCNN用来进行目标检测。
1. 网络工作流程
FasterRCNN的工作流程是:
1、特征提取:将整个图像缩放至固定的大小输入到CNN网络中进行特征提取,得到特征图。
2、候选区域提取:输入特征图,使用区域生成网络RPN,产生一些列的候选区域
3、ROIPooling: 与Fast RCNN网络中一样,使用最大池化固定候选区域的尺寸,送入后续网络中进行处理
4、目标分类和回归:与Fast RCNN网络中一样,使用两个同级层:K+1个类别的SoftMax分类层和边框的回归层,来完成目标的分类和回归。
Faster R-CNN的流程与Fast R-CNN的区别不是很大,重要的改进是使用RPN网络来替代选择性搜索获取候选区域,所以我们可以将Faster R-CNN网络看做RPN和Fast R-CNN网络的结合。
接下来我们来看下该网络预训练模型的使用过程,模型源码位置:fasterRCNN中,如下图所示:
detection文件夹中是模型,数据的实现,weights中包含网络的预训练模型。接下来我们按照以下步骤进行目标检测:
1、获取数据和加载预训练网络
2、获取RPN网络生成的候选区域
3、获取网络的目标检测结果
首先导入相应的工具包:
from detection.datasets import pascal_voc
import matplotlib.pyplot as plt
import numpy as np
from detection.models.detectors import faster_rcnn
import tensorflow as tf
import visualize
1.1 数据加载
加载voc数据集中的一张图片进行网络预测:
pascal = pascal_voc.pascal_voc("train")
image,imagemeta,bbox,label = pascal[218]
在将图像送入网络之前,我们对其进行了尺度的调整,标准化等处理,获取可展示的图像:
img_mean = (122.7717, 115.9465, 102.9801)
img_std = (1., 1., 1.)
rgd_image= np.round(image+img_mean).astype(np.uint8)
获取原始图像,进行比较:
from detection.datasets.utils import get_original_image
ori_img = get_original_image(image[0],imagemeta[0],img_mean)
将图像进行对比显示:
rgd_image= np.round(image+img_mean).astype(np.uint8)
fig,axes=plt.subplots(nrows=1,ncols=2,figsize=(10,8),dpi=100)
axes[0].imshow(ori_img.astype('uint8'))
axes[0].set_title("原图像")
axes[1].imshow(rgd_image[0])
axes[1].set_title("送入网络中的图像")
plt.show()
将原图像的长边缩放为1216,短边按相应比例进行调整后,并按照均值进行填充
ori_img.shape
(375, 500, 3)
image.shape
(1, 1216, 1216, 3)
imagemeta中的信息是:原图像大小,图像缩放后的大小,送入网络中图像的大小,图像缩放比例,图像是否翻转(未使用)。
imagemeta
array([[ 375. , 500. , 3. , 912. , 1216. , 3. ,
1216. , 1216. , 3. , 2.432, 0. ]], dtype=float32)
1.2 模型加载
加载使用coco数据集预训练的模型,对图像进行预测。
classes = ['bg', 'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard',
'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush']
实例化faster-RCNN模型:
model = faster_rcnn.FasterRCNN(num_classes=len(classes))
加载预训练模型,由于fasterRCNN不是按照model的子类构建,所以无法通过h5文件直接加载模型结构,我们将结构实例化后,在加载权重获取整个预训练模型。
model((image,imagemeta,bbox,label),training=True)
model.load_weights("weights/faster_rcnn.h5")
通过model.summary()查看网络架构,如下:
1.3 模型预测过程
模型的预测分为两部分:RPN生成候选区域和Fast RCNN进行目标的分类与回归
1.3.1 RPN获取候选区域
proposals = model.simple_test_rpn(image[0],imagemeta[0])
候选区域的结果如下所示:对于上述图像共产生1533个候选区域,每个候选区域使用相对于输入网络中图像归一化后的左上角坐标和右下角坐标。
<tf.Tensor: shape=(1533, 4), dtype=float32, numpy=
array([[0.20729761, 0.00852748, 0.748096 , 0.46975034],
[0.42213044, 0.5887971 , 0.7810232 , 0.9806169 ],
[0.40125194, 0.4384725 , 0.48458642, 0.47913405],
...,
[0.25977597, 0.435113 , 0.27290097, 0.4483906 ],
[0.38884488, 0.41798416, 0.41393432, 0.4339822 ],
[0.5885266 , 0.65331775, 0.62330776, 0.6913476 ]], dtype=float32)>
我们将这些候选区域绘制在图像上,需要获取绝对位置:
visualize.draw_boxes(rgd_image[0],boxes=proposals[:,:4]*1216)
plt.show()
如下图所示:
1.3.2 FastRCNN进行目标检测
我们将获取的候选区域送入到Fast RCNN网络中进行检测:
res = model.simple_test_bboxes(image[0],imagemeta[0],proposals)
res是一个字典,其结果如下所示:rois是目标框,class_ids是所属的类别,scores是置信度。
{'rois': array([[ 95.65208 , 8.963474 , 370.8639 , 224.11072 ],
[ 57.620296 , 226.60101 , 159.39307 , 310.5221 ],
[ 86.15405 , 323.98065 , 369.12762 , 497.70337 ],
[ 83.67178 , 170.96815 , 135.69716 , 221.05861 ],
[ 74.37474 , 327.855 , 210.86298 , 422.48798 ],
[ 73.24604 , 0.97371644, 206.86272 , 47.992523 ],
[ 63.968616 , 256.5716 , 192.52466 , 365.3871 ],
[ 67.055145 , 88.534515 , 137.3221 , 130.74608 ],
[227.8164 , 291.93015 , 370.9528 , 434.4086 ],
[147.73048 , 218.15501 , 177.35306 , 260.56738 ],
[ 82.44483 , 40.140255 , 133.15623 , 107.72627 ],
[122.62652 , 239.552 , 141.19394 , 272.06354 ],
[154.03288 , 115.91441 , 372.5167 , 426.57187 ],
[218.90562 , 364.88345 , 247.20554 , 419.03842 ],
[248.15126 , 407.61325 , 373.63068 , 479.57568 ],
[139.69551 , 248.66753 , 154.51906 , 264.16055 ],
[212.88734 , 195.23204 , 238.25243 , 209.22202 ]],
dtype=float32),
'class_ids': array([ 1, 1, 1, 1, 1, 1, 1, 1, 1, 46, 1, 46, 61, 46, 57, 45, 40],
dtype=int32),
'scores': array([0.99917287, 0.992269 , 0.99193186, 0.98929125, 0.986894 ,
0.98671734, 0.98594207, 0.97716457, 0.97271395, 0.97136974,
0.9637522 , 0.9585419 , 0.9218482 , 0.8920589 , 0.85597926,
0.81343234, 0.78660023], dtype=float32)}
将检测结果展示在图像上:
visualize.display_instances(ori_img,res['rois'],res['class_ids'],classes,res['scores'])
plt.show()
上述我们介绍了Faster RCNN的工作流程并且给大家展示了网络的检测结果。那接下来我们解决以下几个问题:
1、网络中的每一部分是怎么构建,怎么完成相应的功能的?
2、怎么训练fastrcnn网络去完成我们自己的任务?
那接下来我们就解决上述问题。
2. 模型结构详解
Faster RCNN的网络结构如下图所示:
我们依然将网络分为四部分:
- Backbone:Backbone由CNN卷积神经网络构成,常用的是VGG和resnet, 用来提取图像中的特征,获取图像的特征图。该特征图被共享用于后续RPN层生成候选区域和ROIPooling层中。
- RPN网络:RPN网络用于生成候选区域,用于后续的目标检测。
- Roi Pooling: 该部分收集图像的特征图和RPN网络提取的候选区域位置,综合信息后获取固定尺寸的特征,送入后续全连接层判定目标类别和确定目标位置。
- 目标分类与回归: 该部分利用ROIpooling输出特征向量计算候选区域的类别,并通过回归获得检测框最终的精确位置。
接下来我们就从这四个方面来详细分析fasterRCNN网络的构成,并结合源码理解每一部分实现的功能。
2.1 backbone
backbone一般为VGG,ResNet等网络构成,主要进行特征提取,将最后的全连接层舍弃,得到特征图送入后续网络中进行处理。
在源码中使用ResNet + FPN 结构来提取特征。与普通的 FasterRCNN 只需要将一个特征图输入到后续网络中不同,由于加入 FPN结构,需要将多个特征图逐个送入到后续网络中,如下图所示:
Resnet进行特征提取,FPN结构作用是当前层的特征图会融合未来层的特征进行上采样,并加以利用。因为有了这样一个结构,当前的特征图就可以获取未来层的信息,也就将低阶特征与高阶特征就有机融合起来了,提升检测精度。如下图所示:
在这里ResNet和FPN的完整结构如下图所示:Resnet进行特征提取,FPN网络进行特征融合获取多个特征图后,输入到RPN网络中的特征图是[p2,p3,p4,p5,p6] ,而作为后续目标检测网络FastRCNN的输入则是 [p2,p3,p4,p5] 。
我们看下源码实现的内容:
1、resnet特征提取的结果
C2,C3,C4,C5 = model.backbone(image,training=False)
C2,C3,C4,C5是resnet进行特征提取的结果,送入网络中图像大小为(1216,1216,3),经过特征提取后特征图的大小为:
TensorShape([1, 304, 304, 256])
TensorShape([1, 152, 152, 512])
TensorShape([1, 76, 76, 1024])
TensorShape([1, 38, 38, 2048])
2、FPN特征融合的结果
P2,P3,P4,P5,P6 = model.neck([C2,C3,C4,C5],training=False)
P2,P3,P4,P5,P6是特征融合之后的结果,送入后续网络中,其特征图的大小:
TensorShape([1, 304, 304, 256])
TensorShape([1, 152, 152, 512])
TensorShape([1, 76, 76, 1024])
TensorShape([1, 38, 38, 2048])
TensorShape([1, 19, 19, 256])
那网络的整体架构表示成:
2.2 RPN网络
经典的检测方法生成检测框都非常耗时,如overfeat中使用滑动窗口生成检测框;或如R-CNN使用选择性搜索方法生成检测框。而Faster RCNN则抛弃了传统的滑动窗口和选择性搜索的方法,直接使用RPN生成候选区域,能极大提升检测速度。
RPN网络的主要流程是:
1、生成一系列的固定参考框anchors,覆盖图像的任意位置,然后送入后续网络中进行分类和回归
2、分类分支:通过softmax分类判断anchor中是否包含目标
3、回归分支:计算目标框对于anchors的偏移量,以获得精确的候选区域
4、最后的Proposal层则负责综合含有目标的anchors和对应bbox回归偏移量获取候选区域,同时剔除太小和超出边界的候选区域。
2.2.1 anchors
anchor在目标检测中表示 固定的参考框 ,首先预设一组不同尺度不同长宽比的固定参考框,覆盖几乎所有位置, 每个参考框负责检测与其交并比大于阈值 (训练预设值,常用0.5或0.7) 的目标 ,anchor技术将候选区域生成问题转换为 “这个固定参考框中有没有目标,目标框偏离参考框多远” ,不再需要多尺度遍历滑窗,真正实现了又好又快。
在FastRCNN中框出多尺度、多种长宽比的anchors,如下图所示:下图中分别是尺度为32,64,128,长宽比为1:1,1:2,2:1的一组anchors,我们利用这组anchor在特征图上进行滑动,并对应到原图上即可获取一系列的固定参考框。
由于有 FPN 网络,所以会在多个不同尺度特征图中生成anchor,假设某一个特征图大小为hxw,首先会计算这个特征相对于输入图像的下采样倍数 stride:
如下图所示:
每一个尺度特征图上生成不同比列的anchor:
得到一系列的anchors后就可送入后续网络中进行分类和回归。
在源码中我们可生成一幅图像对应的anchors:
anchors,valid_flags = model.rpn_head.generator.generate_pyramid_anchors(imagemeta)
对于1216x1216的图像生成的anchor的数量为:
TensorShape([369303, 4])
anchor的取值为:
<tf.Tensor: shape=(369303, 4), dtype=float32, numpy=
array([[ -22.627417, -11.313708, 22.627417, 11.313708],
[ -16. , -16. , 16. , 16. ],
[ -11.313708, -22.627417, 11.313708, 22.627417],
...,
[ 789.9613 , 970.98065 , 1514.0387 , 1333.0193 ],
[ 896. , 896. , 1408. , 1408. ],
[ 970.98065 , 789.9613 , 1333.0193 , 1514.0387 ]],
dtype=float32)>
我们将前10000个anchor绘制在图像上:
visualize.draw_boxes(rgd_image[0],boxes=anchors[:10000,:4])
plt.show()
2.2.2 RPN分类
一副MxN大小的矩阵送入Faster RCNN网络后,经过backbone特征提取到RPN网络变为HxW大小的特征图。如下图所示,是RPN进行分类的网络结构:(k=9) 先做一个1x1的卷积,得到[batchsize,H,W,18]的特征图,然后进行变形,将特征图转换为[batchsize,9xH,W,2]的特征图后,送入softmax中进行分类,得到分类结果后,再进行reshape最终得到[batchsize,H,W,18]大小的结果,18表示k=9个anchor是否包含目标的概率值。
2.2.3 RPN回归
RPN回归的结构如下图所示:(k=9)
经过该卷积输出特征图为为[1, H, W,4x9],这里相当于feature maps每个点都有9个anchors,每个anchors又都有4个用于回归的:
变换量。
该变换量预测的是anchor与真实值之间的平移量和尺度因子:
利用源码我们可以获得对anchors的分类和回归结果:
rpn_feature_maps = [P2,P3,P4,P5,P6]
rpn_class_logits,rpn_probs,rpn_deltas = model.rpn_head(rpn_feature_maps,training = False)
结果分析:
TensorShape([1, 369303, 2])
TensorShape([1, 369303, 2])
TensorShape([1, 369303, 4])
其中 rpn_probs的取值为:
<tf.Tensor: shape=(1, 369303, 2), dtype=float32, numpy=
array([[[9.94552910e-01, 5.44707105e-03],
[9.97310877e-01, 2.68914248e-03],
[9.95540321e-01, 4.45961533e-03],
...,
[9.99888301e-01, 1.11637215e-04],
[9.99961257e-01, 3.87872169e-05],
[9.99820888e-01, 1.79159630e-04]]], dtype=float32)>
我们获取一些分类置信度较高的结果,将这些anchor绘制在图像上:
rpn_probs_tmp = rpn_probs[0,:,1]
limit = 100
ix = tf.nn.top_k(rpn_probs_tmp,k=limit).indices[::-1]
visualize.draw_boxes(rgd_image[0],tf.gather(anchors,ix).numpy())
2.2.4 Proposal层
Proposal层负责综合RPN网络对anchors分类和回归的结果,利用回归的结果对包含目标的anchors进行修正,计算出候选区域,送入后续RoI Pooling层中。
Proposal层处理流程如下:
1.利用RPN网络回归的结果[公式]对所有的anchors进行修正,得到修正后的检测框 2.根据RPN网络分类的softmax输出的概率值由大到小对检测框进行排序,提取前6000个结果,即提取修正位置后的检测框 3.限定超出图像边界的检测框为图像边界,防止后续roi pooling时候选区域超出图像边界。
4.对剩余的检测框进行非极大值抑制NMS
5.Proposal层的输出是对应输入网络图像尺度的归一化后的坐标值[x1, y1, x2, y2]。
到此RPN网络的工作就结束了。
Proposal层有3个输入:RPN分类和回归结果,以及图像的元信息。
proposals_list = model.rpn_head.get_proposals(rpn_probs,rpn_deltas,imagemeta)
结果为:
[<tf.Tensor: shape=(1533, 4), dtype=float32, numpy=
array([[0.20729761, 0.00852748, 0.748096 , 0.46975034],
[0.42213044, 0.5887971 , 0.7810232 , 0.9806169 ],
[0.40125194, 0.4384725 , 0.48458642, 0.47913405],
...,
[0.25977597, 0.435113 , 0.27290097, 0.4483906 ],
[0.38884488, 0.41798416, 0.41393432, 0.4339822 ],
[0.5885266 , 0.65331775, 0.62330776, 0.6913476 ]], dtype=float32)>]
将其绘制在图像上
visualize.draw_boxes(rgd_image[0],boxes=proposals_list[0].numpy()[:,:4]*1216)
plt.show()
2.3 ROIPooling
RoI Pooling层则负责收集RPN网络生成的候选区域,并将其映射到特征图中并固定维度,送入后续网络中进行分类和回归。
RoI Pooling 的作用过程,如下图所示:
RoIpooling使用最大池化将任何有效的RoI区域内的特征转换成具有pool_H×pool_W的固定空间范围的小的特征图,其中pool_H和pool_W是超参数,比如设置为7x7, 它们独立于任何特定的RoI,如下图所示:
在实现过程中,FPN网络产生了多个尺度特征图,那候选区域要映射到哪个特征图中呢?
在这里,不同尺度的ROI使用不同特征层作为ROI pooling层的输入,大尺度ROI就用后面一些的金字塔层,比如P5;小尺度ROI就用前面一点的特征层,比如P3,我们使用下面的公式确定ROI所在的特征层:
其中,224是ImageNet的标准输入,k0是基准值,设置为4,w和h是ROI区域的长和宽,假设ROI是112x112的大小,那么k = k0-1 = 4-1 = 3,意味着该ROI应该使用P3的特征层。k值会做取整处理,防止结果不是整数,而且为了保证k值在2-5之间,还会做截断处理。
pool_region_list = model.roi_align((proposals_list,rcnn_feature_maps,imagemeta),training = False)
输出结果为:每一个候选区域都被固定为7x7大小
[<tf.Tensor: shape=(1533, 7, 7, 256), dtype=float32, numpy=
array([[[[-6.26428795e+00, 3.55317879e+00, 3.37260556e+00, ...,
6.22574663e+00, 3.75851846e+00, -2.49103808e+00],
[-9.01443863e+00, 7.67611027e-01, 7.18744850e+00, ...,
6.20492172e+00, 4.09835625e+00, 6.05924249e-01],
[-7.43907213e+00, -3.76329374e+00, 5.01457691e+00, ...,
6.22656918e+00, 1.19414163e+00, 3.06410480e+00],
...,
[ 1.39127302e+00, -1.71078873e+00, 4.01916075e+00, ...,
5.94641972e+00, 3.63194764e-01, 2.91014194e+00],
[-5.21681070e+00, 2.39917469e+00, 2.49682212e+00, ...,
5.92232943e+00, 3.01222801e+00, 1.63518691e+00],
[-1.26697767e+00, -6.90211892e-01, 4.50919747e-01, ...,
1.97156405e+00, -1.07467103e+00, 4.54943466e+00]]
2.4 目标分类与回归
该部分利用获得的候选区域的特征图,通过全连接层与softmax计算每个候选区域具体属于的类别(如人,车,电视等),输出概率值;同时再次利用回归方法获得每个候选区域的位置偏移量,用于回归更加精确的目标检测框。该部分网络结构如下所示:
从RoI Pooling层获取到固定大小的特征图后,送入后续网络,可以看到做了如下2件事:
1.通过全连接和softmax对候选区域进行分类 2.再次对候选区域进行回归修正,获取更高精度的检测框
实现流程如下:
首先获取网络分类和回归的结果:
rcnn_class_logits,rcnn_class_probs,rcnn_deltas_list = model.bbox_head(pool_region_list,training=False)
利用结果对候选区域进行修正:
detection_list = model.bbox_head.get_bboxes(rcnn_class_probs,rcnn_deltas_list,proposals_list,imagemeta)
结果为:一共检测出17个目标,每个目标右目标位置,目标类别id,目标类别置信度6个值构成。
[<tf.Tensor: shape=(17, 6), dtype=float32, numpy=
array([[2.3262584e+02, 2.1799168e+01, 9.0194098e+02, 5.4503723e+02,
1.0000000e+00, 9.9917287e-01],
[1.4013255e+02, 5.5109363e+02, 3.8764392e+02, 7.5518970e+02,
1.0000000e+00, 9.9226898e-01],
[2.0952664e+02, 7.8792090e+02, 8.9771838e+02, 1.2104146e+03,
1.0000000e+00, 9.9193186e-01],
[2.0348978e+02, 4.1579453e+02, 3.3001547e+02, 5.3761450e+02,
1.0000000e+00, 9.8929125e-01],
[1.8087936e+02, 7.9734338e+02, 5.1281873e+02, 1.0274907e+03,
1.0000000e+00, 9.8689401e-01],
[1.7813437e+02, 2.3680782e+00, 5.0309012e+02, 1.1671781e+02,
1.0000000e+00, 9.8671734e-01],
[1.5557167e+02, 6.2398212e+02, 4.6821997e+02, 8.8862134e+02,
1.0000000e+00, 9.8594207e-01],
[1.6307811e+02, 2.1531593e+02, 3.3396735e+02, 3.1797446e+02,
1.0000000e+00, 9.7716457e-01],
[5.5404950e+02, 7.0997412e+02, 9.0215717e+02, 1.0564817e+03,
1.0000000e+00, 9.7271395e-01],
[3.5928052e+02, 5.3055298e+02, 4.3132263e+02, 6.3369983e+02,
4.6000000e+01, 9.7136974e-01],
[2.0050583e+02, 9.7621101e+01, 3.2383597e+02, 2.6199030e+02,
1.0000000e+00, 9.6375221e-01],
[2.9822769e+02, 5.8259045e+02, 3.4338364e+02, 6.6165851e+02,
4.6000000e+01, 9.5854193e-01],
[3.7460797e+02, 2.8190384e+02, 9.0596057e+02, 1.0374227e+03,
6.1000000e+01, 9.2184818e-01],
[5.3237848e+02, 8.8739655e+02, 6.0120386e+02, 1.0191014e+03,
4.6000000e+01, 8.9205891e-01],
[6.0350385e+02, 9.9131537e+02, 9.0866974e+02, 1.1663280e+03,
5.7000000e+01, 8.5597926e-01],
[3.3973947e+02, 6.0475940e+02, 3.7579034e+02, 6.4243842e+02,
4.5000000e+01, 8.1343234e-01],
[5.1774200e+02, 4.7480432e+02, 5.7942987e+02, 5.0882794e+02,
4.0000000e+01, 7.8660023e-01]], dtype=float32)>]
可以将其绘制在图像上:
visualize.draw_boxes(rgd_image[0],boxes=detection_list[0][:,:4])
plt.show()
到这我们就完成了整个网络的介绍。
3. FasterRCNN的训练
Faster R-CNN的训练分为两部分,即RPN网络和检测网络fastRCNN的训练:
整个训练过程分为四步:
- 第一步:RPN网络的训练,使用ImageNet预训练的模型初始化,并端到端微调用于区域建议任务。
- 第二步:利用第一步的RPN生成的建议框,由Fast R-CNN训练一个单独的检测网络,这个检测网络同样是由ImageNet预训练的模型初始化的,这时候两个网络还没有共享卷积层。
- 第三步:用检测网络初始化RPN训练,但是固定共享的卷积层,并且只微调RPN独有的层,现在两个网络共享卷积层了。
- 第四步:保持共享的卷积层固定,微调Fast R-CNN的fc层。这样,两个网络共享相同的卷积层,构成一个统一的网络。
接下来我们分别介绍各个训练步骤:
3.1 RPN网络的训练
RPN网络的作用从众多的anchors中提取包含目标的,并且经过回归调整的候选区域。为了训练RPN,给每个anchor分配是否包含目标的标签,也就是正负样本的标记,然后进行训练。
3.1.1正负样本标记
- 与真实框ground truth(GT)交并比IOU大于0.7的anchor是正样本,即anchor中包含目标,目标值设为1
- 与真实框ground truth(GT)交并比IOU小于0.3的anchor是负样本,即anchor中不包含目标,目标值设为-1
- 其他的anchor舍弃,不参与网络的训练,目标值设为0
3.1.2 RPN网络的损失函数
RPN网络的损失函数是:
其中
-
i
i
i表示anchor的索引 -
p
i
p_i
pi?是第i个anchor 预测为目标的可能性,
p
i
?
p_i^*
pi??为ground-truth标签。如果这个anchor是positive的,则ground-truth标签为1,否则为0。(即当第i个anchor与GT间IoU>0.7,认为是该anchor是positive,标签为1;反之IoU<0.3时,认为是该anchor是negative,标签为0) -
t
i
t_i
ti?表示表示正样本anchor到预测区域bounding box的4个参数化预测结果,
t
i
?
t_i^*
ti??是这个positive anchor对应的ground-truth box的偏移,如下所示:
预测值:
真实值:
其中,x,y,w,h表示窗口中心坐标和窗口的宽度和高度,变量x,
x
a
x_a
xa?和
x
?
x^*
x?分别表示预测窗口、anchor窗口和Ground Truth的坐标(y,w,h同理)
整个Loss分为两部分:分类和回归的损失
-
L
c
l
s
L_{cls}
Lcls?分类的损失(classification loss),是一个二分类器的softmax loss。 -
L
r
e
g
L_{reg}
Lreg?是回归损失,为
s
m
o
o
t
h
(
x
)
smooth(x)
smooth(x)损失,并且只有正样本才参与回归损失计算 -
N
c
l
s
N_{cls}
Ncls?和
N
r
e
g
N_{reg}
Nreg?分别用来标准化分类损失项
L
c
l
s
L_{cls}
Lcls?和回归损失项
L
r
e
g
{L_{reg}}
Lreg?,默认用batch size设置
N
c
l
s
N_{cls}
Ncls?,用anchor位置数目~2000初始化
N
r
e
g
N_{reg}
Nreg? -
N
c
l
s
N_{cls}
Ncls?和
N
r
e
g
N_{reg}
Nreg?相差过大,用参数λ来平衡两者,一般取值为
N
c
l
s
N_{cls}
Ncls?和
N
r
e
g
N_{reg}
Nreg?的比值10即可。
3.1.3 训练过程
在训练时每次迭代的正负样本是由一幅图像的正负样本组成的:
- 随机采样256个anchor,计算损失函数,其中采样的正负anchor的比例是1:1。
- 通过从零均值标准差为0.01的高斯分布中获取的权重来随机初始化所有新层(最后一个卷积层其后的层),所有其他层(即共享的卷积层)是通过对ImageNet分类预训练的模型来初始化的
- 采用带动量的随机梯度下降算法对网络进行训练
3.1.4 实现
1、正负样本设置
将产生的369303个anchor与目标真实值的计算交并比设置正负样本:
rpn_target_matchs,rpn_target_deltas = model.rpn_head.anchor_target.build_targets(anchors,valid_flags,bbox,label)
所有的anchor都设置了分类的目标值,回归的目标值只有正负样本设置了目标值,一共有369303个Anchor,参与训练的有256个anchor。
TensorShape([1, 369303])
TensorShape([1, 256, 4])
获取正样本:正样本是包含目标的anchor,其目标值设为1,正样本的个数是29个
positive_anchors = tf.gather(anchors,tf.where(tf.equal(rpn_target_matchs,1))[:,1])
TensorShape([29, 4])
我们将这些正样本绘制在图像上:可以看出这些anchor与目标还是非常接近的
接下来,我们看下负样本的结果,负样本的目标值是-1,负样本的个数是227,与29个正样本一共是256个anchor参与网络训练,其余的不参与网络训练。
negtivate_anchors = tf.gather(anchors,tf.where(tf.equal(rpn_target_matchs,-1))[:,1])
TensorShape([227, 4])
同样我们也将负样本展示在图像上,从图像可以看出这些负样本的anchor与目标差距还是很大的。
2、损失函数
损失函数计算是将网络预测结果和真实值进行比较,获取两者之间的差别。损失函数由两部分组成:分类和回归
rpn_class_loss, rpn_bbox_loss = model.rpn_head.loss(
rpn_class_logits, rpn_deltas, bbox, label, imagemeta)
<tf.Tensor: shape=(), dtype=float32, numpy=0.20614956>
<tf.Tensor: shape=(), dtype=float32, numpy=0.034301624>
接下来我们使用梯度下降算法对网络进行训练就可以了
3.2 FastRCNN网络的训练
使用RPN网络收集到的候选区域和imageNet预训练的卷积网络提取的特征对检测的FastRCNN网络进行训练。
3.2.1 正负样本标记
在FastRCNN网络训练时:
- 首先将与真实框ground truth(GT)交并比IOU大于0.5的候选区域设为正样本,类别的目标值是GT的类别
- 将与真实框ground truth(GT)交并比IOU小于0.5的候选区域设为负样本,类别的目标值是0
3.2.2 Faster RCNN的损失函数(同Fast-RCNN)
FastRCNN的输出由两部分组成:一部分是softmax层进行分类,输出类别有K个类别加上”背景”类,另一部分是回归bounding box regressor。也就是:
-
一部分输出在K+1个类别上的离散概率分布(每个候选区域),
p
=
(
p
0
,
p
1
,
.
.
.
,
p
k
)
p=(p_0,p_1,...,p_k)
p=(p0?,p1?,...,pk?)。通常,通过全连接层的K+1个输出上的Softmax来计算概率值。 -
另一部分输出对于由K个类别中的每一个检测框回归偏移,
t
k
=
(
t
x
k
,
t
y
k
,
t
w
k
,
t
h
k
)
t^k=(t^k_x,t^k_y,t^k_w,t^k_h)
tk=(txk?,tyk?,twk?,thk?)、其中
t
k
t_k
tk?指定相对于候选框的尺度不变转换和对数空间高度/宽度移位。,与在RPN网络中是一样的。
每个训练的候选区域用 分类目标值u和检测框回归目标值v标记 。背景样本用u=0来表示,对每个标记的候选区域使用多任务损失L以联合训练分类和检测框回归:
其中
L
c
l
s
(
p
.
u
)
=
?
l
o
g
p
u
L_{cls}(p.u)=-logp_u
Lcls?(p.u)=?logpu?,表示交叉熵损失,第二个损失
L
l
o
c
L_{loc}
Lloc?,是定义目标值和预测检测框的四元组之间的损失使用smoothL1损失计算,同样是只有正样本(非背景)的候选区域才计算回归损失,参数λ设为1。
3.2.3 训练过程
FastRCNN的训练获取每张图片中的正负样本:
- 对所有正样本根据IOU值进行排序,每张图片取前256个区域,将这些区域的坐标保存下来,作为该图片的训练样本
- 用于Softmax分类和检测框回归的全连接层的权重分别使用具有方差0.01和0.001的零均值高斯分布初始化,偏置初始化为0,特征提取网络使用ImageNet的预训练网络
- 使用梯度下降算法进行优化
3.2.4 实现
1、正负样本设置
将proposal层产生的候选区域与目标真实值的计算交并比设置正负样本:
rois_list, rcnn_target_matchs_list, rcnn_target_deltas_list = \
model.bbox_target.build_targets(
proposals_list,bbox, label, imagemeta)
获取正样本:正样本是负责目标检测的候选区域,其目标值不是0,正样本的个数是64个
positive_proposal = tf.gather(rois_list[0], tf.where(
tf.not_equal(rcnn_target_matchs_list, 0))[:, 1])
TensorShape([64, 4])
将其展示在图像上:可以这些框跟真实值是非常接近的
visualize.draw_boxes(rgd_image[0],positive_proposal.numpy()*1216)
plt.show()
同样我们也可以获取负样本(背景),并绘制在图像上:
negtivate_proposal = tf.gather(rois_list[0], tf.where(
tf.equal(rcnn_target_matchs_list, 0))[:, 1])
TensorShape([192, 4])
visualize.draw_boxes(rgd_image[0],negtivate_proposal.numpy()*1216)
plt.show()
2、损失函数
损失函数计算是将网络预测结果和真实值进行比较,获取两者之间的差别。在这里我们需要将参与网络训练的候选区域进行ROIPooling后送入网络中训练。损失函数由两部分组成:分类和回归:
pooled_regions_list = model.roi_align(
(rois_list, rcnn_feature_maps, imagemeta), training=True)
rcnn_class_logits_list, rcnn_probs_list, rcnn_deltas_list = \
model.bbox_head(pooled_regions_list, training=True)
rcnn_class_loss, rcnn_bbox_loss = model.bbox_head.loss(
rcnn_class_logits_list, rcnn_deltas_list,
rcnn_target_matchs_list, rcnn_target_deltas_list)
<tf.Tensor: shape=(), dtype=float32, numpy=0.56958425>
<tf.Tensor: shape=(), dtype=float32, numpy=0.28708345>
接下来使用梯度下降算法进行预测即可
3.3 共享卷积训练
用fastRCNN检测网络初始化RPN训练,但是固定共享的卷积层,并且只微调RPN独有的层,现在两个网络共享卷积层了,接下来保持共享的卷积层固定,微调Fast R-CNN的fc层。这样,RPN网络和Fast R-CNN网络共享相同的卷积层,构成一个统一的网络。
Faster R-CNN还有一种端到端的训练方式,可以一次完成训练,将RPN loss与Fast RCNN loss相加,然后进行梯度下降优化,更新参数。
4. 端到端训练
前面我们已经介绍了网络模型架构和预测结果,在网络预测前我们需要对网络进行训练,接下来使用端到端的方式进行模型训练,基本步骤是:
1、加载数据集:我们在这里使用VOC数据集,所以需要加载VOC数据集
2、模型实例化:加载faster RCNN模型
3、模型训练:计算损失函数,使用反向传播算法对模型进行训练
完成网络的训练。首先导入相关的工具包:
from detection.datasets import pascal_voc
import tensorflow as tf
import numpy as np
from matplotlib import pyplot as plt
from detection.models.detectors import faster_rcnn
4.1 数据加载
train_dataset = pascal_voc.pascal_voc('train')
['background',
'person',
'aeroplane',
'bicycle',
'bird',
'boat',
'bottle',
'bus',
'car',
'cat',
'chair',
'cow',
'diningtable',
'dog',
'horse',
'motorbike',
'pottedplant',
'sheep',
'sofa',
'train',
'tvmonitor']
num_classes = len(train_dataset.classes)
4.2. 模型实例化
model = faster_rcnn.FasterRCNN(num_classes=num_classes)
4.3 模型训练
模型训练也就是要使用损失函数,进行反向传播,利用优化器进行参数更新,训练的流程是:
1、指定优化器:在这里我们使用加动量的SGD方法
2、设置epoch,进行遍历获取batch数据送入网络中进行预测
3、计算损失函数,使用反向传播更新参数,我们使用tf.GradientTape实现:
-
定义上下文环境:tf.GradientTape -
计算损失函数loss -
使用 tape.gradient(loss,model.trainable_variables) 自动计算梯度,loss是损失结果,trainable_variables为所有需要训练的变量。 -
使用 optimizer.apply_gradients(zip(grads,model.trainable_variables)) 自动更新模型参数,zip(grads, trainable_variables)将梯度和参数关联起来,然后apply_gradients会自动的利用梯度对参数进行更新。
接下来我们按照这个流程完成模型训练。
optimizer = tf.keras.optimizers.SGD(1e-3, momentum=0.9, nesterov=True)
loss_his = []
for epoch in range(7):
indices = np.arange(train_dataset.num_gtlabels)
np.random.shuffle(indices)
iter = np.round(train_dataset.num_gtlabels/train_dataset.batch_size).astype(np.uint8)
for idx in range(iter):
idx = indices[idx]
batch_image,batch_metas,batch_bboxes,batch_label = train_dataset[idx]
with tf.GradientTape() as tape:
rpn_class_loss,rpn_bbox_loss,rcnn_class_loss,rcnn_bbox_loss = model((batch_image,batch_metas,batch_bboxes,batch_label),training=True)
loss = rpn_class_loss+rpn_bbox_loss+rcnn_class_loss+rcnn_bbox_loss
grads = tape.gradient(loss,model.trainable_variables)
optimizer.apply_gradients(zip(grads,model.trainable_variables))
print("epoch:%d,batch:%d,loss:%f"%(epoch+1,idx,loss))
loss_his.append(loss)
结果为:
epoch:1, loss:147.117371
epoch:2, loss:72.580498
epoch:3, loss:79.347351
epoch:4, loss:41.220577
epoch:5, loss:5.238140
epoch:6, loss:2.924250
epoch:7, loss:5.287500
损失函数的变换如下图所示:
plt.plot(range(len(loss_his)),[loss.numpy() for loss in loss_his])
plt.grid()
当我们训练好模型后,就可以使用训练好的模型进行预测了,也就是本节开头给大家介绍的内容。
总结
-
熟悉FasterRCNN目标检测的思想 利用CNN网络进行特征提取,利用RPN生成候选区域,最后进行分类和回归 -
知道anchor的思想 anchor技术将检测问题转换为**“这个固定参考框中有没有目标,目标框偏离参考框多远”**,不再需要多尺度遍历滑窗 -
掌握RPN网络是如何进行候选区域的生成的 通过softmax判断anchors属于positive或者negative,再利用bounding box regression修正anchors获得精确的proposals -
掌握ROIPooling的使用方法 RoIpooling使用最大池化将任何有效的RoI区域内的特征转换成具有H×W的固定空间范围的小feature map -
知道fasterRCNN的训练方法 分步训练:RPN网络,fastrcnn训练,共享网络训练,端到端的网络训练
|