源碼:https://github.com/rbgirshick/py-faster-rcnn
首先來看\tools\train_net.py,首先parse_args()讀入命令行設置的參數信息,然後讀取yml設置的參數信息,如下:
代碼段1:
if __name__ == '__main__':
args = parse_args()
print('Called with args:')
print(args)
if args.cfg_file is not None:
cfg_from_file(args.cfg_file) #讀取'.\\experiments\\cfgs\\faster_rcnn_end2end.yml'文件中的參數
if args.set_cfgs is not None:
cfg_from_list(args.set_cfgs)
cfg.GPU_ID = args.gpu_id #設置gpu id號
print('Using config:')
pprint.pprint(cfg)
if not args.randomize:
# fix the random seeds (numpy and caffe) for reproducibility
np.random.seed(cfg.RNG_SEED)
caffe.set_random_seed(cfg.RNG_SEED)
# set up caffe
caffe.set_mode_gpu()
caffe.set_device(args.gpu_id)
# imdb_name爲voc_2007_trainval,返回數據集,及數據集中的框標記信息(包括框類別,圖片路徑,寬高等),這裏roidb爲list,長度10022
imdb, roidb = combined_roidb(args.imdb_name)
print '{:d} roidb entries'.format(len(roidb))
output_dir = get_output_dir(imdb)
print 'Output will be saved to `{:s}`'.format(output_dir)
train_net(args.solver, roidb, output_dir,
pretrained_model=args.pretrained_model,
max_iters=args.max_iters)
1. combined_roidb函數解析
代碼段1中的重點函數:
imdb, roidb = combined_roidb(args.imdb_name)
這裏args.imdb_name爲voc_2007_trainval,這個函數根據這個數據集名稱,加載數據集接口給到imdb, 而roidb則爲數據集中圖片的標註框座標及類別信息(包括框類別、座標、圖片路徑、圖片寬高等)。先看看這個函數的輸出結果:
進入該函數的實現,也是在train_net.py文件中,如下代碼段2:
代碼段2:
def combined_roidb(imdb_names):#imdb_names爲數據集名稱列表,'imdb_names:voc_2007_trainval'
def get_roidb(imdb_name):#imdb_names:voc_2007_trainval
imdb = get_imdb(imdb_name) #返回voc2007數據集
print 'Loaded dataset `{:s}` for training'.format(imdb.name)
imdb.set_proposal_method(cfg.TRAIN.PROPOSAL_METHOD) #PROPOSAL_METHOD:'gt', 設置調用的roi函數,在pascal_voc中的gt_roidb
print 'Set proposal method: {:s}'.format(cfg.TRAIN.PROPOSAL_METHOD)
roidb = get_training_roidb(imdb) #翻轉樣本(可選),並獲取數據集的圖片路徑、圖片寬高、框的類別等,返回這些信息imdb.roidb
return roidb
roidbs = [get_roidb(s) for s in imdb_names.split('+')] #獲取各個數據集的訓練樣本的標註框信息
roidb = roidbs[0] #獲取第一個數據集的標註框信息
if len(roidbs) > 1: #如果是多個數據集
for r in roidbs[1:]: #將其他數據集的數據添加到roidb末尾
roidb.extend(r)
imdb = datasets.imdb.imdb(imdb_names)
else:
imdb = get_imdb(imdb_names)
return imdb, roidb
代碼段2的第10行,imdb_names是一個列表,數據集名稱用‘+’分割,這裏調用get_roidb獲取各個數據集的信息。在get_roidb中,調用lib\datasets\factory.py中的get_imdb獲取數據集索引,如下代碼段3,__sets在lib\datasets\pascal_voc.py中已加載voc2007數據集的信息
代碼段3:
def get_imdb(name):
"""Get an imdb (image database) by name."""
if not __sets.has_key(name):
raise KeyError('Unknown dataset: {}'.format(name))
return __sets[name]() #返回已加載的voc2007數據集
代碼段2的第5行,imdb.set_proposal_method(cfg.TRAIN.PROPOSAL_METHOD) 設置訓練樣本標註框的獲取方式,這裏PROPOSAL_METHOD爲‘gt’,調用到lib\datasets\imdb.py以下函數,
代碼段4:
def set_proposal_method(self, method):#method:'gt'
method = eval('self.' + method + '_roidb') #self.gt_roidb,在子類pascal_voc中實現
self.roidb_handler = method #將self.roidb_handler綁定爲method函數接口
method爲self.gt_roidb,這裏將imdb.py文件中的imdb類本身並沒有gt_roidb函數,該函數的實現由pascal_voc.py中的pascal_voc類來實現,而pascal_voc繼承了imdb類,gt_roidb的實現如下:
代碼段5:
def gt_roidb(self):
"""
Return the database of ground-truth regions of interest.
This function loads/saves from/to a cache file to speed up future calls.
"""
#cache_path:'C:\\zhh\\py-faster-rcnn\\data\\cache', 'voc_2007_trainval'
cache_file = os.path.join(self.cache_path, self.name + '_gt_roidb.pkl')#'C:\\zhh\\py-faster-rcnn\\data\\cache\\voc_2007_trainval_gt_roidb.pkl'
if os.path.exists(cache_file):
with open(cache_file, 'rb') as fid:
roidb = cPickle.load(fid)
print '{} gt roidb loaded from {}'.format(self.name, cache_file)
return roidb
gt_roidb = [self._load_pascal_annotation(index)
for index in self.image_index]
with open(cache_file, 'wb') as fid:
cPickle.dump(gt_roidb, fid, cPickle.HIGHEST_PROTOCOL)
print 'wrote gt roidb to {}'.format(cache_file)
return gt_roidb
gt_roidb就是將py-faster-rcnn\\data\\cache\\voc_2007_trainval_gt_roidb.pkl中的標註框信息解析出來。
在回來看代碼段2中的第7行, roidb = get_training_roidb(imdb) ,這個函數從voc2007數據集中獲取標註信息,roidb爲一個List,大小爲10022,在get_trainning_roidb函數中,爲了擴充數據,對voc2007的5011張訓練圖片做了翻轉處理,得到10022張訓練集圖片,roidb的信息如下:
再來看get_trainning_roidb的實現,這個函數在\tools\train.py中實現如下:
代碼段6:
#翻轉樣本(可選),並獲取數據集的圖片路徑、圖片寬高、框的類別等,返回這些信息imdb.roidb
def get_training_roidb(imdb):
"""Returns a roidb (Region of Interest database) for use in training."""
if cfg.TRAIN.USE_FLIPPED: #是否翻轉樣本,這裏是True
print 'Appending horizontally-flipped training examples...'
imdb.append_flipped_images() #對樣本做左右翻轉,樣本數量翻倍
print 'done'
print 'Preparing training data...'
rdl_roidb.prepare_roidb(imdb) #獲取數據集的圖片路徑、圖片寬高、框的類別等
print 'done'
return imdb.roidb
代碼段6中的第6行imdb.append_flipped_images() 對訓練樣本做翻轉處理,其實現如下,可以看到其實並沒有對圖片本身做翻轉,而是計算翻轉後的標註框的座標,然後用flipped:true標記是否做翻轉處理。
代碼段7:
# 對5011張訓練集圖片的標註框做左右翻轉,需要處理的是標記框中的x座標要做處理。處理後得到5011x2張訓練集圖片信息
def append_flipped_images(self):
num_images = self.num_images #5011
widths = self._get_widths() #widths:list, len5011, voc2007中每張訓練圖片的寬度
for i in xrange(num_images):#
boxes = self.roidb[i]['boxes'].copy() #調用pascal_voc 中的gt_roidb讀取voc2007 pkl文件,self.roidb返回的是一個list,大小爲5011, 這裏獲取第i圖片的標記框座標
oldx1 = boxes[:, 0].copy() #獲取框的左上角 x 座標,這裏有3個boxes, 因此oldx1長度爲3,[262, 164, 240]
oldx2 = boxes[:, 2].copy()#獲取框的右下角 x 座標,這裏有3個boxes, 因此oldx1長度爲3,[323, 252, 294]
boxes[:, 0] = widths[i] - oldx2 - 1 #寬度減去右下角 x 座標,作爲框的左上角x 座標,即將圖片左右翻轉
boxes[:, 2] = widths[i] - oldx1 - 1 #寬度減去左上角 x 座標,作爲框的右下角 x 座標,即將圖片左右翻轉
assert (boxes[:, 2] >= boxes[:, 0]).all() #右下角 x 座標 要比 左上角 x 座標 值要大
entry = {'boxes' : boxes,
'gt_overlaps' : self.roidb[i]['gt_overlaps'],
'gt_classes' : self.roidb[i]['gt_classes'],
'flipped' : True}
self.roidb.append(entry) #將翻轉後的圖片加入到roidb隊列
self._image_index = self._image_index * 2 #圖片名稱複製一份
再來看代碼段6中的第10行rdl_roidb.prepare_roidb(imdb) 這個函數獲取擴充後的數據集的圖片路徑、圖片寬高、框的類別等,其在lib\roi_data_layer\roidb.py中實現如下:
代碼段8:
def prepare_roidb(imdb):
"""Enrich the imdb's roidb by adding some derived quantities that
are useful for training. This function precomputes the maximum
overlap, taken over ground-truth boxes, between each ROI and
each ground-truth box. The class with maximum overlap is also
recorded.
"""
sizes = [PIL.Image.open(imdb.image_path_at(i)).size
for i in xrange(imdb.num_images)]#10022(原來5011張,翻轉後10022張)張訓練圖片的寬高
roidb = imdb.roidb #10022張訓練圖片的標註框信息
for i in xrange(len(imdb.image_index)):
roidb[i]['image'] = imdb.image_path_at(i) #圖片的路徑,第一張圖爲000005.jpg,該圖中標註有3個框,都是chair(類別序號爲9)
roidb[i]['width'] = sizes[i][0] #圖片的寬
roidb[i]['height'] = sizes[i][1] #圖片的高
# need gt_overlaps as a dense array for argmax
gt_overlaps = roidb[i]['gt_overlaps'].toarray() #3x21, 有3個框,一行表示1個框,每列表示該框的類別,值爲1,表示框的類別
# max overlap with gt over classes (columns)
max_overlaps = gt_overlaps.max(axis=1) #獲取3x21中行方向(水平方向)中的最大值這裏得到[1,1,1]
# gt class that had the max overlap
max_classes = gt_overlaps.argmax(axis=1) #獲取行方向(水平)中最大值的列序號,這裏得到[9,9,9]# 框的類別
roidb[i]['max_classes'] = max_classes # 框的類別
roidb[i]['max_overlaps'] = max_overlaps
# sanity checks
# max overlap of 0 => class should be zero (background)
zero_inds = np.where(max_overlaps == 0)[0] #zero_inds空
assert all(max_classes[zero_inds] == 0)
# max overlap > 0 => class should not be zero (must be a fg class)
nonzero_inds = np.where(max_overlaps > 0)[0] #[0,1,2]
assert all(max_classes[nonzero_inds] != 0)
在prepare_roidb中遍歷擴充後的10022張訓練集圖片,獲取圖片的路徑、寬高,標註框的類別,保存到imdb的roidb中。至此代碼段2分析完畢。
2. train_net解析