Faster RCNN代碼理解(Python)

轉自http://www.infocool.net/kb/Python/201611/209696.html#原文地址

第一步,準備

從train_faster_rcnn_alt_opt.py入:html

  1. 初始化參數:args = parse_args() 採用的是Python的argparse 
    主要有–net_name,–gpu,–cfg等(在cfg中只是修改了幾個參數,其餘大部分參數在congig.py中,涉及到訓練整個網絡)。
  2. cfg_from_file(args.cfg_file) 這裏即是代用config中的函數cfg_from_file來讀取前面cfg文件中的參數,同時調用_merge_a_into_b函數把全部的參數整合,其中__C = edict() cfg = __C cfg是一個詞典(edict)數據結構。
  3. faster rcnn採用的是多進程,mp_queue是進程間用於通信的數據結構
import multiprocessing as mp mp_queue = mp.Queue()

同時solvers, max_iters, rpn_test_prototxt = get_solvers(args.net_name)獲得solver參數 
接下來便進入了訓練的各個階段。python


第二步,Stage 1 RPN, init from ImageNet model

cfg.TRAIN.SNAPSHOT_INFIX = 'stage1' mp_kwargs = dict( queue=mp_queue, imdb_name=args.imdb_name, init_model=args.pretrained_model, solver=solvers[0], max_iters=max_iters[0], cfg=cfg) p = mp.Process(target=train_rpn, kwargs=mp_kwargs) p.start() rpn_stage1_out = mp_queue.get() p.join()

能夠看到第一個步驟是用ImageNet的模型M0來Finetuning RPN網絡獲得模型M1。以訓練爲例,這裏的args參數都在腳本 experiments/scrips/faster_rcnn_alt_opt.sh中找到。主要關注train_rpn函數。 
對於train_rpn函數,主要分一下幾步:git

1.在config參數的基礎上改動參數,以適合當前任務,主要有

cfg.TRAIN.HAS_RPN = True cfg.TRAIN.BBOX_REG = False # applies only to Fast R-CNN bbox regression cfg.TRAIN.PROPOSAL_METHOD = 'gt'

這裏,關注proposal method 使用的是gt,後面會使用到gt_roidb函數,重要。github

2. 初始化化caffe

3. 準備roidb和imdb

主要涉及到的函數get_roidb 
在get_roidb函數中調用factory中的get_imdb根據__sets[name]中的key(一個lambda表達式)轉到pascol_voc類。class pascal_voc(imdb)在初始化本身的時候,先調用父類的初始化方法,例如:算法

{
    year:’2007’ image _set:’trainval’ devkit _path:’data/VOCdevkit2007’ data _path:’data /VOCdevkit2007/VOC2007’ classes:(…)_若是想要訓練本身的數據,須要修改這裏_ class _to _ind:{…} _一個將類名轉換成下標的字典 _ 創建索引0,1,2.... image _ext:’.jpg’ image _index: [‘000001’,’000003’,……]_根據trainval.txt獲取到的image索引_ roidb _handler: <Method gt_roidb > salt: <Object uuid > comp _id:’comp4’ config:{…} }

 

注意,在這裏,並無讀入任何數據,只是創建了圖片的索引。數組

imdb.set_proposal_method(cfg.TRAIN.PROPOSAL_METHOD)
  • 1

設置proposal方法,接上面,設置爲gt,這裏只是設置了生成的方法,第一次調用發生在下一句,roidb = get_training_roidb(imdb) –> append_flipped_images()時的這行代碼:「boxes = self.roidb[i][‘boxes’].copy()」,其中get_training_roidb位於train.py,主要實現圖片的水平翻轉,並添加回去。實際是該函數調用了imdb. append_flipped_images也就是在這個函數,調用了pascal_voc中的gt_roidb,轉而調用了同一個文件中的_load_pascal_annotation,該函數根據圖片的索引,到Annotations這個文件夾下去找相應的xml標註數據,而後加載全部的bounding box對象,xml的解析到此結束,接下來是roidb中的幾個類成員的賦值:網絡

  • boxes 一個二維數組,每一行存儲 xmin ymin xmax ymax
  • gt _classes存儲了每一個box所對應的類索引(類數組在初始化函數中聲明)
  • gt _overlap是一個二維數組,共有num _classes(即類的個數)行,每一行對應的box的類索引處值爲1,其他皆爲0,後來被轉成了稀疏矩陣
  • seg _areas存儲着某個box的面積
  • flipped 爲false 表明該圖片還未被翻轉(後來在train.py裏會將翻轉的圖片加進去,用該變量用於區分

最後將這些成員變量組裝成roidb返回。 
在get_training_roidb函數中還調用了roidb中的prepare_roidb函數,這個函數就是用來準備imdb 的roidb,給roidb中的字典添加一些屬性,好比image(圖像的索引),width,height,經過前面的gt _overla屬性,獲得max_classes和max_overlaps. 
至此,數據結構

return roidb,imdb

4. 設置輸出路徑,output_dir = get_output_dir(imdb),函數在config中,用來保存中間生成的caffemodule等

5.正式開始訓練

model_paths = train_net(solver, roidb, output_dir,
                        pretrained_model=init_model,
                        max_iters=max_iters)

調用train中的train_net函數,其中,首先filter_roidb,判斷roidb中的每一個entry是否合理,合理定義爲至少有一個前景box或背景box,roidb全是groudtruth時,由於box與對應的類的重合度(overlaps)顯然爲1,也就是說roidb起碼要有一個標記類。若是roidb包含了一些proposal,overlaps在[BG_THRESH_LO, BG_THRESH_HI]之間的都將被認爲是背景,大於FG_THRESH才被認爲是前景,roidb 至少要有一個前景或背景,不然將被過濾掉。將沒用的roidb過濾掉之後,返回的就是filtered_roidb。在train文件中,須要關注的是SolverWrapper類。詳細見train.py,在這個類裏面,引入了caffe SGDSlover,最後一句self.solver.net.layers[0].set_roidb(roidb)將roidb設置進layer(0)(在這裏就是ROILayer)調用ayer.py中的set_roidb方法,爲layer(0)設置roidb,同時打亂順序。最後train_model。在這裏,就須要去實例化每一個層,在這個階段,首先就會實現ROIlayer,詳細參考layer中的setup,在訓練時roilayer的forward函數,在第一個層,只須要進行數據拷貝,在不一樣的階段根據prototxt文件定義的網絡結構拷貝數據,blobs = self._get_next_minibatch()這個函數讀取圖片數據(調用get_minibatch函數,這個函數在minibatch中,主要做用是爲faster rcnn作實際的數據準備,在讀取數據的時候,分出了boxes,gt_boxes,im_info(寬高縮放)等)。 
第一個層,對於stage1_rpn_train.pt文件中,該layer只有3個top blob:’data’、’im_info’、’gt_boxes’。 
對於stage1_fast_rcnn_train.pt文件中,該layer有6個top blob:top: ‘data’、’rois’、’labels’、’bbox_targets’、’bbox_inside_weights’、’bbox_outside_weights’,這些數據準備都在minibatch中。至此後數據便在caffe中流動了,直到訓練結束。 
畫出網絡的結構 這裏只截取了一部分: 
這裏寫圖片描述
 值得注意的是在rpn-data層使用的是AnchorTargetLayer,該層使用python實現的,日後再介紹。app

6.保存最後獲得的權重參數

rpn_stage1_out = mp_queue.get()

至此,第一階段完成,在後面的任務開始時,若是有須要,會在這個輸出的地址找這一階段獲得的權重文件。ide


第三步,Stage 1 RPN, generate proposals

這一步就是調用上一步訓練獲得的模型M1來生成proposal P1,在這一步只產生proposal,參數:

mp_kwargs = dict(
        queue=mp_queue,
        imdb_name=args.imdb_name,
        rpn_model_path=str(rpn_stage1_out['model_path']), cfg=cfg, rpn_test_prototxt=rpn_test_prototxt) p = mp.Process(target=rpn_generate, kwargs=mp_kwargs) p.start() rpn_stage1_out['proposal_path'] = mp_queue.get()['proposal_path'] p.join()

1.關注rpn_generate函數

前面和上面講到的train_rpn基本相同,從rpn_proposals = imdb_proposals(rpn_net, imdb)開始,imdb_proposals函數在rpn.generate.py文件中,rpn_proposals是一個列表的列表,每一個子列表。對於imdb_proposals,使用im = cv2.imread(imdb.image_path_at(i))讀入圖片數據,調用 im_proposals生成單張圖片的rpn proposals,以及得分。這裏,im_proposals函數會調用網絡的forward,從而獲得想要的boxes和scores,這裏須要好好理解blobs_out = net.forward(data,im_info)中net forward和layer forward間的調用關係。 
這裏寫圖片描述 
在這裏,也會有proposal,一樣會使用python實現的ProposalLayer,這個函數也在rpn文件夾內,後面再補充。

boxes = blobs_out['rois'][:, 1:].copy() / scale scores = blobs_out['scores'].copy() return boxes, scores

至此,獲得imdb proposal

2.保存獲得的proposal文件

queue.put({'proposal_path': rpn_proposals_path}) rpn_stage1_out['proposal_path'] = mp_queue.get()['proposal_path']

至此,Stage 1 RPN, generate proposals結束


第四步,Stage 1 Fast R-CNN using RPN proposals, init from ImageNet model

參數:

cfg.TRAIN.SNAPSHOT_INFIX = 'stage1' mp_kwargs = dict( queue=mp_queue, imdb_name=args.imdb_name, init_model=args.pretrained_model, solver=solvers[1], max_iters=max_iters[1], cfg=cfg, rpn_file=rpn_stage1_out['proposal_path']) p = mp.Process(target=train_fast_rcnn, kwargs=mp_kwargs) p.start() fast_rcnn_stage1_out = mp_queue.get() p.join()

這一步,用上一步生成的proposal,以及imagenet模型M0來訓練fast-rcnn模型M2。 
關注train_fast_rcnn 
一樣地,會設置參數,這裏注意cfg.TRAIN.PROPOSAL_METHOD = ‘rpn’ 不一樣於前面,後面調用的將是rpn_roidb。cfg.TRAIN.IMS_PER_BATCH = 2,每一個mini-batch包含兩張圖片,以及它們proposal的roi區域。且在這一步是有rpn_file的(後面和rpn_roidb函數使用有關)。其餘的和前面差很少。提一下,這裏在train_net的時候,會調用add_bbox_regression_targets位於roidb中,主要是添加bbox迴歸目標,即添加roidb的‘bbox_targets’屬性,同時根據cfg中的參數設定,求取bbox_targets的mean和std,由於須要訓練class-specific regressors在這裏就會涉及到bbox_overlaps函數,放在util.bbox中。 
要注意的是在這一步get_roidb時,如前所說,使用的是rpn_roidb,會調用imdb. create_roidb_from_box_list該方法功能是從box_list中讀取每張圖的boxes,而這個box_list就是從上一步保存的proposal文件中讀取出來的,而後作必定的處理,詳細見代碼,重點是在最後會返回roidb,rpn_roidb中的gt_overlaps是rpn_file中的box與gt_roidb中box的gt_overlaps等計算IoU等處理後獲得的,而不像gt_roidb()方法生成的gt_roidb中的gt_overlaps所有爲1.0。同時使用了imdb.merge_roidb,類imdb的靜態方法【這裏不太懂,須要再學習下】,把rpn_roidb和gt_roidb歸併爲一個roidb,在這裏,須要具體去了解合併的基本原理。 
這裏寫圖片描述


第五步,Stage 2 RPN, init from stage 1 Fast R-CNN model

參數:

cfg.TRAIN.SNAPSHOT_INFIX = 'stage2' mp_kwargs = dict( queue=mp_queue, imdb_name=args.imdb_name, init_model=str(fast_rcnn_stage1_out['model_path']), solver=solvers[2], max_iters=max_iters[2], cfg=cfg) p = mp.Process(target=train_rpn, kwargs=mp_kwargs) rpn_stage2_out = mp_queue.get()

這部分就是利用模型M2練rpn網絡,這一次與stage1的rpn網絡不通,這一次conv層的參數都是不動的,只作前向計算,訓練獲得模型M3,這屬於微調了rpn網絡。 
這裏寫圖片描述


第六步,Stage 2 RPN, generate proposals

參數:

mp_kwargs = dict(
        queue=mp_queue,
        imdb_name=args.imdb_name,
        rpn_model_path=str(rpn_stage2_out['model_path']), cfg=cfg, rpn_test_prototxt=rpn_test_prototxt) p = mp.Process(target=rpn_generate, kwargs=mp_kwargs) p.start() rpn_stage2_out['proposal_path'] = mp_queue.get()['proposal_path'] p.join()

這一步,基於上一步獲得的M3模型,產生proposal P2,網絡結構和前面產生proposal P1的同樣。 
這裏寫圖片描述


第七步,Stage 2 Fast R-CNN, init from stage 2 RPN R-CNN model

參數:

cfg.TRAIN.SNAPSHOT_INFIX = 'stage2' mp_kwargs = dict( queue=mp_queue, imdb_name=args.imdb_name, init_model=str(rpn_stage2_out['model_path']), solver=solvers[3], max_iters=max_iters[3], cfg=cfg, rpn_file=rpn_stage2_out['proposal_path']) p = mp.Process(target=train_fast_rcnn, kwargs=mp_kwargs) p.start() fast_rcnn_stage2_out = mp_queue.get() p.join()

這一步基於模型M3和P2訓練fast rcnn獲得最終模型M4,這一步,conv層和rpn都是參數固定,只是訓練了rcnn層(也就是全鏈接層),與stage1不一樣,stage1只是固定了rpn層,其餘層仍是有訓練。模型結構與stage1相同: 
這裏寫圖片描述


第八步,輸出最後模型

final_path = os.path.join(
        os.path.dirname(fast_rcnn_stage2_out['model_path']), args.net_name + '_faster_rcnn_final.caffemodel') print 'cp {} -> {}'.format( fast_rcnn_stage2_out['model_path'], final_path) shutil.copy(fast_rcnn_stage2_out['model_path'], final_path) print 'Final model: {}'.format(final_path)

 

只是對上一步模型輸出的一個拷貝。 
至此,整個faster-rcnn的訓練過程就結束了。


AnchorTargetLayer和ProposalLayer

前面說過還有這兩個層沒有說明,一個是anchortarget layer一個是proposal layer,下面逐一簡要分析。

class AnchorTargetLayer(caffe.Layer)

首先是讀取參數,在prototxt,實際上只讀取了param_str: 「‘feat_stride’: 16」,這是個很重要的參數,目前個人理解是滑塊滑動的大小,對於識別物體的大小頗有用,好比小物體的識別,須要把這個參數減少等。 
首先 setup部分,

anchor_scales = layer_params.get('scales', (8, 16, 32)) self._anchors = generate_anchors(scales=np.array(anchor_scales))

調用generate_anchors方法生成最初始的9個anchor該函數位於generate_anchors.py 主要功能是生成多尺度,多寬高比的anchors,8,16,32其實就是scales:[2^3 2^4 2^5],base_size爲16,具體是怎麼實現的能夠查閱源代碼。_ratio_enum()部分生成三種寬高比 1:2,1:1,2:1的anchor以下圖所示:(如下參考 另一篇博客) 
這裏寫圖片描述 
_scale_enum()部分,生成三種尺寸的anchor,以_ratio_enum()部分生成的anchor[0 0 15 15]爲例,擴展了三種尺度 128*128,256*256,512*512,以下圖所示: 
這裏寫圖片描述 
另一個函數就是forward()。 
在faster rcnn中會根據不一樣圖的輸入,獲得不一樣的feature map,height, width = bottom[0].data.shape[-2:]首先獲得conv5的高寬,以及gt box gt_boxes = bottom[1].data,圖片信息im_info = bottom[2].data[0, :],而後計算偏移量,shift_x = np.arange(0, width) * self._feat_stride,在這裏,你會發現,例如你獲得的fm是H=61,W=36,而後你乘以16,獲得的圖形大概就是1000*600,其實這個16大概就是網絡的縮放比例。接下來就是生成anchor,以及對anchor作必定的篩選,詳見代碼。

另一個須要理解的就是proposal layer,這個只是在測試的時候用,許多東西和AnchorTargetLayer相似,不詳細介紹,能夠查看代碼。主要看看forward函數,函數算法介紹在註釋部分寫的很詳細:

# Algorithm: # for each (H, W) location i # generate A anchor boxes centered on cell i # apply predicted bbox deltas at cell i to each of the A anchors # clip predicted boxes to image # remove predicted boxes with either height or width < threshold # sort all (proposal, score) pairs by score from highest to lowest # take top pre_nms_topN proposals before NMS # apply NMS with threshold 0.7 to remaining proposals # take after_nms_topN proposals after NMS # return the top proposals (-> RoIs top, scores top)

在這個函數中會引用NMS方法。


代碼文件夾說明

tools

在tools文件夾中,是咱們直接調用的最外層的封裝文件。其中主要包含的文件爲:

  • _init_paths.py :用來初始化路徑的,也就是以後的路徑會join(path,*)
  • compress_net.py:用來壓縮參數的,使用了SVD來進行壓縮,這裏能夠發現,做者對於fc6層和fc7層進行了壓縮,也就是兩個全鏈接層。
  • demo.py :一般,咱們會直接調用這個函數,若是要測試本身的模型和數據,這裏須要修改。這裏調用了fast_rcnn中的test、config、nums_wrapper函數。vis_detections用來作檢測,parse_args用來進行參數設置,以及damo和主函數。
  • eval_recall.py:評估函數
  • reval.py:re-evaluate,這裏調用了fast_rcnn以及dataset中的函數。其中,from_mats函數和from_dets函數分別loadmat文件和pkl文件。
  • rpn_genetate.py:這個函數調用了rpn中的genetate函數,以後咱們會對rpn層作具體的介紹。這裏,主要是一個封裝調用的過程,咱們在這裏調用配置的參數、設置rpn的test參數,以及輸入輸出等操做。
  • test_net.py:測試fast rcnn網絡。主要就是一些參數配置。
  • train_faster_rcnn_alt_opt.py:訓練faster rcnn網絡使用交替的訓練,這裏就是根據faster rcnn文章中的具體實現。能夠在主函數中看到,其包括的步驟爲: 
    • RPN 1,使用imagenet model進行初始化參數,生成proposal,這裏存儲在mp_kwargs
    • fast rcnn 1,使用 imagenet model 進行初始化參數,使用剛剛生成的proposal進行fast rcnn的訓練
    • RPN 2使用 fast rcnn 中的參數進行初始化(這裏要注意哦),並生成proposal
    • fast rcnn 2,使用RPN 2 中的 model進行初始化參數
    • 值得注意的是:在咱們訓練時,咱們能夠在get_solvers中的max_iters中設置迭代次數,在不肯定網絡是否能夠調通時,減小迭代次數能夠減小測試時間。
    • 咱們在訓練faster rcnn網絡時,就是調用這個文件訓練的
  • train_net.py:使用fast rcnn,訓練本身數據集的網絡模型
  • train_svms.py:使用最原始的RCNN網絡訓練post-hoc SVMs

RPN

這裏咱們主要看lib/rpn文件夾下的代碼。這裏主要介紹了rpn的模型,其中,包含的主要文件以下:

  • generate_anchors.py: 生成多尺度和多比例的錨點。這裏由generate_anthors函數主要完成,能夠看到,使用了 3 個尺度( 128, 256, and 512)以及 3 個比例(1:1,1:2,2:1)。一個錨點由w, h, x_ctr, y_ctr固定,也就是寬、高、x center和y center固定。
  • proposal_layer.py:這個函數是用來將RPN的輸出轉變爲object proposals的。做者新增了ProposalLayer類,這個類中,從新了set_up和forward函數,其中forward實現了:生成錨點box、對於每一個錨點提供box的參數細節、將預測框切成圖像、刪除寬、高小於閾值的框、將全部的(proposal, score) 對排序、獲取 pre_nms_topN proposals、獲取NMS 、獲取 after_nms_topN proposals。(注:NMS,nonmaximum suppression,非極大值抑制)
  • anchor_target_layer.py:生成每一個錨點的訓練目標和標籤,將其分類爲1 (object), 0 (not object) , -1 (ignore).當label>0,也就是有object時,將會進行box的迴歸。其中,forward函數功能:在每個cell中,生成9個錨點,提供這9個錨點的細節信息,過濾掉超過圖像的錨點,測量同GT的overlap。
  • proposal_target_layer.py:對於每個object proposal 生成訓練的目標和標籤,分類標籤從0-k,對於標籤>0的box進行迴歸。(注意,同anchor_target_layer.py不一樣,二者一個是生成anchor,一個是生成proposal)
  • generate.py:使用一個rpn生成object proposals。

做者就是經過以上這些文件生成rpn的。

nms

lib/nms文件夾下是非極大值抑制,這部分你們應該已經很是熟悉了,其Python版本的核心函數爲py_cpu_nms.py,具體實現以及註釋以下:

def py_cpu_nms(dets, thresh): """Pure Python NMS baseline.""" #x一、y一、x二、y二、以及score賦值 x1 = dets[:, 0] y1 = dets[:, 1] x2 = dets[:, 2] y2 = dets[:, 3] scores = dets[:, 4] #每個op的面積 areas = (x2 - x1 + 1) * (y2 - y1 + 1) #order是按照score排序的 order = scores.argsort()[::-1] keep = [] while order.size > 0: i = order[0] keep.append(i) xx1 = np.maximum(x1[i], x1[order[1:]]) yy1 = np.maximum(y1[i], y1[order[1:]]) xx2 = np.minimum(x2[i], x2[order[1:]]) yy2 = np.minimum(y2[i], y2[order[1:]]) #計算相交的面積 w = np.maximum(0.0, xx2 - xx1 + 1) h = np.maximum(0.0, yy2 - yy1 + 1) inter = w * h #計算:重疊面積/(面積1+面積2-重疊面積) ovr = inter / (areas[i] + areas[order[1:]] - inter) inds = np.where(ovr <= thresh)[0] order = order[inds + 1]

Faster R-CNN統一的網絡結構以下圖所示,能夠簡單看做RPN網絡+Fast R-CNN網絡。

fast rcnn結構

原理步驟以下:

  1. 首先向CNN網絡【ZF或VGG-16】輸入任意大小圖片;

  2. 通過CNN網絡前向傳播至最後共享的卷積層,一方面獲得供RPN網絡輸入的特徵圖,另外一方面繼續前向傳播至特有卷積層,產生更高維特徵圖;

  3. 供RPN網絡輸入的特徵圖通過RPN網絡獲得區域建議和區域得分,並對區域得分採用非極大值抑制【閾值爲0.7】,輸出其Top-N【文中爲300】得分的區域建議給RoI池化層;

  4. 第2步獲得的高維特徵圖和第3步輸出的區域建議同時輸入RoI池化層,提取對應區域建議的特徵;

  5. 第4步獲得的區域建議特徵經過全鏈接層後,輸出該區域的分類得分以及迴歸後的bounding-box。

單個RPN網絡結構以下:

fast rcnn結構

注意: 上圖中卷積層/全鏈接層表示卷積層或者全鏈接層,做者在論文中表示這兩層其實是全鏈接層,可是網絡在全部滑窗位置共享全鏈接層,能夠很天然地用n×n卷積核【論文中設計爲3×3】跟隨兩個並行的1×1卷積覈實現

RPN的做用:RPN在CNN卷積層後增長滑動窗口操做以及兩個卷積層完成區域建議功能,第一個卷積層將特徵圖每一個滑窗位置編碼成一個特徵向量,第二個卷積層對應每一個滑窗位置輸出k個區域得分和k個迴歸後的區域建議,並對得分區域進行非極大值抑制後輸出得分Top-N【文中爲300】區域,告訴檢測網絡應該注意哪些區域,本質上實現了Selective Search、EdgeBoxes等方法的功能。

  1. 首先套用ImageNet上經常使用的圖像分類網絡,本文中試驗了兩種網絡:ZF或VGG-16,利用這兩種網絡的部分卷積層產生原始圖像的特徵圖;

  2. 對於1中特徵圖,用n×n【論文中設計爲3×3,n=3看起來很小,可是要考慮到這是很是高層的feature map,其size自己也沒有多大,所以9個矩形中,每一個矩形窗框都是能夠感知到很大範圍的】的滑動窗口在特徵圖上滑動掃描【代替了從原始圖滑窗獲取特徵】,每一個滑窗位置經過卷積層1映射到一個低維的特徵向量【ZF網絡:256維;VGG-16網絡:512維,低維是相對於特徵圖大小W×H,typically~60×40=2400】後採用ReLU,併爲每一個滑窗位置考慮k種【論文中k=9】可能的參考窗口【論文中稱爲anchors,見下解釋】,這就意味着每一個滑窗位置會同時預測最多9個區域建議【超出邊界的不考慮】,對於一個W×H的特徵圖,就會產生W×H×k個區域建議;

  3. 步驟2中的低維特徵向量輸入兩個並行鏈接的卷積層2:reg窗口迴歸層【位置精修】和cls窗口分類層,分別用於迴歸區域建議產生bounding-box【超出圖像邊界的裁剪到圖像邊緣位置】和對區域建議是否爲前景或背景打分,這裏因爲每一個滑窗位置產生k個區域建議,因此reg層有4k個輸出來編碼【平移縮放參數】k個區域建議的座標,cls層有2k個得分估計k個區域建議爲前景或者背景的機率。

Anchors是一組大小固定的參考窗口:三種尺度{ 1282256251221282,2562,5122 }×三種長寬比{1:1,1:2,2:1},以下圖所示,表示RPN網絡中對特徵圖滑窗時每一個滑窗位置所對應的原圖區域中9種可能的大小,至關於模板,對任意圖像任意滑窗位置都是這9中模板。繼而根據圖像大小計算滑窗中心點對應原圖區域的中心點,經過中心點和size就能夠獲得滑窗位置和原圖位置的映射關係,由此原圖位置並根據與Ground Truth重複率貼上正負標籤,讓RPN學習該Anchors是否有物體便可。對於每一個滑窗位置,產生k=9個anchor對於一個大小爲W*H的卷積feature map,總共會產生WHk個anchor。

fast rcnn結構

平移不變性

Anchors這種方法具備平移不變性,就是說在圖像中平移了物體,窗口建議也會跟着平移。同時這種方式也減小了整個模型的size,輸出層 512×(4+2)×9=2.8×104512×(4+2)×9=2.8×104 個參數【512是前一層特徵維度,(4+2)×9是9個Anchors的前景背景得分和平移縮放參數】,而MultiBox有 1536×4+1×800=6.1×1061536×(4+1)×800=6.1×106個 參數,而較小的參數能夠在小數據集上減小過擬合風險。

固然,在RPN網絡中咱們只須要找到大體的地方,不管是位置仍是尺寸,後面的工做均可以完成,這樣的話採用小網絡進行簡單的學習【估計和猜差很少,反正有50%機率】,還不如用深度網絡【還能夠實現卷積共享】,固定尺度變化,固定長寬比變化,固定採樣方式來大體判斷是不是物體以及所對應的位置並下降任務複雜度。

有兩種方法解決多尺度多長寬比問題:

  1. 圖像金字塔:對伸縮到不一樣size的輸入圖像進行特徵提取,雖然有效可是費時.

  2. feature map上使用多尺度(和/或長寬比)的滑窗:例如,DPM分別使用不一樣大小的filter來訓練不一樣長寬比的模型。若這種方法用來解決多尺度問題,能夠認爲是「filter金字塔(pyramid of filters)」

4.5.1 RPN網絡訓練過程

RPN網絡被ImageNet網絡【ZF或VGG-16】進行了有監督預訓練,利用其訓練好的網絡參數初始化; 用標準差0.01均值爲0的高斯分佈對新增的層隨機初始化。

4.5.2 Fast R-CNN網絡預訓練

一樣使用mageNet網絡【ZF或VGG-16】進行了有監督預訓練,利用其訓練好的網絡參數初始化。

4.5.3 RPN網絡微調訓練

PASCAL VOC 數據集中既有物體類別標籤,也有物體位置標籤; 正樣本僅表示前景,負樣本僅表示背景; 迴歸操做僅針對正樣本進行; 訓練時棄用全部超出圖像邊界的anchors,不然在訓練過程當中會產生較大難以處理的修正偏差項,致使訓練過程沒法收斂; 對去掉超出邊界後的anchors集採用非極大值抑制,最終一張圖有2000個anchors用於訓練【詳細見下】; 對於ZF網絡微調全部層,對VGG-16網絡僅微調conv3_1及conv3_1以上的層,以便節省內存。

SGD mini-batch採樣方式: 同Fast R-CNN網絡,採起 image-centric 方式採樣,即採用層次採樣,先對圖像取樣,再對anchors取樣,同一圖像的anchors共享計算和內存。每一個mini-batch包含從一張圖中隨機提取的256個anchors,正負樣本比例爲1:1【固然能夠對一張圖全部anchors進行優化,但因爲負樣本過多最終模型會對正樣本預測準確率很低】來計算一個mini-batch的損失函數,若是一張圖中不夠128個正樣本,拿負樣本補湊齊。

訓練超參數選擇: 在PASCAL VOC數據集上前60k次迭代學習率爲0.001,後20k次迭代學習率爲0.0001;動量設置爲0.9,權重衰減設置爲0.0005。

多任務目標函數【分類損失+迴歸損失】具體以下:

L({pi},{ti})=1NclsiLcls(pi,pi)+λ1NregipiLreg(ti,ti)L({pi},{ti})=1Ncls∑iLcls(pi,pi∗)+λ1Nreg∑ipi∗Lreg(ti,ti∗)
  • i 爲一個anchor在一個mini-batch中的下標

  • pipi 是anchor i爲一個object的預測可能性

  • pipi⋆ 爲ground-truth標籤。若是這個anchor是positive的,則ground-truth標籤 pipi⋆ 爲1,不然爲0。

  • titi 表示表示正樣本anchor到預測區域bounding box的4個參數化座標,【以anchor爲基準的變換

  • titi⋆ 是這個positive anchor對應的ground-truth box。【以anchor爲基準的變換

  • LclsLcls 分類的損失(classification loss),是一個二值分類器(是object或者不是)的softmax loss。其公式爲 Lcls(pi,pi)=log[pipi+(1pi)(1pi)]Lcls(pi,pi⋆)=−log[pi∗pi⋆+(1−pi⋆)(1−pi)]

  • LregLreg 迴歸損失(regression loss),Lreg(ti,ti)=R(titi)Lreg(ti,ti⋆)=R(ti−ti⋆) 【兩種變換之差越小越好】,其中R是Fast R-CNN中定義的robust ross function (smooth L1)。piLregpi⋆Lreg表示迴歸損失只有在positive anchor( pi=1pi⋆=1 )的時候纔會被激活。cls與reg層的輸出分別包含{pipi}和{ titi }。R函數的定義爲: smoothL1(x)=0.5x2ifx∣<1otherwisex0.5smoothL1(x)=0.5x2if∣x∣<1otherwise∣x∣−0.5

  • λ參數用來權衡分類損失 LclsLcls 和迴歸損失 LregLreg ,默認值λ=10【文中實驗代表 λ從1變化到100對mAP影響不超過1%】;

  • NclsNcls 和 NregNreg 分別用來標準化分類損失項 LclsLcls 和迴歸損失項 LregLreg,默認用mini-batch size=256設置 NclsNcls,用anchor位置數目~2400初始化 NregNreg,文中也說明標準化操做並非必須的,能夠簡化省略。

4.5.4 RPN網絡、Fast R-CNN網絡聯合訓練

訓練網絡結構示意圖以下所示:

fast rcnn結構

如上圖所示,RPN網絡、Fast R-CNN網絡聯合訓練是爲了讓兩個網絡共享卷積層,下降計算量

文中經過4步訓練算法,交替優化學習至共享特徵:

  1. 進行上面RPN網絡預訓練,和以區域建議爲目的的RPN網絡end-to-end微調訓練。

  2. 進行上面Fast R-CNN網絡預訓練,用第①步中獲得的區域建議進行以檢測爲目的的Fast R-CNN網絡end-to-end微調訓練【此時無共享卷積層】。

  3. 使用第2步中微調後的Fast R-CNN網絡從新初始化RPN網絡,固定共享卷積層【即設置學習率爲0,不更新】,僅微調RPN網絡獨有的層【此時共享卷積層】。

  4. 固定第3步中共享卷積層,利用第③步中獲得的區域建議,僅微調Fast R-CNN獨有的層,至此造成統一網絡如上圖所示。

**RPN網絡中bounding-box迴歸怎麼理解?同Fast R-CNN中的bounding-box迴歸相比有什麼區別? **

對於bounding-box迴歸,採用如下公式:

  • t
tx=(xxa)waty=(yya)hatw=logwwath=loghhatx=(x−xa)waty=(y−ya)hatw=logwwath=loghha
  • tt∗
tx=(xxa)waty=(yya)hatw=logwwath=loghhatx∗=(x∗−xa)waty∗=(y∗−ya)hatw∗=logw∗wath∗=logh∗ha

其中,x,y,w,h表示窗口中心座標和窗口的寬度和高度,變量x,xaxa 和 xx∗ 分別表示預測窗口、anchor窗口和Ground Truth的座標【y,w,h同理】,所以這能夠被認爲是一個從anchor窗口到附近Ground Truth的bounding-box 迴歸;

RPN網絡中bounding-box迴歸的實質其實就是計算出預測窗口。這裏以anchor窗口爲基準,計算Ground Truth對其的平移縮放變化參數,以及預測窗口【可能第一次迭代就是anchor】對其的平移縮放參數,由於是以anchor窗口爲基準,因此只要使這兩組參數越接近,以此構建目標函數求最小值,那預測窗口就越接近Ground Truth,達到迴歸的目的;

文中提到, Fast R-CNN中基於RoI的bounding-box迴歸所輸入的特徵是在特徵圖上對任意size的RoIs進行Pool操做提取的,全部size RoI共享迴歸參數,而在Faster R-CNN中,用來bounding-box迴歸所輸入的特徵是在特徵圖上相同的空間size【3×3】上提取的,爲了解決不一樣尺度變化的問題,同時訓練和學習了k個不一樣的迴歸器,依次對應爲上述9種anchors,這k個迴歸量並不分享權重。所以儘管特徵提取上空間是固定的【3×3】,但因爲anchors的設計,仍可以預測不一樣size的窗口。

文中提到了三種共享特徵網絡的訓練方式?

  1. 交替訓練,訓練RPN,獲得的區域建議來訓練Fast R-CNN網絡進行微調;此時網絡用來初始化RPN網絡,迭代此過程【文中全部實驗採用】;

  2. 近似聯合訓練: 如上圖所示,合併兩個網絡進行訓練,前向計算產生的區域建議被固定以訓練Fast R-CNN;反向計算到共享卷積層時RPN網絡損失和Fast R-CNN網絡損失疊加進行優化,但此時把區域建議【Fast R-CNN輸入,須要計算梯度並更新】當成固定值看待,忽視了Fast R-CNN一個輸入:區域建議的導數,則沒法更新訓練,因此稱之爲近似聯合訓練。實驗發現,這種方法獲得和交替訓練相近的結果,還能減小20%~25%的訓練時間,公開的python代碼中使用這種方法;

  3. 聯合訓練 須要RoI池化層對區域建議可微,須要RoI變形層實現,具體請參考這片paper:Instance-aware Semantic Segmentation via Multi-task Network Cascades。

圖像Scale細節問題?

文中提到訓練和檢測RPN、Fast R-CNN都使用單一尺度,統一縮放圖像短邊至600像素; 在縮放的圖像上,對於ZF網絡和VGG-16網絡的最後卷積層總共的步長是16像素,所以在縮放前典型的PASCAL圖像上大約是10像素【~500×375;600/16=375/10】。

Faster R-CNN中三種尺度怎麼解釋:

  • 原始尺度:原始輸入的大小,不受任何限制,不影響性能;

  • 歸一化尺度:輸入特徵提取網絡的大小,在測試時設置,源碼中opts.test_scale=600。anchor在這個尺度上設定,這個參數和anchor的相對大小決定了想要檢測的目標範圍;

  • 網絡輸入尺度:輸入特徵檢測網絡的大小,在訓練時設置,源碼中爲224×224。

理清文中anchors的數目

文中提到對於1000×600的一張圖像,大約有20000(~60×40×9)個anchors,忽略超出邊界的anchors剩下6000個anchors,利用非極大值抑制去掉重疊區域,剩2000個區域建議用於訓練; 測試時在2000個區域建議中選擇Top-N【文中爲300】個區域建議用於Fast R-CNN檢測。

RCNN 介紹

Fast RCNN介紹

Faster RCNN論文筆記

Fast RCNN簡要筆記

SPP網絡

http://shartoo.github.io/RCNN-series/
相關文章
相關標籤/搜索