从本节开始,将正式对项目代码进行解读。因代码众多,在一篇文章中说完是不现实的,故打算分成若干篇幅来陆续解读。
本文部分内容参考自以下帖子,致谢!
https://www.cnblogs.com/wangyong/p/8513563.html
https://blog.youkuaiyun.com/Mr_KkTian/article/details/78158785
一、项目代码结构
├── data //用来保存数据集,如VOC2007、coco等
│ ├── cache //保存训练集和测试集的proposals,如voc_2007_test_gt_roidb.pkl,格式[{ },{ },...,{ }]。//如果文件存在则首先从这读取;否则读取.xml文件得到proposal,同时在该目录下生成对应的.pkl文件 //Note:训练集合和测试集变化了,一定的先delete该目录下的对应的.pkl文件
│ ├── coco //存有访问COCO数据集的Python COCO API
│ ├── demo //演示用图片,执行demo.py时使用
│ ├── imagenet_weights //存有ImageNet数据集预训练的分类模型(如vgg16,res101),vgg16.ckpt和res101.ckpt
│ ├── res101_voc_2007_trainval+voc_2012_trainval //由原名为voc_0712_80k-110k.tgz解压出来的,faster-rcnn(res101)网络训练好的模型
│ ├── scripts //包含fetch_faster_rcnn_models.sh,该脚本可用来下载训练好的faster-rcnn模型
│ ├── vgg16_voc_0712_80k-110k.tgz //原文件名为voc_0712_80k-110k.tgz faster-rcnn(vgg16)的模型压缩文件
│ ├── vgg16_voc_2007_trainval+voc_2012_trainval //由原名为voc_0712_80k-110k.tgz解压出来的,faster-rcnn(vgg16)网络训练好的模型
│ ├── VOCdevkit //PASCAL VOC 2007数据集开发工具箱,其实不需要
│ ├── VOCdevkit2007 //VOC2007数据集,其结构参看其它文章
├── docker //构建docker的不同版本的cuda
│ ├── Dockerfile.cuda-7.5
│ └── Dockerfile.cuda-8.0
├── experiments
│ ├── cfgs //保存$NET.yml文件,针对具体网络的配置参数
│ ├── logs //保存每次训练和测试的日志
│ └── scripts //保存三个.sh脚本,用于demo演示、测试和训练
├── lib //需make编译
│ ├── datasets //基类imdb 针对具体数据集的派生类如pascal_voc coco
│ ├── layer_utils //与anchor proposal相关
│ ├── Makefile
│ ├── model //config配置文件 nms bbox test train_val等
│ ├── nets //基类Network,针对具体网络的派生类(如mobilenet_v1,resnet_v1,vgg16)
│ ├── nms //c和cuda的加速代码,生成共享库(.so)
│ ├── roi_data_layer //RoI层
│ ├── setup.py //用于构建Cython模块
│ └── utils //一些辅助工具,计时、可视化
├── LICENSE
├── output //保存训练模型和测试结果
│ ├── res101
│ └── vgg16
├── README.md
├── run_demover1.sh //演示demo
├── run_test.sh //测试
├── run_train.sh //训练+测试
├── tensorboard //可视化tensorboard
│ ├── res101
│ └── vgg16
└── tools
├── convert_from_depre.py
├── demo.py
├── demover1.py //demo
├── _init_paths.py
├── _init_paths.pyc
├── __pycache__
├── reval.py
├── test_net.py //测试
└── trainval_net.py //训练+验证
/tf-faster-rcnn/output目录结构
├── res101 //在faster-rcnn(res101)
│ ├── voc_2007_test //测试结果,按类别保存的.pkl文件
│ │ └── default
│ ├── voc_2007_trainval //训练的模型保存在该文件夹下
│ │ └── default
│ └── voc_2007_trainval+voc_2012_trainval //在voc_07+voc_12上训练好的faster-rcnn(res101)模型 从/data目录下软链接过来的
└── vgg16
├── voc_2007_test //测试结果,按类别保存的.pkl文件
│ └── default
├── voc_2007_trainval //训练的模型保存在该文件夹下
│ └── default
└── voc_2007_trainval+voc_2012_trainval
└── default -> ../../../data/vgg16_voc_2007_trainval+voc_2012_trainval/ //软链接过来的faster-rcnn(vgg6)模型
二、网络基本原理
1、概述
该项目是faster rcnn基于tensorflow的一个实现版本。有关原理性的东西,此处简略概述一下:
总体上可分为几个部分: 特征提取层、生成anchors、rpn层、FC层,Classifier层,损失函数。该网络是一个复合网络,因此理解起来较为困难。
(1)特征提取层:一副待检测图片首先进入特征提取层,经过该层后输出不同维度的特征图。需要注意的是,特征提取层可以使用多种基础网络进行特征提取,比如VGG16或resnet101等,所选网络不同,输出的特征图尺寸不同。
(2)anchors生成:根据输出的特征图,以及诸多预设参数,即可生成anchors集合。这里的anchors本质上是指,在原始图上画出(实际并没有画,可以脑补)的一系列大大小小的矩形框,共计M*N*9个anchors,用于进行物体检测。M、N是输出特征图的宽高。
(3) rpn层:根据第(1) 步输出的特征,从anchors中挑选得到256个anchors(这时被称为rois)用于物体检测和框坐标回归,其中含128个正样本和128个负样本。这些样本用于判断是物体或者是背景,这是个二分类问题;这些样本还用于预测框的位置坐标,这是个回归问题。因此rpn层包含了分类和回归两个网络。
(4)roi pooling层:将256个rois进行剪裁缩放以适应后续的卷积操作。
(5)在特征提取层(conv层)上添加FC层。
(6)Classifier层:通过FC层与softmax计算每个rois的具体类别,并输出cls_prob概率向量;同时再次利用bounding box regression获得每个rois的位置坐标偏移量bbox_pred,用于回归更加精确的目标检测框。如下图示:
(7)损失函数
下面将进入正题,挨个分析网络的各组成部分。
2、特征提取层(conv层、卷积层)
如之前所说,特征提取层是采用了迁移学习的方法。常用的选择可以是VGG-16或Resnet-101。resnet有多种结构形式,如下图所示。本项目中选择使用resnet101来进行特征选择。
conv层一般包含了conv,pooling,relu三种结构。以renet101为例,首先有个输入7x7x64的卷积,然后经过3 + 4 + 23 + 3 = 33个building block,每个block为3层,所以有33 x 3 = 99层,最后有个fc层(用于分类),所以1 + 99 + 1 = 101层,共计有101层网络(注:101层网络仅仅指卷积或者全连接层,而激活层或者Pooling层并没有计算在内)。
resnet101包含了conv1,conv2_x,conv3_x,conv4_x,conv5_x,到底哪层的输出结果作为特征输出呢?
从resnet_v1.py代码中的_image_to_head函数中可以看出,实际上是conv4_x这层的输出结果被作为特征图。如果debug此段代码,会发现输出结果的维度是(1,?,?,1024),正好符合conv4_x的输出。如下图示:
可以发现conv4_x的最后的输出被RPN和RoI Pooling共同使用(共享),而conv5_x(共9层网络)都作用于RoI Pooling之后的一堆特征图(14 x 14 x 1024),特征图的大小维度也刚好符合原本的ResNet101中conv5_x的输入;
注意,在别的faster rcnn的实现中,conv层可能采用了VGG16。此时,所有的卷积层不改变输入图像的宽高大小,只有pooling层每次会导致输出宽高变为原来的一半,一共会经过4次pooling。因此,最终特征图的宽高会变为原图的1/16。本例中使用的是resnet101网络,经过对源代码的debug发现,conv4输出的特征图宽高也是原图的1/16。其实可以通过计算得出这个结论,此处不再细挖。
最后一点需要说明的是,输入的待检测图像,并不需要限定分辨率大小。进入网络之前对输入图像做尺寸标准化处理,设定图像短边不超过600,图像长边不超过1000,因此可以假定图片最大尺寸为1000*600。此处的相关代码,可以参看minibatch.py文件中的_get_image_blob()函数。基本思想就是短边先按600来进行缩放,如果发现缩放后长边大于1000,那么就改用1000来缩放长边,再缩放短边,确保最大尺寸不超过1000*600。
2、生成anchors
可分为以下几部分: 计算anchors偏移矩阵,生成9个base-anchors、前二者结合计算anchors。这几部分的功能都在源码文件snippets.py中的generate_anchors_pre_tf函数中。
(1) 计算anchors偏移矩阵
先介绍个参数,self._feat_stride = [16, ]
无论Conv层选择何种网络,这个参数的值都是16。查阅了许多资料,都说这个值是指原始图经过特征提取后得到的特征图的边的缩小比例。如果是VGG网络,像上面所说的,输出的尺寸会变为原来的1/16之一,因此这个参数是指针对特征图的缩放倍数。但这个结论对于选择了resnet101网络来说,也成立吗?此处存疑。
不管怎么讲,代码中确实使用了这个参数来计算anchors的偏移矩阵。过程如下:
1)根据原图尺寸和_feat_stride参数,计算特征图大小M*N。
2)根据M、N值的大小,生成宽和高的两个关于16的倍数序列,都是从0开始。比如这样:0,16,32,48,64.........
3)对这两个序列使用各种(神奇)变换(主要是tf.meshgrid和reshape),就会形成需要的偏移矩阵的前两列。
4)如下图,图中每个方格代表一个16*16像素的滑动窗口,会在原始图像上进行滑动。偏移矩阵前两列数值代表的就是滑动窗口的左上角的偏移坐标点。
偏移矩阵的后两列值跟前两列是相同的,其实是为了后面矩阵计算方便。主要理解前两列值就可以。
为方便观察其逻辑,现假定M=5,N=4的特征矩阵,完整的偏移矩阵如下,注意前两列与后两列值相同:
关于anchors的偏移矩阵生成到此完毕,但真实图像的偏移矩阵比这个长的多。
(2)生成9个base-anchors
1)生成一个边长为16的方框,坐标[0,0.15,15],以及中心点的坐标[7.5, 7.5]。</