一.理解fast-rcnn到faster-rcnn的结构
如果不理解faster-rcnn的结构就去看代码是不可能看懂的,faster-rcnn是在fast-rcnn的基础上改进的。
那么我们就先从fast-rcnn开始
(文字是跟着图片描述的)
1. 选择性搜索Selective Search(SS)在图片中获得大约2000个候选框,使用的方法是Selective Search(SS)(Region Proposa)
2. 用vgg16 前五个阶段是conv + relu + pooling的多层的卷积网络得到一些特征映射(Feature Map)
3. 得到feature map,根据之前RoI框选择出对应的区域(既可以理解为将feature map映射回原图像)
4.通过ROI Pooling得到固定大小的特征,再把这些特征输入全连接层4096。
5.最后是21和84的两个全连接层(这两个全连接层是并列的,不是前后关系),前者是分类的输出,代表每个region proposal属于每个类别(21类)的得分,后者是回归的输出,代表每个region proposal的四个坐标。
6.最后是两个损失层,分类的是softmaxWithLoss,输入是label和分类层输出的得分;回归的是SmoothL1Loss,输入是回归层的输出和target坐标及weight。
7.最后对每个类别采用NMS(non-maximun suppression)
(下面这个图细分了fast-rcnn的构造)
(下面这个是损失部分的)
接下来是faster-rcnn的结构理解
(下面这个是faster-rcnn的结构图)
faster-rcnn就是在fast-rcnn的基础上用RPN替换掉Selective Search来生成ROI(注意图中的 区域建议 是虚线的)
RPN网络输入的特征图经过RPN网络得到区域建议和区域得分,并对区域得分采用非极大值抑制【阈值为0.7】,输出其Top-N得分的区域建议给RoI池化层
(了解到这里就可以理解代码了,其余的不详解结构,给出几个网站有兴趣的可以看看:
https://www.cnblogs.com/zyly/p/9247863.html
https://www.cnblogs.com/zyly/p/9246418.html
https://www.cnblogs.com/CZiFan/p/9903518.html
https://blog.csdn.net/shenxiaolu1984/article/details/51036677
https://blog.csdn.net/gentelyang/article/details/80469553
)
二.代码解读
给出github代码链接:https://github.com/endernewton/tf-faster-rcnn
这个tf-faster-rcnn代码想要训练起来一定要花大量的时间去解决问题,非常有耐心才可以(这里不教解决bug,自己上网查进行解决)。
一般代码都有demo文件,让你初步运行查看结果和测试环境。
安装
1.在设置脚本中更新-arch以匹配GPU
cd tf-faster-rcnn/lib
# Change the GPU architecture (-arch) if necessary
vim setup.py
2.构建Cython模块
make clean
make
cd ..
3.安装Python COCO API。代码要求API访问COCO dataset
cd data
git clone https://github.com/pdollar/coco.git
cd coco/PythonAPI
make
cd ../../..
5.下载模型
./data/scripts/fetch_faster_rcnn_models.sh
4.创建一个文件夹和一个软链接来使用预训练的模型
NET=res101
TRAIN_IMDB=voc_2007_trainval+voc_2012_trainval
mkdir -p output/${NET}/${TRAIN_IMDB}
cd output/${NET}/${TRAIN_IMDB}
ln -s ../../../data/voc_2007_trainval+voc_2012_trainval ./default
cd ../../..
在路径下:tf-faster-rcnn-master/output/res101/voc_2007_trainval+voc_2012_trainval/default/ 我们可以看到如下模型
我们也从这里开始:
在目录tf-faster-rcnn-master下运行:
GPU_ID=0
CUDA_VISIBLE_DEVICES=${GPU_ID} ./tools/demo.py
一般来说是可以运行的但失败了呢?不要紧,接下来看代码,理解了代码就可以解决问题
(没必要的代码我并没有放进来)
demo.py
因为代码的跳转问题,图文讲述特别麻烦,我接下来按照这个main进行跳转
if __name__ == '__main__':
cfg.TEST.HAS_RPN = True # 将RPN用于提案
args = parse_args()
# model path
demonet = args.demo_net
dataset = args.dataset
# 模型路径
tfmodel = os.path.join('../output', demonet, DATASETS[dataset][0], 'default', NETS[demonet][0])
if not os.path.isfile(tfmodel + '.meta'):
raise IOError(('{:s} not found.\nDid you download the proper networks from '
'our server and place them properly?').format(tfmodel + '.meta'))
# set config
tfconfig = tf.ConfigProto(allow_soft_placement=True)
tfconfig.gpu_options.allow_growth = True
# init session
sess = tf.Session(config=tfconfig)
# load network
if demonet == 'vgg16':
net = vgg16()
elif demonet == 'res101':
net = resnetv1(num_layers=101)
else:
raise NotImplementedError
net.create_architecture("TEST", 21,
tag='default', anchor_scales=[8, 16, 32])
saver = tf.train.Saver()
# 加载模型
saver.restore(sess, tfmodel)
print('Loaded network {:s}'.format(tfmodel))
# 验证图片集
im_names = ['000456.jpg', '000542.jpg', '001150.jpg', '001763.jpg', '004545.jpg']
for im_name in im_names:
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
print('Demo for data/demo/{}'.format(im_name))
# 验证函数进入
demo(sess, net, im_name)
# 保存图片
plt.savefig(im_name)
plt.show()
parse_args()跳转
def parse_args():
"""Parse input arguments."""
parser = argparse.ArgumentParser(description='Tensorflow Faster R-CNN demo')
parser.add_argument('--net', dest='demo_net', help='Network to use [vgg16 res101]',
choices=NETS.keys(), default='res101')
parser.add_argument('--dataset', dest='dataset', help='Trained dataset [pascal_voc pascal_voc_0712]',
choices=DATASETS.keys(), default='pascal_voc_0712')
args = parser.parse_args()
return args
可以看到网络(net)用的是res101,数据集(dataset)用的是pascal_voc_0712.
(注意,没用到数据集,只是拼接路径用到pascal_voc_0712)
CLASSES = ('__background__',
'aeroplane', 'bicycle', 'bird', 'boat',
'bottle', 'bus', 'car', 'cat', 'chair',
'cow', 'diningtable', 'dog', 'horse',
'motorbike', 'person', 'pottedplant',
'sheep', 'sofa', 'train', 'tvmonitor')
NETS = {'vgg16': ('vgg16_faster_rcnn_iter_70000.ckpt',), 'res101': ('res101_faster_rcnn_iter_110000.ckpt',)}
DATASETS = {'pascal_voc': ('voc_2007_trainval',), 'pascal_voc_0712': ('voc_2007_trainval+voc_2012_trainval',)}
CLASSES : 是数据的类别名称
NETS : 是使用的网络,这里有vgg16 、res101 代码中还提供了mobile、res50、res152,也可以使用,也提供了相应的模型下载链接https://drive.google.com/drive/folders/0B1_fAEgxdnvJSmF3YUlZcHFqWTQ下载后解压放到tf-faster-rcnn-master/output/res101/voc_2007_trainval+voc_2012_trainval/default/ 路径,根据相应的进行修改代码,即可使用不同的模型
DATASETS :这里是使用相应的数据,提供我们下载的模型中有使用, voc_2007_trainval+voc_2012_trainval和coco_2014_train+coco_2014_valminusminival数据的,没有voc_2007_trainval的模型,毕竟数据越大对验证越好,这里有两种格式voc和coco的数据,代码中只提供了voc数据的模型,如果要用到coco的自行修改。
用pascal_voc代表使用数据voc_2007_trainval ( 在我们训练自己的数据的时候使用这个比较方便)
这里使用pascal_voc_0712代表使用数据voc_2007_trainval+voc_2012_trainval (当然这里只代表模型是用这个数据集进行训练的,现在还不用提供数据集)
接下来我们看到模型路径tfmodel,根据上面是数据这里拼接出模型的路径:
output/res101/voc_2007_trainval/default/res101_faster_rcnn_iter_110000.ckpt.meta
后面这个模型文件是训练完成后才会保存的(也可以直接去下载模型https://drive.google.com/drive/folders/0B1_fAEgxdnvJSmF3YUlZcHFqWTQ 这个网站提供了各种模型google drive外网的)。
tfmodel = os.path.join('../output', demonet, DATASETS[dataset][0], 'default', NETS[demonet][0])
选择对应的网络结构
if demonet == 'vgg16':
net = vgg16()
elif demonet == 'res101':
net = resnetv1(num_layers=101)
else:
raise NotImplementedError
进入测试
net.create_architecture("TEST", 21,
tag='default', anchor_scales=[8, 16, 32])
这里就不进一步了解了,不然走得太深,训练模型的时候再详细了解,跳转看一下参数就ok了。
跳转network.py 中 create_architecture函数
def create_architecture(self, mode, num_classes, tag=None,
anchor_scales=(8, 16, 32), anchor_ratios=(0.5, 1, 2)):
可以看到参数:
mode="TEST"\"TRAIN"(训练还是测试),
num_classes=类别数,
anchor_scales (不懂看一下RPN之anchors生成)
anchor_ratios (不懂看一下RPN之anchors生成)
接下来是循环输入图片调用函数预测
im_names = ['000456.jpg', '000542.jpg', '001150.jpg', '001763.jpg', '004545.jpg']
for im_name in im_names:
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
print('Demo for data/demo/{}'.format(im_name))
demo(sess, net, im_name)
plt.savefig(im_name)
跳转demo函数
在demo函数里也就只有一个重要的函数im_detect
调用这个函数返回boxes和scores,因为一张图可能有多个物体,每个物体识别到的返回的box不止一个,之后的代码就是对结果进行分类和找出最好的box。最后通过函数 vis_detections 进行可视化。
def demo(sess, net, image_name):
"""Detect object classes in an image using pre-computed object proposals."""
# 加载演示图像
im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
im = cv2.imread(im_file)
# 计算时间
timer = Timer()
timer.tic()
# 验证返回边框和分数
scores, boxes = im_detect(sess, net, im)
timer.toc()
print('Detection took {:.3f}s for {:d} object proposals'.format(timer.total_time, boxes.shape[0]))
# 可视化检测每个类
CONF_THRESH = 0.8
NMS_THRESH = 0.3
# 将vis_detections 函数中for 循环之前的3行代码移动到这里
# 展示图片的初始设置
im = im[:, :, (2, 1, 0)]
fig, ax = plt.subplots(figsize=(12, 12))
ax.imshow(im, aspect='equal')
for cls_ind, cls in enumerate(CLASSES[1:]):
cls_ind += 1 # because we skipped background
cls_boxes = boxes[:, 4 * cls_ind:4 * (cls_ind + 1)]
cls_scores = scores[:, cls_ind]
# np.newaxis 把每个数弄成一个维度
# np.hstack 按列合并
dets = np.hstack((cls_boxes, cls_scores[:, np.newaxis])).astype(np.float32)
# 最大抑制值
keep = nms(dets, NMS_THRESH)
dets = dets[keep, :]
# 将ax做为参数传入vis_detections,即增加第4项
vis_detections(im, cls, dets, ax, thresh=CONF_THRESH)
# 将vis_detections 函数中for 循环之后的3行代码移动到这里
plt.axis('off')
plt.tight_layout()
plt.draw()
跳转函数 在test.py中 im_detect :
def im_detect(sess, net, im):
blobs, im_scales = _get_blobs(im)
assert len(im_scales) == 1, "Only single-image batch implemented"
im_blob = blobs['data']
blobs['im_info'] = np.array([im_blob.shape[1], im_blob.shape[2], im_scales[0]], dtype=np.float32)
_, scores, bbox_pred, rois = net.test_image(sess, blobs['data'], blobs['im_info'])
boxes = rois[:, 1:5] / im_scales[0]
scores = np.reshape(scores, [scores.shape[0], -1])
bbox_pred = np.reshape(bbox_pred, [bbox_pred.shape[0], -1])
if cfg.TEST.BBOX_REG:
# Apply bounding-box regression deltas
box_deltas = bbox_pred
pred_boxes = bbox_transform_inv(boxes, box_deltas)
pred_boxes = _clip_boxes(pred_boxes, im.shape)
else:
# Simply repeat the boxes, once for each class
pred_boxes = np.tile(boxes, (1, scores.shape[1]))
return scores, pred_boxes
跳转函数 _get_blobs:
def _get_blobs(im):
"""Convert an image and RoIs within that image into network inputs."""
blobs = {}
blobs['data'], im_scale_factors = _get_image_blob(im)
return blobs, im_scale_factors
跳转函数 _get_image_blob:
MAX_SIZE 1000
SCALES (600,)
根据 600/最小边 = im_scale, 对边进行改变。
例如:输入(500,500), 600/500=1.2 ,改边 500*1.2=600,改边500*1.2
def _get_image_blob(im):
im_orig = im.astype(np.float32, copy=True)
im_orig -= cfg.PIXEL_MEANS
im_shape = im_orig.shape
im_size_min = np.min(im_shape[0:2])
im_size_max = np.max(im_shape[0:2])
processed_ims = []
im_scale_factors = []
for target_size in (600,):
im_scale = float(target_size) / float(im_size_min)
# Prevent the biggest axis from being more than MAX_SIZE
if np.round(im_scale * im_size_max) > cfg.TEST.MAX_SIZE:
im_scale = float(cfg.TEST.MAX_SIZE) / float(im_size_max)
im = cv2.resize(im_orig, None, None, fx=im_scale, fy=im_scale,
interpolation=cv2.INTER_LINEAR)
im_scale_factors.append(im_scale)
processed_ims.append(im)
# 创建一个blob来保存输入图像
blob = im_list_to_blob(processed_ims)
return blob, np.array(im_scale_factors)
函数 test_image
输入 图片信息和图片,输出scores=21*300分数、boxes=300*84框
最后在im_detect 函数中进行处理,来到demo.
在demo函数里循环匹配,在nms里挑选出比较优良的例如:33*5个 那5个分别是box和score。
在函数 vis_detections 里挑选出最好的进行画框: