最後更新日期:2016年4月29日html
本教程主要基於python版本的faster R-CNN,由於python layer的使用,這個版本會比matlab的版本速度慢10%,可是準確率應該是差很少的。python
目前已經實現的有兩種方式:git
推薦使用第二種,由於第二種使用的顯存更小,並且訓練會更快,同時準確率差很少甚至略高一點。github
1配置python layersshell
#In your Makefile.config, make sure to have this line uncommented WITH_PYTHON_LAYER := 1 # Unrelatedly, it's also recommended that you use CUDNN USE_CUDNN := 1
2安裝幾個依賴cython, python-opencv, easydict
數據庫
sudo apt-get install python-opencv sudo pip install cython easydict
1克隆工程緩存
git clone --recursive https://github.com/rbgirshick/py-faster-rcnn.git
2編譯Cython模塊網絡
cd $FRCN_ROOT/lib make
3編譯caffe和pycaffeapp
cd $FRCN_ROOT/caffe-fast-rcnn # Now follow the Caffe installation instructions here: # http://caffe.berkeleyvision.org/installation.html # If you're experienced with Caffe and have all of the requirements installed # and your Makefile.config in place, then simply do: make -j8 && make pycaffe
安裝步驟完成後,就能夠運行一下demo了。框架
cd $FRCN_ROOT ./tools/demo.py
首先工程的根目錄簡單的稱爲 FRCN_ROOT,能夠看到根目錄下有如下幾個文件夾
這裏是caffe框架目錄
用來存放pretrained模型,好比imagenet上的,以及讀取文件的cache緩存
存放配置文件以及運行的log文件,另外這個目錄下有scripts能夠用end2end或者alt_opt兩種方式訓練。
用來存放一些python接口文件,如其下的datasets主要負責數據庫讀取,config負責cnn一些訓練的配置選項。
裏面存放了三個模型文件,小型網絡的ZF,大型網絡VGG16,中型網絡VGG_CNN_M_1024。推薦使用VGG16,若是使用端到端的approximate joint training方法,開啓CuDNN,只須要3G的顯存便可。
這裏存放的是訓練完成後的輸出目錄,默認會在faster_rcnn_end2end文件夾下
裏面存放的是訓練和測試的Python文件。
接下來咱們就要建立本身的數據集了,這部分主要在lib目錄裏操做。這裏下面存在3個目錄:
在這裏修改讀寫數據的接口主要是datasets目錄下
主要存放的是python的訓練和測試腳本,以及訓練的配置文件config.py
作非極大抑制的部分,有gpu和cpu兩種實現方式
主要是一些ROI處理操做
這就是RPN的核心代碼部分,有生成proposals和anchor的方法
transform
utils
1構建本身的IMDB子類
1.1文件概述
可有看到datasets目錄下主要有三個文件,分別是
factory.py 是個工廠類,用類生成imdb類而且返回數據庫共網絡訓練和測試使用;imdb.py 這裏是數據庫讀寫類的基類,分裝了許多db的操做,可是具體的一些文件讀寫須要繼承繼續讀寫;pascal_voc.py Ross在這裏用pascal_voc.py這個類來操做。
1.2讀取文件函數分析
接下來我來介紹一下pasca_voc.py這個文件,咱們主要是基於這個文件進行修改,裏面有幾個重要的函數須要修改
1.3訓練數據格式
在個人檢測任務裏,我主要是在SED數據集上作行人檢測,所以我這裏只有background 和person 兩類物體,爲了操做方便,我像pascal_voc數據集裏面同樣每一個圖像用一個xml來標註。若是你們不知道怎麼生成xml文件,能夠用這個工具 labelImg?
這裏我要特別提醒一下你們,必定要注意座標格式,必定要注意座標格式,必定要注意座標格式,重要的事情說三遍!!!要否則你會犯不少錯誤都會是由於座標不一致引發的報錯。
1.4修改讀取接口
這裏是原始的pascal_voc的init函數,在這裏,因爲咱們本身的數據集每每比voc的數據集要更簡單的一些,在做者代碼裏面用了不少的路徑拼接,咱們不用去迎合他的格式,將這些操做簡單化便可,在這裏我會一一列舉每一個我修改過的函數。這裏按照文件中的順序排列。
修改後的初始化函數:
class hs(imdb): def __init__(self, image_set, devkit_path=None): # modified imdb.__init__(self, image_set) self._image_set = image_set self._devkit_path = devkit_path #datasets路徑 self._data_path = os.path.join(self._devkit_path,image_set) #圖片文件夾路徑 self._classes = ('__background__', # always index 0 'person') #two classes self._class_to_ind = dict(zip(self.classes, xrange(self.num_classes))) # form the dict{'__background__':'0','person':'1'} self._image_ext = '.jpg' self._image_index = self._load_image_set_index('ImageList.txt') # Default to roidb handler self._roidb_handler = self.selective_search_roidb self._salt = str(uuid.uuid4()) self._comp_id = 'comp4' # PASCAL specific config options self.config = {'cleanup' : True, 'use_salt' : True, 'use_diff' : False, 'matlab_eval' : False, 'rpn_file' : None, 'min_size' : 16} #小於16個像素的框扔掉 assert os.path.exists(self._devkit_path), \ 'VOCdevkit path does not exist: {}'.format(self._devkit_path) assert os.path.exists(self._data_path), \ 'Path does not exist: {}'.format(self._data_path)
修改後的image_path_from_index:
def image_path_from_index(self, index): #modified """ Construct an image path from the image's "index" identifier. """ image_path = os.path.join(self._data_path,index +'.jpg') assert os.path.exists(image_path), \ 'Path does not exist: {}'.format(image_path) return image_path
修改後的_load_image_set_index:
def _load_image_set_index(self,imagelist): # modified """ Load the indexes listed in this dataset's image set file. """ # Example path to image set file: # self._devkit_path + /VOCdevkit2007/VOC2007/ImageSets/Main/val.txt image_set_file = os.path.join(self._devkit_path, imagelist) assert os.path.exists(image_set_file), \ 'Path does not exist: {}'.format(image_set_file) with open(image_set_file) as f: image_index = [x.strip() for x in f.readlines()] return image_index
gt_roidb(self):
這個函數裏有個生成ground truth的文件,我須要特別說明一下,若是你再次訓練的時候修改了數據庫,好比添加或者刪除了一些樣本,可是你的數據庫名字函數原來那個,必需要在data/cache/目錄下把數據庫的緩存文件.pkl給刪除掉,不然其不會從新讀取相應的數據庫,而是直接從以前讀入而後緩存的pkl文件中讀取進來,這樣修改的數據庫並無進入網絡,而是加載了老版本的數據。
修改的_load_pascal_annotation(self, index):
def _load_pascal_annotation(self, index): #modified """ Load image and bounding boxes info from XML file in the PASCAL VOC format. """ filename = os.path.join(self._devkit_path, 'Annotations', index + '.xml') tree = ET.parse(filename) objs = tree.findall('object') if not self.config['use_diff']: # Exclude the samples labeled as difficult non_diff_objs = [ obj for obj in objs if int(obj.find('difficult').text) == 0] # if len(non_diff_objs) != len(objs): # print 'Removed {} difficult objects'.format( # len(objs) - len(non_diff_objs)) objs = non_diff_objs num_objs = len(objs) boxes = np.zeros((num_objs, 4), dtype=np.uint16) gt_classes = np.zeros((num_objs), dtype=np.int32) overlaps = np.zeros((num_objs, self.num_classes), dtype=np.float32) # "Seg" area for pascal is just the box area seg_areas = np.zeros((num_objs), dtype=np.float32) # Load object bounding boxes into a data frame. for ix, obj in enumerate(objs): bbox = obj.find('bndbox') # Make pixel indexes 0-based 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) overlaps = scipy.sparse.csr_matrix(overlaps) return {'boxes' : boxes, 'gt_classes': gt_classes, 'gt_overlaps' : overlaps, 'flipped' : False, 'seg_areas' : seg_areas}
由於我和Pascal用了同樣的xml格式,因此這個函數個人改動很少。若是你想用txt文件保存ground truth,作出相應的修改便可。
座標的順序強調一下,要左上右下,而且x1必需要小於x2,這個是基本,反了會在座標水平變換的時候會出錯,座標從0開始,若是已是0,則不須要再-1。若是怕出錯,能夠直接把出界的的直接置0.
記得在最後的main下面也修改相應的路徑
from datasets.hs import hs d = hs('hs', '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/') res = d.roidb from IPython import embed; embed()
OK,在這裏咱們已經完成了整個的讀取接口的改寫。
2修改factory.py
當網絡訓練時會調用factory裏面的get方法得到相應的imdb,
首先在文件頭import 把pascal_voc改爲hs
# -------------------------------------------------------- # Fast R-CNN # Copyright (c) 2015 Microsoft # Licensed under The MIT License [see LICENSE for details] # Written by Ross Girshick # -------------------------------------------------------- """Factory method for easily getting imdbs by name.""" __sets = {} from datasets.hs import hs import numpy as np # # Set up voc_<year>_<split> using selective search "fast" mode # for year in ['2007', '2012']: # for split in ['train', 'val', 'trainval', 'test']: # name = 'voc_{}_{}'.format(year, split) # __sets[name] = (lambda split=split, year=year: pascal_voc(split, year)) # # # Set up coco_2014_<split> # for year in ['2014']: # for split in ['train', 'val', 'minival', 'valminusminival']: # name = 'coco_{}_{}'.format(year, split) # __sets[name] = (lambda split=split, year=year: coco(split, year)) # # # Set up coco_2015_<split> # for year in ['2015']: # for split in ['test', 'test-dev']: # name = 'coco_{}_{}'.format(year, split) # __sets[name] = (lambda split=split, year=year: coco(split, year)) name = 'hs' devkit = '/home/zyy/workspace/wangml/py-faster-rcnn/lib/datasets/' __sets['hs'] = (lambda name = name,devkit = devkit: hs(name,devkit)) 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]() def list_imdbs(): """List all registered imdbs.""" return __sets.keys()
1.預訓練模型介紹
首先在data目錄下,有兩個目錄
faster_rcnn_models/
imagenet_models/
faster_rcnn_model文件夾下面是做者用faster rcnn訓練好的三個網絡,分別對應着小、中、大型網絡,你們能夠試用一下這幾個網絡,看一些檢測效果,他們訓練都迭代了80000次,數據集都是pascal_voc的數據集。
imagenet_model文件夾下面是在Imagenet上訓練好的通用模型,在這裏用來初始化網絡的參數.
在這裏我比較推薦先用中型網絡訓練,中型網絡訓練和檢測的速度都比較快,效果也都比較理想,大型網絡的話訓練速度比較慢,中型網絡訓練大概半天,大型網絡的話用25個小時。
2.修改模型文件配置
模型文件在models下面對應的網絡文件夾下,在這裏我用中型網絡的配置文件修改成例子
好比:個人檢測目標物是person ,那麼個人類別就有兩個類別即 background 和 person
所以,首先打開網絡的模型文件夾,打開train.prototxt
修改的地方重要有三個
分別是個地方
OK,若是你要進一步修改網絡訓練中的學習速率,步長,gamma值,以及輸出模型的名字,須要在同目錄下的solver.prototxt中修改。
3.啓動Fast RCNN網絡訓練
python ./tools/train_net.py --gpu 1 --solver models/hs/faster_rcnn_end2end/solver.prototxt --weights data/imagenet_models/VGG_CNN_M_1024.v2.caffemodel --imdb hs --iters 80000 --cfg experiments/cfgs/faster_rcnn_end2end.yml
參數講解:
這裏的–是兩個-,不要輸錯
train_net.py是網絡的訓練文件,以後的參數都是附帶的輸入參數
--gpu 表明機器上的GPU編號,若是是nvidia系列的tesla顯卡,能夠在終端中輸入nvidia-smi來查看當前的顯卡負荷,選擇合適的顯卡
--solver 表明模型的配置文件,train.prototxt的文件路徑已經包含在這個文件之中
--weights 表明初始化的權重文件,這裏用的是Imagenet上預訓練好的模型,中型的網絡咱們選擇用VGG_CNN_M_1024.v2.caffemodel
--imdb 這裏給出的訓練的數據庫名字須要在factory.py的_sets中,我在文件裏面有_sets[‘hs’],train_net.py這個文件會調用factory.py再生成hs這個類,來讀取數據
4.啓動Fast RCNN網絡檢測
能夠參考tools下面的demo.py 文件,來作檢測,而且將檢測的座標結果輸出到相應的txt文件中。
鑑於以前我用的版本是15年11月的版本,有些小夥伴在使用此教程時會有一些錯誤,因此我從新作了部分修訂,目前可以在2016年4月29日版本的版本上成功運行,若是有問題,隨時聯繫我。
參考博客:http://www.cnblogs.com/louyihang-loves-baiyan/p/4885659.html