第一篇博客,水平有限,如有错误之处还望各位指出,有问题欢迎大家一起讨论。
本文主要写了一些关于py-faster rcnn的使用心得,用它来训练自己的数据集,同时使用官方提供的一些工具,来完成一些检测任务。
0、前期准备
已搭建好caffe、已安装python,opencv等。
已安装好Py-Faster-Rcnn,并已完成配置,可使用官方提供的模型和demo.py来检测图片。
我们使用的系统是ubuntu 14.04版本,GPU为GTX980ti ,6G显存,亲测可跑VGG16(batchsize 16).(注,该显卡在windows下的matlab版本的Faster Rcnn无法跑VGG16,显存不够。。。。)
一、准备数据集
我们使用的是pascal VOC2007 格式的数据集。需要准备图片,包含图片标注信息的xml文件,设置的训练验证集和测试集的txt文档。
在路径py-faster-rcnn/data/VOCdevkit2007/VOC2007/ 下:
Annotations:放入xml文件。
JPEGImages:放入图片。
ImageSets/Main:放入 trainval.txt 和 test.txt
我们是直接使用自己的数据集代替官方的pascal VOC2007数据集来进行训练的。所以在数据集的准备上参考pascal voc2007的格式即可。
二、训练方式选择
py-faster rcnn 提供了两种训练方式:
1.四步交替训练方法(官方的matlab版本只有这种训练方式)
该训练方法第一步是训练RPN网络,第二步训练Fast-rcnn网络,第三步固定卷积层(开始共享卷积)只训练RPN独有的层,第四部只训练Fast-Rcnn特有的层。详细过程可参考论文,和训练代码: py-faster-rcnn/tools/train_faster_rcnn_alt_opt.py。该方法训练时间相对较久一些,且以后的使用中参数设置,和网络设置相对麻烦一点。但其检测效果稍微优于端对端的训练方法。
2.端对端的近似训练方法
该方法直接将RPN网络和Fast-Rcnn网络融合为一个整体进行端对端的训练。训练收敛速度快,网络设置修改简单,效果稍微差于四步训练的方法。详细过程可参考论文,以及训练代码:py-faster-rcnn/tools/train_net.py。
综上所属推荐使用端对端的训练方式,操作简单,易于修改,训练速度快。
三、网络的选择
官方提供了ZF网络和VGG网络(VGG16和VGG_M_1024)的网络结构,和相应的在Imagenet数据集下的预训练网络模型。(只要熟悉以后我们也可以自己设置网络模型,不用预训练模型从初始状态开始训练)
在确定了训练方式之后,进入所选择的训练方式的模型下可查看相应的网络模型,以及训练超参数。
例如:我们选择使用端对端的训练方式,ZF网络。
则在路径:py-faster-rcnn/models/pascal_voc/ZF/faster_rcnn_end2end/ 下有solver.prototxt,test.prototxt,train.prototxt 三个文件。其中,train.prototxt 即是ZF网络的训练模型。test.prototxt 即ZF网络的测试模型。修改模型可在这两个文件中修改,同时在多类别检测任务中,类别数量需要在这两个文件中修改。若训练中需加载预训练模型来Fine-turning,则所需加载的网络层的名称应该与预训练模型的网络层名称相同。solver.prototxt中则是CNN的训练超参数。
可将test.prototxt或train.prototxt中的内容复制网页 http://ethereon.github.io/netscope/#/editor 中来观看网络结构的可视化情况,用于直观的理解网络结构,以及检查修改后的网络结构是否正确。
由于类别数量改变所需修改处:(由于类别数量的改变,在加载与训练模型时需要修改与类别相关层的名称)
在test.prototxt和train.prototxt中,类别数量和bbox_pred处需要修改。
四、开始训练
(以端对端训练为例,四步训练法同理)
在选择好训练方式,修改好网络模型,设置好训练超参数后。可开始训练。推荐使用官方例程的脚本来启动训练。
端对端训练脚本位于:py-faster-rcnn/experiments/scripts/faster_rcnn_end2end.sh (四步训练则为faster_rcnn_alt_opt.sh)。该脚本程序集成了模型训练,测试集测试,日志保存这几个功能,同时也可以设置训练函数py-faster-rcnn/tools/train_net.py 中所需输入的参数(例如最大迭代次数,是否使用GPU等)。
在该脚本文件中,我们针对自己的数据级需要一些修改:
1.第30行最大迭代次数:ITERS=40000 ;由于我们使用的是pascal-voc的格式,所以修改此处。
2.第53行预训练模型加载地址: --weights data/imagenet_models/ZF_faster_rcnn_final.caffemodel \(此处表示加载了一个ZF模型为预训练模型,用于做迁移训练。)
其他地方需要注意几个路径地址即可。
训练命令: 在Faster-Rcnn的根目录下打开终端,输入./experiments/scripts/faster_rcnn_end2end.sh 0 ZF pascal_voc 开始使用GPU 0 和ZF网络,以及pascal_voc的格式启动faster_rcnn_end2end.sh脚本程序开始训练我们的数据集。
除此之外,我们也可以使用脚本所调用的训练代码:py-faster-rcnn/tools/train_net.py 来直接进行训练。这样我们需要在命令行中提供一些参数。
例如:python tools/train_./tools/demo.py --gpu 0 --net zffaster_rcnn_alt_opt.py --gpu 0 --net_name ZF --weights data/faster_rcnn_models/ZF_faster_rcnn_final.caffemodel --cfg experiments/cfgs/faster_rcnn_alt_opt.yml --imdb voc_2007_trainval (这是一个四步训练的命令,端对端的方式同理,注意根据train_net.py 中所需的参数来输入)
注:在训练前需清空之前训练的缓存,py-faster-rcnn/data/cache 中的内容 和 py-faster-rcnn/data/VOCdevkit2007/annotations_cache 中的内容。且保证输出py-faster-rcnn/output 为空,或将之前的训练结果保存在其他文件。
训练完成后,模型保存于py-faster-rcnn/output 下。日志保存于py-faster-rcnn/experiments/logs下。
下面是faster_rcnn_end2end.sh:
#!/bin/bash
# Usage:
# ./experiments/scripts/faster_rcnn_end2end.sh GPU NET DATASET [options args to {train,test}_net.py]
# DATASET is either pascal_voc or coco.
#
# Example:
# ./experiments/scripts/faster_rcnn_end2end.sh 0 VGG_CNN_M_1024 pascal_voc \
# --set EXP_DIR foobar RNG_SEED 42 TRAIN.SCALES "[400, 500, 600, 700]"
set -x
set -e
export PYTHONUNBUFFERED="True"
GPU_ID=$1
NET=$2
NET_lc=${NET,,}
DATASET=$3
array=( $@ )
len=${#array[@]}
EXTRA_ARGS=${array[@]:3:$len}
EXTRA_ARGS_SLUG=${EXTRA_ARGS// /_}
case $DATASET in
pascal_voc)
TRAIN_IMDB="voc_2007_trainval"
TEST_IMDB="voc_2007_test"
PT_DIR="pascal_voc"
ITERS=40000
;;
coco)
# This is a very long and slow training schedule
# You can probably use fewer iterations and reduce the
# time to the LR drop (set in the solver to 350,000 iterations).
TRAIN_IMDB="coco_2014_train"
TEST_IMDB="coco_2014_minival"
PT_DIR="coco"
ITERS=500
;;
*)
echo "No dataset given"
exit
;;
esac
LOG="experiments/logs/faster_rcnn_end2end_${NET}_${EXTRA_ARGS_SLUG}.txt.`date +'%Y-%m-%d_%H-%M-%S'`"
exec &> >(tee -a "$LOG")
echo Logging output to "$LOG"
time ./tools/train_net.py --gpu ${GPU_ID} \
--solver models/${PT_DIR}/${NET}/faster_rcnn_end2end/solver.prototxt \
--weights data/imagenet_models/VGG16_faster_rcnn_final.caffemodel \
--imdb ${TRAIN_IMDB} \
--iters ${ITERS} \
--cfg experiments/cfgs/faster_rcnn_end2end.yml \
${EXTRA_ARGS}
set +x
NET_FINAL=`grep -B 1 "done solving" ${LOG} | grep "Wrote snapshot" | awk '{print $4}'`
set -x
time ./tools/test_net.py --gpu ${GPU_ID} \
--def models/${PT_DIR}/${NET}/faster_rcnn_end2end/test.prototxt \
--net ${NET_FINAL} \
--imdb ${TEST_IMDB} \
--cfg experiments/cfgs/faster_rcnn_end2end.yml \
${EXTRA_ARGS}
五、图片检测
图片检测代码位于:py-faster-rcnn/tools/demo.py 。将待测图片存放于:py-faster-rcnn/data/demo/ 下。
若直接使用该程序检测,首先需要将我们训练保存的模型复制到py-faster-rcnn/data/faster_rcnn_models/ 路径下。然后再在demo.py中修改模型名称,类别名称,CONF_THRESH,NMS_THRESH阈值等。详细参考demo.py(此处已非原版,现可一张图检测显示多个类别)
注:第132行,端对端训练方式和四步训练方式是不同的,此处是加载网络结构,使用不同训练方式时需要修改。
调用命令:./tools/demo.py --gpu 0 --net zf
下面是我的demo.py
#!/usr/bin/env python
#coding=utf8
# 因为代码中我加了中文注释,所以 上面这行用于指定编码 ,否则python代码执行会报错 (一张图显示多个类别)
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------
"""
Demo script showing detections in sample images.
See README.md for installation instructions before running.
"""
import _init_paths
from fast_rcnn.config import cfg
from fast_rcnn.test import im_detect
from fast_rcnn.nms_wrapper import nms
from utils.timer import Timer
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as sio
import caffe, os, sys, cv2
import argparse
#CLASSES = ('__background__',
# 'aeroplane', 'bicycle', 'bird', 'boat',
# 'bottle', 'bus', 'car', 'cat', 'chair',
# 'cow', 'diningtable', 'dog', 'horse',
# 'motorbike', 'person', 'pottedplant',
# 'sheep', 'sofa', 'train', 'tvmonitor')
CLASSES = ('__background__',
'standing','sitting','sternal_recumb','ventral_recumb','lateral_recumb')
NETS = {'vgg16': ('VGG16',
'vgg16_faster_rcnn_iter_40000.caffemodel'),
'zf': ('ZF',
'zf_faster_rcnn_iter_40000.caffemodel')}
#zf_fast_rcnn_iter_1000.caffemodel ZF_faster_rcnn_final-1.caffemodel VGG16_faster_rcnn_final.caffemodel
#增加ax
def vis_detections(im, class_name, dets, ax, thresh=0.5):
"""Draw detected bounding boxes."""
inds = np.where(dets[:, -1] >= thresh)[0]
if len(inds) == 0:
return
# 删除这三行
# im = im[:, :, (2, 1, 0)]
# fig, ax = plt.subplots(figsize=(12, 12))
# ax.imshow(im, aspect='equal')
for i in inds:
bbox = dets[i, :4]
score = dets[i, -1]
ax.add_patch(
plt.Rectangle((bbox[0], bbox[1]),
bbox[2] - bbox[0],
bbox[3] - bbox[1], fill=False,
edgecolor='red', linewidth=3.5)
)
ax.text(bbox[0], bbox[1] - 2,
'{:s} {:.3f}'.format(class_name, score),
bbox=dict(facecolor='blue', alpha=0.5),
fontsize=14, color='white')
ax.set_title(('{} detections with '
'p({} | box) >= {:.1f}').format(class_name, class_name,
thresh),
fontsize=14)
# 删除这三行
# plt.axis('off')
# plt.tight_layout()
# plt.draw()
def demo(net, image_name):
"""Detect object classes in an image using pre-computed object proposals."""
# Load the demo image
im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
im = cv2.imread(im_file)
# Detect all object classes and regress object bounds
timer = Timer()
timer.tic()
scores, boxes = im_detect(net, im)
timer.toc()
print ('Detection took {:.3f}s for '
'{:d} object proposals').format(timer.total_time, boxes.shape[0])
# Visualize detections for each class
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]
dets = np.hstack((cls_boxes,
cls_scores[:, np.newaxis])).astype(np.float32)
keep = nms(dets, NMS_THRESH)
dets = dets[keep, :]
vis_detections(im, cls, dets, ax, thresh=CONF_THRESH)
# 将vis_detections 函数中for 循环之后的3行代码移动到这里
plt.axis('off')
plt.tight_layout()
plt.draw()
def parse_args():
"""Parse input arguments."""
parser = argparse.ArgumentParser(description='Faster R-CNN demo')
parser.add_argument('--gpu', dest='gpu_id', help='GPU device id to use [0]',
default=0, type=int)
parser.add_argument('--cpu', dest='cpu_mode',
help='Use CPU mode (overrides --gpu)',
action='store_true')
parser.add_argument('--net', dest='demo_net', help='Network to use [vgg16]',
choices=NETS.keys(), default='vgg16')
args = parser.parse_args()
return args
if __name__ == '__main__':
cfg.TEST.HAS_RPN = True # Use RPN for proposals
args = parse_args()
prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
'faster_rcnn_end2end', 'test.prototxt')
caffemodel = os.path.join(cfg.DATA_DIR, 'faster_rcnn_models',
NETS[args.demo_net][1])
if not os.path.isfile(caffemodel):
raise IOError(('{:s} not found.\nDid you run ./data/script/'
'fetch_faster_rcnn_models.sh?').format(caffemodel))
if args.cpu_mode:
caffe.set_mode_cpu()
else:
caffe.set_mode_gpu()
caffe.set_device(args.gpu_id)
cfg.GPU_ID = args.gpu_id
net = caffe.Net(prototxt, caffemodel, caffe.TEST)
print '\n\nLoaded network {:s}'.format(caffemodel)
# Warmup on a dummy image
im = 128 * np.ones((300, 500, 3), dtype=np.uint8)
for i in xrange(2):
_, _= im_detect(net, im)
# im_names = ['000456.jpg', '000542.jpg', '001150.jpg',
# '001763.jpg', '004545.jpg']
im_names = ['8172355(13).jpg','12238055(11).jpg',
'8185056(15).jpg', '8499359(12).jpg','8193000(14).jpg']
for im_name in im_names:
print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
print 'Demo for data/demo/{}'.format(im_name)
demo(net, im_name)
plt.show()
六、测试集评估
测试集评估代码位于:py-faster-rcnn/tools/test_net.py 。该函数用于检测整个测试集的图片,计算评价指标AP和mAP。
使用它,(由于def parse_args():)同样需要在终端给它一些所需的参数。
例如:调用命令 python tools/test_net.py --gpu 0 --def models/pascal_voc/ZF/faster_rcnn_alt_opt/faster_rcnn_test.pt --net output/faster_rcnn_alt_opt/voc_2007_trainval/ZF_faster_rcnn_final.caffemodel --cfg experiments/cfgs/faster_rcnn_alt_opt.yml --imdb voc_2007_test
七、卷积特征可视化
训练好的模型每一层输出特征的可视化,这是一种检验是否训练了一个好模型的参考指标之一。如果卷积图片后特征明显,干净则说明网络有学习到特征,这往往有不错的检测效果表现。如果卷积图片后,特征显示杂乱无章,则往往是网络未收敛,参数设置不当,或网络设计不当等原因,这往往导致很差的检测效果。
代码位于:py-faster-rcnn/tools/feature_visualize.py 。
使用它,需要根据自己的数据修改四个地方:
1.29行等,需要修改类别名,网络地址等。
2.87行,输出卷积特征图的地址
3.183行,带测图片名(图片地址和检测时的地址相同)
4.155行,针对所选择的是四步训练还是端对端训练来修改。
下面是我的feature_visualize.py:
#!/usr/bin/env python
#coding=utf8
# 因为代码中我加了中文注释,所以 上面这行用于指定编码 ,否则python代码执行会报错 (一张图显示多个类别)
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------
"""
Demo script showing detections in sample images.
See README.md for installation instructions before running.
"""
import _init_paths
from fast_rcnn.config import cfg
from fast_rcnn.test import im_detect
from fast_rcnn.nms_wrapper import nms
from utils.timer import Timer
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as sio
import caffe, os, sys, cv2
import argparse
import math
CLASSES = ('__background__',
'standing','sitting','sternal_recumb','ventral_recumb','lateral_recumb')
#CLASSES = ('__background__',
# 'mango')
NETS = {'vgg16': ('VGG16',
'/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/vgg16_faster_rcnn_iter_40000.caffemodel'),
'zf': ('ZF',
'/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/zf_faster_rcnn_iter_40000.caffemodel')} #修改1 #zf_fast_rcnn_iter_1000 ZF_faster_rcnn_final
def vis_detections(im, class_name, dets, thresh=0.5):
"""Draw detected bounding boxes."""
inds = np.where(dets[:, -1] >= thresh)[0]
if len(inds) == 0:
return
im = im[:, :, (2, 1, 0)]
fig, ax = plt.subplots(figsize=(12, 12))
ax.imshow(im, aspect='equal')
for i in inds:
bbox = dets[i, :4]
score = dets[i, -1]
ax.add_patch(
plt.Rectangle((bbox[0], bbox[1]),
bbox[2] - bbox[0],
bbox[3] - bbox[1], fill=False,
edgecolor='red', linewidth=3.5)
)
ax.text(bbox[0], bbox[1] - 2,
'{:s} {:.3f}'.format(class_name, score),
bbox=dict(facecolor='blue', alpha=0.5),
fontsize=14, color='white')
ax.set_title(('{} detections with '
'p({} | box) >= {:.1f}').format(class_name, class_name,
thresh),
fontsize=14)
plt.axis('off')
plt.tight_layout()
#plt.draw()
def save_feature_picture(data, name, image_name=None, padsize = 1, padval = 1):
data = data[0]
#print "data.shape1: ", data.shape
n = int(np.ceil(np.sqrt(data.shape[0])))
padding = ((0, n ** 2 - data.shape[0]), (0, 0), (0, padsize)) + ((0, 0),) * (data.ndim - 3)
#print "padding: ", padding
data = np.pad(data, padding, mode='constant', constant_values=(padval, padval))
#print "data.shape2: ", data.shape
data = data.reshape((n, n) + data.shape[1:]).transpose((0, 2, 1, 3) + tuple(range(4, data.ndim + 1)))
#print "data.shape3: ", data.shape, n
data = data.reshape((n * data.shape[1], n * data.shape[3]) + data.shape[4:])
#print "data.shape4: ", data.shape
plt.figure()
plt.imshow(data,cmap='gray')
plt.axis('off')
#plt.show() #修改2
if image_name == None:
img_path = './data/feature_picture/'
else:
img_path = './data/feature_picture/' + image_name + "/"
check_file(img_path)
plt.savefig(img_path + name + ".jpg", dpi = 400, bbox_inches = "tight")
def check_file(path):
if not os.path.exists(path):
os.mkdir(path)
def demo(net, image_name):
"""Detect object classes in an image using pre-computed object proposals."""
# Load the demo image
im_file = os.path.join(cfg.DATA_DIR, 'demo', image_name)
im = cv2.imread(im_file)
#im = cv2.imread('/home/snowflake/Downloads/Faster-Rcnn-Muzy/py-faster-rcnn/tools/8478075(11).jpg')
# Detect all object classes and regress object bounds
timer = Timer()
timer.tic()
scores, boxes = im_detect(net, im)
for k, v in net.blobs.items():
if k.find("conv")>-1 or k.find("pool")>-1 or k.find("rpn")>-1:
save_feature_picture(v.data, k.replace("/", ""), image_name)#net.blobs["conv1_1"].data, "conv1_1")
timer.toc()
print ('Detection took {:.3f}s for '
'{:d} object proposals').format(timer.total_time, boxes.shape[0])
# Visualize detections for each class
CONF_THRESH = 0.8
NMS_THRESH = 0.3
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]
dets = np.hstack((cls_boxes,
cls_scores[:, np.newaxis])).astype(np.float32)
keep = nms(dets, NMS_THRESH)
dets = dets[keep, :]
vis_detections(im, cls, dets, thresh=CONF_THRESH)
def parse_args():
"""Parse input arguments."""
parser = argparse.ArgumentParser(description='Faster R-CNN demo')
parser.add_argument('--gpu', dest='gpu_id', help='GPU device id to use [0]',
default=0, type=int)
parser.add_argument('--cpu', dest='cpu_mode',
help='Use CPU mode (overrides --gpu)',
action='store_true')
parser.add_argument('--net', dest='demo_net', help='Network to use [zf]',
choices=NETS.keys(), default='zf')
args = parser.parse_args()
return args
def print_param(net):
for k, v in net.blobs.items():
print (k, v.data.shape)
print ""
for k, v in net.params.items():
print (k, v[0].data.shape)
if __name__ == '__main__':
cfg.TEST.HAS_RPN = True # Use RPN for proposals
args = parse_args()
#修改4
prototxt = os.path.join(cfg.MODELS_DIR, NETS[args.demo_net][0],
'faster_rcnn_end2end', 'test.prototxt')
#print "prototxt: ", prototxt
caffemodel = os.path.join(cfg.DATA_DIR, 'faster_rcnn_models',
NETS[args.demo_net][1])
if not os.path.isfile(caffemodel):
raise IOError(('{:s} not found.\nDid you run ./data/script/'
'fetch_faster_rcnn_models.sh?').format(caffemodel))
if args.cpu_mode:
caffe.set_mode_cpu()
else:
caffe.set_mode_gpu()
caffe.set_device(args.gpu_id)
cfg.GPU_ID = args.gpu_id
net = caffe.Net(prototxt, caffemodel, caffe.TEST)
#print_param(net)
print '\n\nLoaded network {:s}'.format(caffemodel)
# Warmup on a dummy image
im = 128 * np.ones((300, 500, 3), dtype=np.uint8)
for i in xrange(2):
_, _= im_detect(net, im)
# im_names = ['8478075(11).jpg','8185056(15).jpg','8193000(14).jpg','8499359(12).jpg']
im_names = ['8478075(11).jpg']
# 修改 3
for im_name in im_names:
print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
print 'Demo for data/demo/{}'.format(im_name)
demo(net, im_name)
#plt.show()