前言
前面已經介紹瞭如何準備數據集,以及如何修改數據集讀寫接口來操作數據集,接下來我來說明一下怎麼來訓練網絡和之後的檢測過程。
修改模型文件
faster rcnn有兩種各種訓練方式:
- Alternative training(alt-opt)
- Approximate joint training(end-to-end)
兩種方法有什麼不同,可以參考我這篇博客,推薦使用第二種,因爲第二種使用的顯存更小,而且訓練會更快,同時準確率差不多,兩種方式需要修改的代碼是不一樣的,同時faster rcnn提供了三種訓練模型,小型的ZF model,中型的VGG_CNN_M_1024和大型的VGG16,論文中說VGG16效果比其他兩個好,但是同時佔用更大的GPU顯存(~11GB)
我使用的是VGG model + alternative training,需要檢測的類別只有一類,加上背景所以總共是兩類(background + person)。
下面修改模型文件:
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/stage1_fast_rcnn_train.pt
layer { name: 'data' type: 'Python' top: 'data' top: 'rois' top: 'labels' top: 'bbox_targets' top: 'bbox_inside_weights' top: 'bbox_outside_weights' python_param { module: 'roi_data_layer.layer' layer: 'RoIDataLayer' param_str: "'num_classes': 2" #按訓練集類別改,該值爲類別數+1 } }
layer { name: "cls_score" type: "InnerProduct" bottom: "fc7" top: "cls_score" param { lr_mult: 1.0 } param { lr_mult: 2.0 } inner_product_param { num_output: 2 #按訓練集類別改,該值爲類別數+1 weight_filler { type: "gaussian" std: 0.01 } bias_filler { type: "constant" value: 0 } } }
layer { name: "bbox_pred" type: "InnerProduct" bottom: "fc7" top: "bbox_pred" param { lr_mult: 1.0 } param { lr_mult: 2.0 } inner_product_param { num_output: 8 #按訓練集類別改,該值爲(類別數+1)*4,四個頂點座標 weight_filler { type: "gaussian" std: 0.001 } bias_filler { type: "constant" value: 0 } } }
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/stage1_rpn_train.pt
layer { name: 'input-data' type: 'Python' top: 'data' top: 'im_info' top: 'gt_boxes' python_param { module: 'roi_data_layer.layer' layer: 'RoIDataLayer' param_str: "'num_classes': 2" #按訓練集類別改,該值爲類別數+1 } }
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/stage2_fast_rcnn_train.pt
layer { name: 'data' type: 'Python' top: 'data' top: 'rois' top: 'labels' top: 'bbox_targets' top: 'bbox_inside_weights' top: 'bbox_outside_weights' python_param { module: 'roi_data_layer.layer' layer: 'RoIDataLayer' param_str: "'num_classes': 2" #按訓練集類別改,該值爲類別數+1 } }
layer { name: "cls_score" type: "InnerProduct" bottom: "fc7" top: "cls_score" param { lr_mult: 1.0 } param { lr_mult: 2.0 } inner_product_param { num_output: 2 #按訓練集類別改,該值爲類別數+1 weight_filler { type: "gaussian" std: 0.01 } bias_filler { type: "constant" value: 0 } } }
layer { name: "bbox_pred" type: "InnerProduct" bottom: "fc7" top: "bbox_pred" param { lr_mult: 1.0 } param { lr_mult: 2.0 } inner_product_param { num_output: 8 #按訓練集類別改,該值爲(類別數+1)*4,四個頂點座標 weight_filler { type: "gaussian" std: 0.001 } bias_filler { type: "constant" value: 0 } } }
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/stage2_rpn_train.pt
layer { name: 'input-data' type: 'Python' top: 'data' top: 'im_info' top: 'gt_boxes' python_param { module: 'roi_data_layer.layer' layer: 'RoIDataLayer' param_str: "'num_classes': 2" #按訓練集類別改,該值爲類別數+1 } }
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/faster_rcnn_test.pt
layer { name: "cls_score" type: "InnerProduct" bottom: "fc7" top: "cls_score" inner_product_param { num_output: 2 #按訓練集類別改,該值爲類別數+1 } } layer { name: "bbox_pred" type: "InnerProduct" bottom: "fc7" top: "bbox_pred" inner_product_param { num_output: 84 #按訓練集類別改,該值爲(類別數+1)*4,四個頂點座標 } }
訓練測試
訓練前還需要注意幾個地方:
cache問題:
假如你之前訓練了官方的VOC2007的數據集或其他的數據集,是會產生cache的問題的,建議在重新訓練新的數據之前將其刪除。
py-faster-rcnn/output
py-faster-rcnn/data/cache
訓練參數
參數放在如下文件:
py-faster-rcnn/models/pascal_voc/VGG16/faster_rcnn_alt_opt/stage_fast_rcnn_solver*.pt
base_lr: 0.001 lr_policy: 'step' step_size: 30000 display: 20 ....
迭代次數在文件py-faster-rcnn/tools/train_faster_rcnn_alt_opt.py中進行修改:
max_iters = [80000, 40000, 80000, 40000]
分別對應rpn第1階段,fast rcnn第1階段,rpn第2階段,fast rcnn第2階段的迭代次數,自己修改即可,不過注意這裏的值不要小於上面的solver裏面的step_size的大小,大家自己修改吧
開始訓練
首先修改experiments/scripts/faster_rcnn_alt_opt.sh
成如下,修改地方已標註:
#!/bin/bash
# Usage:
# ./experiments/scripts/faster_rcnn_alt_opt.sh GPU NET DATASET [options args to {train,test}_net.py]
# DATASET is only pascal_voc for now
#
# Example:
# ./experiments/scripts/faster_rcnn_alt_opt.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
caltech) # 這裏將pascal_voc改爲caltech
TRAIN_IMDB="caltech_train" # 改爲與factor.py中命名的name格式相同,爲caltech_train
TEST_IMDB="caltech_test" # 改爲與factor.py中命名的name格式相同,爲caltech_test
PT_DIR="caltech" # 這裏將pascal_voc改爲caltech
ITERS=40000
;;
coco)
echo "Not implemented: use experiments/scripts/faster_rcnn_end2end.sh for coco"
exit
;;
*)
echo "No dataset given"
exit
;;
esac
LOG="experiments/logs/faster_rcnn_alt_opt_${NET}_${EXTRA_ARGS_SLUG}.txt.`date +'%Y-%m-%d_%H-%M-%S'`"
exec &> >(tee -a "$LOG")
echo Logging output to "$LOG"
time ./tools/train_faster_rcnn_alt_opt.py --gpu ${GPU_ID} \
--net_name ${NET} \
--weights data/imagenet_models/${NET}.v2.caffemodel \
--imdb ${TRAIN_IMDB} \
--cfg experiments/cfgs/faster_rcnn_alt_opt.yml \
${EXTRA_ARGS}
set +x
NET_FINAL=`grep "Final model:" ${LOG} | awk '{print $3}'`
set -x
time ./tools/test_net.py --gpu ${GPU_ID} \
--def models/${PT_DIR}/${NET}/faster_rcnn_alt_opt/faster_rcnn_test.pt \
--net ${NET_FINAL} \
#--net output/faster_rcnn_alt_opt/train/ZF_faster_rcnn_final.caffemodel \
--imdb ${TEST_IMDB} \
--cfg experiments/cfgs/faster_rcnn_alt_opt.yml \
${EXTRA_ARGS}
調用如下命令進行訓練及測試,從上面代碼可以看出,該shell文件在訓練完後會接着進行測試,但是我的測試集沒有標註,所以測試的時候會報錯,但是由於Caltech數據集的測試結果有專門的評估代碼,所以我不用faster r-cnn提供的代碼進行測試,而是直接進行檢測生成座標,用專門的評估代碼進行檢測。
cd py-faster-rcnn
./experiments/scripts/faster_rcnn_alt_opt.sh 0 VGG16 caltech
- 參數1:指定gpu_id。
- 參數2:指定網絡模型參數。
- 參數3:數據集名稱,目前只能爲
pascal_voc
。
在訓練過程中,會調用py_faster_rcnn/tools/train_faster_rcnn_alt_opt.py
文件開始訓練網絡。
可能會出現的Bugs
AssertionError: assert (boxes[:, 2] >= boxes[:, 0]).all()
問題重現
在訓練過程中可能會出現如下報錯:
File "/py-faster-rcnn/tools/../lib/datasets/imdb.py", line 108, in
append_flipped_images
assert (boxes[:, 2] >= boxes[:, 0]).all()
AssertionError
問題分析
檢查自己數據發現,左上角座標 (x, y) 可能爲0,或標定區域溢出圖片(即座標爲負數),而faster rcnn會對Xmin,Ymin,Xmax,Ymax進行減一操作,如果Xmin爲0,減一後變爲65535,從而在左右翻轉圖片時導致如上錯誤發生。
問題解決
修改
lib/datasets/imdb.py
中的append_flipped_images()
函數:數據整理,在一行代碼爲
boxes[:, 2] = widths[i] - oldx1 - 1
下加入代碼:for b in range(len(boxes)): if boxes[b][2]< boxes[b][0]: boxes[b][0] = 0
修改
lib/datasets/caltech.py
,_load_pascal_annotation()
函數,將對Xmin,Ymin,Xmax,Ymax減一去掉,變爲:# Load object bounding boxes into a data frame. for ix, obj in enumerate(objs): bbox = obj.find('bndbox') # Make pixel indexes 0-based # 這裏我把‘-1’全部刪除掉了,防止有的數據是0開始,然後‘-1’導致變爲負數,產生AssertError錯誤 x1 = float(bbox.find('xmin').text) y1 = float(bbox.find('ymin').text) x2 = float(bbox.find('xmax').text) y2 = float(bbox.find('ymax').text) cls = self._class_to_ind[obj.find('name').text.lower().strip()] boxes[ix, :] = [x1, y1, x2, y2] gt_classes[ix] = cls overlaps[ix, cls] = 1.0 seg_areas[ix] = (x2 - x1 + 1) * (y2 - y1 + 1)
(可選)如果1和2可以解決問題,就沒必要用方法3。修改
lib/fast_rcnn/config.py
,不使圖片實現翻轉,如下改爲:# Use horizontally-flipped images during training? __C.TRAIN.USE_FLIPPED = False
如果如上三種方法都無法解決該問題,那麼肯定是你的數據集座標出現小於等於0的數,你應該一一排查。
訓練fast rcnn時出現loss=nan的情況。
問題重現
問題分析
這是由於模型不收斂,導致loss迅速增長。
而我出現以上現象的原因主要是因爲我在出現AssertionError的時候直接使用了第三種方法導致的。也就是禁用圖片翻轉。
問題解決
啓用圖片翻轉。
訓練結果
訓練後的模型放在output/faster_rcnn_alt_opt/train/VGG16_faster_rcnn_final.caffemodel
,該模型可以用於之後的檢測。
檢測
檢測步驟
經過以上訓練後,就可以用得到的模型來進行檢測了。檢測所參考的代碼是tools/demo.py
,具體步驟如下:
- 將
output/faster_rcnn_alt_opt/train/VGG16_faster_rcnn_final.caffemodel
,拷貝到data/faster_rcnn_models
下,命名爲VGG16_Caltech_faster_rcnn__final.caffemodel
- 進入
tools/
文件夾中,拷貝demo.py
爲demo_caltech.py
。 - 修改demo_caltech.py代碼如下:
#!/usr/bin/env python
# --------------------------------------------------------
# Faster R-CNN
# Copyright (c) 2015 Microsoft
# Licensed under The MIT License [see LICENSE for details]
# Written by Ross Girshick
# --------------------------------------------------------
import matplotlib
matplotlib.use('Agg');
"""
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__', # 這裏改爲自己的類別
'person')
NETS = {'vgg16': ('VGG16',
'VGG16_Caltech_faster_rcnn_final.caffemodel'), #這裏需要修改爲訓練後得到的模型的名稱
'zf': ('ZF',
'ZF_Caltech_faster_rcnn_final.caffemodel')} #這裏需要修改爲訓練後得到的模型的名稱
def vis_detections(im, image_name, 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()
plt.savefig('/home/jk/py-faster-rcnn/output/faster_rcnn_alt_opt/test/'+image_name) #將檢測後的圖片保存到相應的路徑
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, 'VOCdevkit/Caltech/JPEGImages', 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.85 # 設置權值,越低檢測出的框越多
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, image_name, 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 [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_alt_opt', 'faster_rcnn_test.pt')
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)
testfile_path = '/home/jk/py-faster-rcnn/data/VOCdevkit/Caltech/ImageSets/Main/test.txt'
with open(testfile_path) as f:
im_names = [x.strip()+'.jpg' for x in f.readlines()] # 從test.txt文件中讀取圖片文件名,找到相應的圖片進行檢測。也可以使用如下的方法,把項檢測的圖片存到tools/demo/文件夾下進行讀取檢測
#im_names = ['set06_V002_I00023.jpg', 'set06_V002_I00072.jpg', 'set06_V002_I00097.jpg',
# 'set06_V002_I00151.jpg', 'set07_V010_I00247.jpg']
for im_name in im_names:
print '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
print 'Demo for data/demo/{}'.format(im_name)
demo(net, im_name)
plt.show()
在命令行中輸入一下命令進行檢測:
python tools/demo_caltech.py
檢測結果
放幾張檢測後的結果圖,感覺檢測效果並不是很好,很多把背景當成行人的錯誤: