Faster RCNN复现
文章之前,我们先来明确检测类任务都在干些什么:
需求:
对图像中的特定种类目标做出分类,并求出目标在图像中所处的位置即最终需要的信息:
- object-classes_name
- object-position
在一般的检测任务中类别信息通常由索引代替,例如1-> apple,2 - > cat,3 - > dog,… > 而位置一般可以由两组坐标代替:> 矩形的左上角,右下角坐标(x1,y1,x2,y2)
Faster R-CNN作为两阶段检测网络发展中最重要的一个网络,基本可以视为检测任务的里程碑性成果。
延伸扩展的MaskRCNN,CascadeRCNN都成为了2019年这个时间点上除了各家AI大厂私有网络范围外,支撑很多业务得以开展的基础。所以,Pytorch为基础来从头复现FasterRCNN网络是非常有必要的,其中包含了太多的招数和理论中不会包括的先验知识。
甚至,以Faster RCNN为基础去复现其他的检测网络 所需要的精力和时间都会大大降低
我们的目标:用最简洁,最贴合原文得写法复现Resnet - Faster R-CNN
注:> > 本文中的代码为结构性示例的代码片段,不能够复制粘贴直接运行
架构
VGG16-19因为参数的急剧膨胀和深层结构搭建导致参数量暴涨,网络在反向传播过程中要不断地传播梯度,而当网络层数加深时,梯度在逐层传播过程中会逐渐衰减,导致无法对前面网络层的权重进行有效的调整。
因此vgg19就出现了它的局限性。
而在之后提出的残差网络中,加入了短连接为梯度带来了一个直接向前面层的传播通道,缓解了梯度的减小问题,同时,将整个网络的深度加到了100层+,甚至后来的DenseNet出现了实用的200层+网络。并且大量使用了1 * 1卷积来降低参数量因此本文将尝试ResNet 101 +FasterRCNN,以及衔接DenseNet和FasterRCNN的可能性。
从以上图中我们可以看出Faster R-CNN除了作为特征提取部分的主干网络,剩下的最关键的也就是以下部分
- RPN`
- RPN LossFunction
- ROI Pooling
- Faster-R-CNN Loss Function
也就是说我们的复现工作要着重从这些部分开始。现在看到的最优秀的复现版本应该是Jianwei Yang page
本文的代码较多的综合了多种写法,以及pytorch标准结构的写法
0.整体流程
先来看看代码(只是大概的看一下就行):
#################################非Resnet版本只看看基本结构就可以
class FasterRCNN(nn.Module):
n_classes = 21
classes = np.asarray(['__background__',
'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor'])
PIXEL_MEANS = np.array([[[102.9801, 115.9465, 122.7717]]])
SCALES = (600,)
MAX_SIZE = 1000
def __init__(self, classes=None, debug=False):
super(FasterRCNN, self).__init__()
if classes is not None:
self.classes = classes
self.n_classes = len(classes)
self.rpn = RPN()
self.roi_pool = RoIPool(7, 7, 1.0/16)
self.fc6 = FC(512 * 7 * 7, 4096)
self.fc7 = FC(4096, 4096)
self.score_fc = FC(4096, self.n_classes, relu=False)
self.bbox_fc = FC(4096, self.n_classes * 4, relu=False)
# loss
self.cross_entropy = None
self.loss_box = None
@property
def loss(self):
return self.cross_entropy + self.loss_box * 10
def forward(self, im_data, im_info, gt_boxes=None, gt_ishard=None, dontcare_areas=None):
features, rois = self.rpn(im_data, im_info, gt_boxes, gt_ishard, dontcare_areas)
if self.training:
roi_data = self.proposal_target_layer(rois, gt_boxes, gt_ishard, dontcare_areas, self.n_classes)
rois = roi_data[0]
# roi pool
pooled_features = self.roi_pool(features, rois)
x = pooled_features.view(pooled_features.size()[0], -1)
x = self.fc6(x)
x = F.dropout(x, training=self.training)
x = self.fc7(x)
x = F.dropout(x, training=self.training)
cls_score = self.score_fc(x)
cls_prob = F.softmax(cls_score)
bbox_pred = self.bbox_fc(x)
if self.training:
self.cross_entropy, self.loss_box = self.build_loss(cls_score, bbox_pred, roi_data)
return cls_prob, bbox_pred, rois
这段代码并不是完整定义,只显示了主要流程,辅助性,功能性的方法全部被省略。
结构也被大大简化
当我们以数据为线索则会产生以下的流程
我们在主干网络中可以清晰地看到,向前按照什么样的顺序执行了整个流程(just take a look)
值得注意的是,在以上执行流程中,有些过程需要相应的辅助函数来进行
比如loss的构建,框生成等等,都需要完备的函数库来辅助进行。
以上流程图 ,以及本文的叙述顺序与线索,都是以数据为依托的,明确各个部分数据之间的计算
输入输出信息是非常重要的
初始训练数据包含了:
1.DataLoader
数据加载部分十分自然地,要输入我们的数据集,我们这篇文章使用按最常用的
coco2014/17标准
pascal VOC标准
自定义数据集标准
全部部分当然不能展现 但是我们会在开源项目中 演示Voclike数据集,以及自定义数据集如何方便的被加载->开源-快速训练工具(未完成)
在本文中为了关注主旨我们只介绍自定义数据集和VocLike数据集的加载过程
Data2Dataset
数据的原始形式,当然是以图片为主
我们以一张图为例.
使用labelme标注之后
保存之后软件就会自动生成例如:
{
"version": "3.4.1",
"flags": {
},
"shapes": [
{
"label": "dog",
"line_color": null,
"fill_color": null,
"points": [
[
7,
144
],
[
307,
588
]
],
"shape_type": "rectangle"
},
......
{
"label": "dog",
"line_color": null,
"fill_color": null,
"points": [
[
756,
130
],
[
974,
507
]
],
"shape_type": "rectangle"
}
],
"lineColor": [
0,
255,
0,
128
],
"fillColor": [
255,
0,
0,
128
],
"imagePath": "timg.jpeg",
"imageData": "此处为base64编码过得图像数据"
}
还有labelimg xml标准的数据样本
<annotation>
<folder>图片</folder>
<filename>timg.jpeg</filename>
<path>/home/winshare/图片/timg.jpeg</path>
<source>
<database>Unknown</database>
</source>
<size>
<width>1000</width>
<height>612</height>
<depth>3</depth>
</size>
<segmented>0</segmented>
<object>
<name>dog</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>9</xmin>
<ymin>163</ymin>
<xmax>309</xmax>
<ymax>584</ymax>
</bndbox>
</object>
....
<object>
<name>dog</name>
<pose>Unspecified</pose>
<truncated>0</truncated>
<difficult>0</difficult>
<bndbox>
<xmin>748</xmin>
<ymin>142</ymin>
<xmax>977</xmax>
<ymax>508</ymax>
</bndbox>
</object>
</annotation>
以及yolo标准的bbox
class_id box
0 0.159000 0.610294 0.300000 0.687908
0 0.346000 0.433824 0.216000 0.638889
0 0.491500 0.449346 0.191000 0.588235
0 0.650000 0.511438 0.246000 0.614379
0 0.863000 0.535948 0.230000 0.588235
yolo的box值最终会由下面的方法转换为标准的框数据(xywh)
def convert(size, box): # 归一化操作
#size图像尺寸
#box包围盒
dw = 1. / size[0]
dh = 1. / size[1]
x = (box[0] + box[1]) / 2.0
y = (box[2] + box[3]) / 2.0
w = box[1] - box[0]
h = box[3] - box[2]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return (x, y, w, h)
Dataset2Dataloader
很多对Faster RCNN复现的版本中,标准的数据加载流程没有被固定化,所以数据被以各种datalayer ,roidb等等方法包装,Pytorch0.4之后,实质上已经出现了最标准化的数据输入形式
即:
一般情况下数据从原始形式,也就是上一节获取到的图片以及label的类别坐标信息。
我们假设以字典单位来承载这个数据单元