Faster-RCNN代碼+理論——1

昨天剛參加完一個ibm的醫療影像大賽——我負責的模型是做多目標識別並輸出位置的模型。由於之前沒有什麼經驗,採用了在RGB圖像上表現不錯的Faster-RCNN,但是比賽過程表明:效果不是很好。所以這裏把我對Faster-RCNN的原理及代碼(https://github.com/yhenon/keras-frcnn)結合起來,分析一下,以釐清Faster-RCNN究竟是什麼,它是怎麼進行操作的。

① 簡單介紹

Faster RCNN可以看做“區域生成網絡(RPN)+Fast RCNN“的系統,用RPN代替Fast RCNN中的Selective Search來進行候選框的選擇和修正。
Faster RCNN是由Ross Girshick於2015年提出的,其團隊的何凱明大神(Resnet的發明者)將這個新的方法用於實時的目標檢測(Real-Time Object Detection),簡單網絡目標檢測速度達到17fps,在PASCAL VOC上準確率爲59.9%;複雜網絡達到5fps,準確率78.8%。

② RPN+Fast RCNN的思路

從RCNN到Fast RCNN,再到這裏的Faster RCNN,目標檢測的四個基本步驟(候選區域生成特徵提取目標分類位置迴歸修正)終於被統一到一個深度網絡框架之內。所有計算沒有重複,完全在GPU中完成,從而顯著的提高了運行速度。
這裏寫圖片描述

此圖來自shenxiaolu1984的博客,在此感謝一下。

Faster-RCNN的做法是:
  • 將RPN放在最後一個卷積層的後面
  • RPN直接訓練得到候選區域

這裏寫圖片描述

本系列文章:

將結合代碼(Python-keras)詳細的介紹Faster-RCNN及其相關內容,並補充一些有用的技巧。

③ Faster-RCN的結構

在這裏,基本的思路是:在經過比較常用的用於ImageNet分類(如VGG,Resnet等)上提取好的特徵圖上,對所有可能的候選框(Bounding box)進行判別。後續對Bbox還有迴歸來修正座標的步驟,所以候選框實際上是比較稀疏的。

其步驟如下:
這裏寫圖片描述

④ 特徵提取(提出feature map)

原始特徵提取(上圖第二步)包含若干層conv+maxpooling,直接套用ImageNet上常見的分類網絡即可。這裏以VGG爲例,其結構形式爲:

# coding: UTF-8
from keras.layers import Flatten, Dense, Input, Conv2D, MaxPooling2D, Dropout
from keras.layers import GlobalAveragePooling2D, GlobalMaxPooling2D, TimeDistributed

# 注意: theano和tensorflow作爲後端的時候,接收圖片格式不一致。
if K.image_dim_ordering() == 'th':
    # theano 接收形式爲channel, width, height
    input_shape_img = (3, None, None)
else:
    # tensorflow 接收形式爲width, height, channel
    input_shape_img = (None, None, 3)
img_input = Input(shape=input_shape_img)

def nn_base(img_input):
    # Block 1
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(img_input)
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)
    # 縮水1/2 1024x1024 -> 512x512
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

    # Block 2
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)
    # 縮水1/2 512x512 -> 256x256
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

    # Block 3
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x)
    # 縮水1/2 256x256 -> 128x128
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)

    # Block 4
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x)
    # 縮水1/2 128x128 -> 64x64
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)

    # Block 5
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x)

    # 顯然,最後返回的x是64x64x512的feature map。
    return x

以這個nn_base函數的返回值x作爲後續的base_layers,緊接着,在base_layers後面添加一個conv層,並作爲rpn的迴歸和分類的輸入:

# 這裏num_anchors = 3x3 = 9,後面會提到
def rpn(base_layers, num_anchors):

    x = Conv2D(512, (3, 3), padding='same', activation='relu', kernel_initializer='normal', name='rpn_conv1')(base_layers)

    # rpn分類和迴歸
    x_class = Conv2D(num_anchors, (1, 1), activation='sigmoid', kernel_initializer='uniform', name='rpn_out_class')(x)
    x_reg = Conv2D(num_anchors * 4, (1, 1), activation='linear', kernel_initializer='zero', name='rpn_out_regress')(x)

    return [x_class, x_reg, base_layers]

這裏,x_class是使用1*1的卷積核在rpn_conv1上進行卷積,生成了num_anchors數量的channel ,每個channel包含特徵圖(w*h)個sigmoid激活值,表明該anchor是否可用,與我們剛剛計算的y_rpn_cls對應。同樣地,x_regr與剛剛計算的y_rpn_regr對應。

⑤ RPN(Region Proposal Networks)的設計和訓練思路

如下圖所示,RPN是在CNN訓練得到的用於分類任務的feature map基礎上,進行
  • 在feature map上滑動窗口。
  • 建一個神經網絡用於物體分類(x_class)+框位置的迴歸(x_reg)。
  • 滑動窗口的位置提供了物體的大體位置信息。
  • 框的迴歸修正框的位置,使其與對應的bbox位置更相近。

這裏寫圖片描述

SPP的映射機制

上圖是RPN的網絡流程圖,即利用了SPP的映射機制,從上面的rpn_conv1上進行滑窗來替代從原圖滑窗。
不過,要如何訓練出一個網絡來替代selective search相類似的功能呢?
實際上思路很簡單,就是先通過SPP根據一一對應的點從rpn_conv1映射回原圖,根據設計不同的固定初始尺度訓練一個網絡,就是給它大小不同(但設計固定)的region圖,然後根據與ground truth的覆蓋率給它正負標籤,讓它學習裏面是否有object即可。
這就又變成介紹RCNN之前提出的traditional method,訓練出一個能檢測物體的網絡,然後對整張圖片進行滑窗判斷,不過這樣子的話由於無法判斷region的尺度和scale ratio,故需要多次放縮,這樣子測試,估計判斷一張圖片是否有物體就需要很久。(傳統hog+svm->dpm)

如何降低這一部分的複雜度?

要知道我們只需要找出大致的地方,無論是精確定位位置還是尺寸,後面的工作都可以完成。鑑於此,用層數低的網絡還不如用深層的網絡,通過① 固定anchor_box_scales變化②固定anchor_box_ratios變化③固定的採樣方式(反正後面的工作能進行調整,更何況它本身就可以對box的位置進行調整)這樣子來降低任務複雜度呢。

這裏有個很不錯的地方就是在前面可以共享卷積計算結果(第四部分的base_layers),這也算是用深度網絡的另一個原因吧。

文章叫這些proposal region爲anchor(錨點)的原因也正是這三個固定的變化。這個網絡的結果就是卷積層的每個點都有有關於k個achor boxes的輸出,包括是不是物體,調整box相應的位置。這相當於給了比較死的初始位置(三個固定),然後來大致判斷是否是物體以及所對應的位置.
這樣子的話RPN所要做的也就完成了,這個網絡也就完成了它應該完成的使命,剩下的交給其他部分完成。

⑥ 錨點位置(anchor)

特徵可以看做一個尺度64x64x512的特徵圖(feature map),對於該圖像的每一個位置,考慮9個可能的候選窗口(即前面的num_anchors):三種面積[128,256,512] × 三種比例[1:1,1:2,2:1] 。
這九種可能的窗口形式分別爲:

Window Scale Ratio
128 x 128 128 1:1
128 x 256 128 1:2
128 x 64 128 2:1
256 x 256 256 1:1
256 x 512 256 1:2
256 x 128 256 2:1
512 x 512 512 1:1
512 x 1024 512 1:2
512 x 256 512 2:1

這些候選窗口稱爲anchors。

注意:這裏採用一個Config類來表示Faster-RCNN對應的各種參數(包括上面說的面積和比例關係等)

from keras import backend as K
import math

class Config:

    def __init__(self):

        self.verbose = True

        self.network = 'vgg'

        # setting for data augmentation
        self.use_horizontal_flips = False
        self.use_vertical_flips = False
        self.rot_90 = False


        # 比賽的時候,調整anchor_box_scales和anchor_box_ratios
        # anchor box scales
        self.anchor_box_scales = [128, 256, 512]

        # anchor box ratios
        self.anchor_box_ratios = [[1, 1], [1./math.sqrt(2), 2./math.sqrt(2)], [2./math.sqrt(2), 1./math.sqrt(2)]]

        # size to resize the smallest side of the image
        self.im_size = 600

        # image channel-wise mean to subtract
        self.img_channel_mean = [103.939, 116.779, 123.68]
        self.img_scaling_factor = 1.0

        # number of ROIs at once
        self.num_rois = 4

        # stride at the RPN (this depends on the network configuration)
        self.rpn_stride = 16

        self.balanced_classes = False

        # scaling the stdev
        # 基於樣本估算標準偏差。標準偏差反映數值相對於平均值(mean) 的離散程度。
        self.std_scaling = 4.0
        self.classifier_regr_std = [8.0, 8.0, 4.0, 4.0]

        # overlaps for RPN
        self.rpn_min_overlap = 0.3
        self.rpn_max_overlap = 0.7

        # overlaps for classifier ROIs
        self.classifier_min_overlap = 0.3
        self.classifier_max_overlap = 0.8

        # placeholder for the class mapping, automatically generated by the parser
        self.class_mapping = None

        #location of pretrained weights for the base network 
        # weight files can be found at:
        # https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_th_dim_ordering_th_kernels_notop.h5
        # https://github.com/fchollet/deep-learning-models/releases/download/v0.2/resnet50_weights_tf_dim_ordering_tf_kernels_notop.h5

        self.model_path = 'model_frcnn.vgg.hdf5'

從下面的calc_rpn函數可以看出,有四層循環:1、2層爲取出框的尺寸大小。3、4層爲選定錨點的座標,並與實際經過縮放對應的bbox的座標(x1,y1表示矩形框左上角,x2, y2表示右下角)計算IoU以及進行判斷每個錨點對應的9種框是否能匹配到bbox,以及匹配的程度。

# coding: UTF-8
from __future__ import absolute_import
import numpy as np
import cv2
import random
import copy
# 這裏C代表一個參數類(上面的Config),C = Config()
def calc_rpn(C, img_data, width, height, resized_width, resized_height, img_length_calc_function):


    # 接下來讀取了幾個參數,downscale就是從圖片到特徵圖的縮放倍數(默認爲16.0) 這裏,
    # img_length_calc_function(也就是實際的vgg中的get_img_output_length中整除的值一樣。)
    # anchor_size和anchor_ratios是我們初步選區大小的參數,比如3個size和3個ratios,可以組合成9種不同形狀大小的選區。
    downscale = float(C.rpn_stride)
    anchor_sizes = C.anchor_box_scales
    anchor_ratios = C.anchor_box_ratios
    num_anchors = len(anchor_sizes) * len(anchor_ratios)    

    # calculate the output map size based on the network architecture
    # 接下來,
    # 通過img_length_calc_function 對VGG16 返回的是一個height和width都整除16的結果這個方法計算出了特徵圖的尺寸。
    # output_width = output_height = 600 // 16 = 37
    (output_width, output_height) = img_length_calc_function(resized_width, resized_height)


    # 下一步是幾個變量初始化可以先不看,後面用到的時候再看。

    # n_anchratios = 3
    n_anchratios = len(anchor_ratios)

    # initialise empty output objectives
    y_rpn_overlap = np.zeros((output_height, output_width, num_anchors))
    y_is_box_valid = np.zeros((output_height, output_width, num_anchors))
    y_rpn_regr = np.zeros((output_height, output_width, num_anchors * 4))

    num_bboxes = len(img_data['bboxes'])

    num_anchors_for_bbox = np.zeros(num_bboxes).astype(int)
    best_anchor_for_bbox = -1*np.ones((num_bboxes, 4)).astype(int)
    best_iou_for_bbox = np.zeros(num_bboxes).astype(np.float32)
    best_x_for_bbox = np.zeros((num_bboxes, 4)).astype(int)
    best_dx_for_bbox = np.zeros((num_bboxes, 4)).astype(np.float32)


    # 因爲我們的計算都是基於resize以後的圖像的,所以接下來把bbox中的x1,x2,y1,y2分別通過縮放匹配到resize以後的圖像。
    # 這裏記做gta,尺寸爲(num_of_bbox,4)。
    # get the GT box coordinates, and resize to account for image resizing
    gta = np.zeros((num_bboxes, 4))
    for bbox_num, bbox in enumerate(img_data['bboxes']):
        # get the GT box coordinates, and resize to account for image resizing
        gta[bbox_num, 0] = bbox['x1'] * (resized_width / float(width))
        gta[bbox_num, 1] = bbox['x2'] * (resized_width / float(width))
        gta[bbox_num, 2] = bbox['y1'] * (resized_height / float(height))
        gta[bbox_num, 3] = bbox['y2'] * (resized_height / float(height))

    # rpn ground truth
    # 這一段計算了anchor的長寬,然後比較重要的就是把特徵圖的每一個點作爲一個錨點,
    # 通過乘以downscale,映射到圖片的實際尺寸,再結合anchor的尺寸,忽略掉超出圖片範圍的。
    # 一個個大小、比例不一的矩形選框就躍然紙上了。
    # 第一層for 3層
    # 第二層for 3層
    for anchor_size_idx in range(len(anchor_sizes)):
        for anchor_ratio_idx in range(n_anchratios):
            # 框的尺寸選定
            anchor_x = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][0]
            anchor_y = anchor_sizes[anchor_size_idx] * anchor_ratios[anchor_ratio_idx][1]   
            # 對1024 --> 600 --> 37的形式,output_width = 37
            # 選定錨點座標: x_anc y_anc
            for ix in range(output_width):                  
                # x-coordinates of the current anchor box   
                x1_anc = downscale * (ix + 0.5) - anchor_x / 2
                x2_anc = downscale * (ix + 0.5) + anchor_x / 2  

                # ignore boxes that go across image boundaries                  
                if x1_anc < 0 or x2_anc > resized_width:
                    continue

                for jy in range(output_height):

                    # y-coordinates of the current anchor box
                    y1_anc = downscale * (jy + 0.5) - anchor_y / 2
                    y2_anc = downscale * (jy + 0.5) + anchor_y / 2

                    # ignore boxes that go across image boundaries
                    if y1_anc < 0 or y2_anc > resized_height:
                        continue


                    # 定義了兩個變量,bbox_type和best_iou_for_loc,後面會用到。計算了anchor與gta的交集 iou(),
                    # 然後就是如果交集大於best_iou_for_bbox[bbox_num]或者大於我們設定的閾值,就會去計算gta和anchor的中心點座標,
                    # bbox_type indicates whether an anchor should be a target 
                    bbox_type = 'neg'

                    # this is the best IOU for the (x,y) coord and the current anchor
                    # note that this is different from the best IOU for a GT bbox
                    best_iou_for_loc = 0.0

                    # 對選出的選擇框,判斷其和實際上圖片的所有Bbox中,有無滿足大於規定threshold的情況。
                    for bbox_num in range(num_bboxes):

                        # get IOU of the current GT box and the current anchor box
                        curr_iou = iou([gta[bbox_num, 0], gta[bbox_num, 2], gta[bbox_num, 1], gta[bbox_num, 3]], [x1_anc, y1_anc, x2_anc, y2_anc])
                        # calculate the regression targets if they will be needed
                        # 默認的最大rpn重疊部分(rpn_max_overlap)爲0.7,最小(rpn_min_overlap)爲0.3
                        if curr_iou > best_iou_for_bbox[bbox_num] or curr_iou > C.rpn_max_overlap:
                            cx = (gta[bbox_num, 0] + gta[bbox_num, 1]) / 2.0
                            cy = (gta[bbox_num, 2] + gta[bbox_num, 3]) / 2.0
                            cxa = (x1_anc + x2_anc)/2.0
                            cya = (y1_anc + y2_anc)/2.0

                            # 計算出x,y,w,h四個值的梯度值。
                            # 爲什麼要計算這個梯度呢?因爲RPN計算出來的區域不一定是很準確的,從只有9個尺寸的anchor也可以推測出來,
                            # 因此我們在預測時還會進行一次迴歸計算,而不是直接使用這個區域的座標。
                            tx = (cx - cxa) / (x2_anc - x1_anc)
                            ty = (cy - cya) / (y2_anc - y1_anc)
                            tw = np.log((gta[bbox_num, 1] - gta[bbox_num, 0]) / (x2_anc - x1_anc))
                            th = np.log((gta[bbox_num, 3] - gta[bbox_num, 2]) / (y2_anc - y1_anc))

                        # 前提是:當前的bbox不是背景 != 'bg'
                        if img_data['bboxes'][bbox_num]['class'] != 'bg':

                            # all GT boxes should be mapped to an anchor box, so we keep track of which anchor box was best
                            if curr_iou > best_iou_for_bbox[bbox_num]:
                                # jy 高度 ix 寬度
                                best_anchor_for_bbox[bbox_num] = [jy, ix, anchor_ratio_idx, anchor_size_idx]
                                best_iou_for_bbox[bbox_num] = curr_iou
                                best_x_for_bbox[bbox_num,:] = [x1_anc, x2_anc, y1_anc, y2_anc]
                                best_dx_for_bbox[bbox_num,:] = [tx, ty, tw, th]

                            # we set the anchor to positive if the IOU is >0.7 (it does not matter if there was another better box, it just indicates overlap)
                            if curr_iou > C.rpn_max_overlap:
                                bbox_type = 'pos'
                                # 因爲num_anchors_for_bbox 形式爲 [0, 0, 0, 0]
                                # 這步操作的結果爲 [1, 1, 1, 1]
                                num_anchors_for_bbox[bbox_num] += 1
                                # we update the regression layer target if this IOU is the best for the current (x,y) and anchor position
                                if curr_iou > best_iou_for_loc:
                                    # 不斷修正最佳iou對應的區域和梯度
                                    best_iou_for_loc = curr_iou
                                    best_grad = (tx, ty, tw, th)

                            # if the IOU is >0.3 and <0.7, it is ambiguous and no included in the objective
                            if C.rpn_min_overlap < curr_iou < C.rpn_max_overlap:
                                # gray zone between neg and pos
                                if bbox_type != 'pos':
                                    bbox_type = 'neutral'

                    # turn on or off outputs depending on IOUs
                    # 接下來根據bbox_type對本anchor進行打標,y_is_box_valid和y_rpn_overlap分別定義了這個anchor是否可用和是否包含對象。
                    if bbox_type == 'neg':
                        y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                        y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                    elif bbox_type == 'neutral':
                        y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                        y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 0
                    elif bbox_type == 'pos':
                        y_is_box_valid[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                        y_rpn_overlap[jy, ix, anchor_ratio_idx + n_anchratios * anchor_size_idx] = 1
                        # 默認是36個選擇
                        start = 4 * (anchor_ratio_idx + n_anchratios * anchor_size_idx)
                        y_rpn_regr[jy, ix, start:start+4] = best_grad


    # we ensure that every bbox has at least one positive RPN region
    # 這裏又出現了一個潛在問題: 可能會有bbox可能找不到心儀的anchor,那這些訓練數據就沒法利用了,
    # 因此我們用一個折中的辦法來保證每個bbox至少有一個anchor與之對應。
    # 下面是具體的方法,比較簡單,對於沒有對應anchor的bbox,在中性anchor裏挑最好的,當然前提是你不能跟我完全不相交,那就太過分了。。
    for idx in range(num_anchors_for_bbox.shape[0]):
        if num_anchors_for_bbox[idx] == 0:
            # no box with an IOU greater than zero ... 遇到這種情況只能pass了
            if best_anchor_for_bbox[idx, 0] == -1:
                continue
            y_is_box_valid[
                best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], best_anchor_for_bbox[idx,2] + n_anchratios *
                best_anchor_for_bbox[idx,3]] = 1
            y_rpn_overlap[
                best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], best_anchor_for_bbox[idx,2] + n_anchratios *
                best_anchor_for_bbox[idx,3]] = 1
            start = 4 * (best_anchor_for_bbox[idx,2] + n_anchratios * best_anchor_for_bbox[idx,3])
            y_rpn_regr[
                best_anchor_for_bbox[idx,0], best_anchor_for_bbox[idx,1], start:start+4] = best_dx_for_bbox[idx, :]

    # y_rpn_overlap 原來的形式np.zeros((output_height, output_width, num_anchors))
    # 現在變爲 (num_anchors, output_height, output_width)
    y_rpn_overlap = np.transpose(y_rpn_overlap, (2, 0, 1))
    # (新的一列,num_anchors, output_height, output_width)
    y_rpn_overlap = np.expand_dims(y_rpn_overlap, axis=0)

    y_is_box_valid = np.transpose(y_is_box_valid, (2, 0, 1))
    y_is_box_valid = np.expand_dims(y_is_box_valid, axis=0)

    y_rpn_regr = np.transpose(y_rpn_regr, (2, 0, 1))
    y_rpn_regr = np.expand_dims(y_rpn_regr, axis=0)

    # pos表示box neg表示背景
    pos_locs = np.where(np.logical_and(y_rpn_overlap[0, :, :, :] == 1, y_is_box_valid[0, :, :, :] == 1))
    neg_locs = np.where(np.logical_and(y_rpn_overlap[0, :, :, :] == 0, y_is_box_valid[0, :, :, :] == 1))

    num_pos = len(pos_locs[0])

    # one issue is that the RPN has many more negative than positive regions, so we turn off some of the negative
    # regions. We also limit it to 256 regions.
    # 因爲negtive的anchor肯定遠多於postive的,
    # 因此在這裏設定了regions數量的最大值爲256,並對pos和neg的樣本進行了均勻的取樣。
    num_regions = 256

    # 對感興趣的框超過128的時候...
    if len(pos_locs[0]) > num_regions/2:
        # val_locs爲一個list
        val_locs = random.sample(range(len(pos_locs[0])), len(pos_locs[0]) - num_regions/2)
        y_is_box_valid[0, pos_locs[0][val_locs], pos_locs[1][val_locs], pos_locs[2][val_locs]] = 0
        num_pos = num_regions/2
    # 使得neg(背景)和pos(錨框)數量一致
    if len(neg_locs[0]) + num_pos > num_regions:
        val_locs = random.sample(range(len(neg_locs[0])), len(neg_locs[0]) - num_pos)
        y_is_box_valid[0, neg_locs[0][val_locs], neg_locs[1][val_locs], neg_locs[2][val_locs]] = 0
    # axis = 1按行拼接
    # a = [[1 2 3]
    # [4 5 6]]
    # b = [[11 21 31]
    #  [ 7  8  9]]
    # np.concatenate((a,b), axis=1) =
    # [[ 1  2  3 11 21 31]
    #  [ 4  5  6  7  8  9]]

    y_rpn_cls = np.concatenate([y_is_box_valid, y_rpn_overlap], axis=1)
    y_rpn_regr = np.concatenate([np.repeat(y_rpn_overlap, 4, axis=1), y_rpn_regr], axis=1)
    # 最後,得到了兩個返回值y_rpn_cls,y_rpn_regr。分別用於確定anchor是否包含物體,和迴歸梯度。
    # 值得注意的是, y_rpn_cls和y_rpn_regr數量是比實際的輸入圖片對應的Bbox數量多挺多的。
    return np.copy(y_rpn_cls), np.copy(y_rpn_regr)

這裏簡單介紹一下IoU,可以理解將IoU簡單理解爲是一個定位精度評價公式。IoU定義了兩個bounding box的重疊度,如下圖所示:
這裏寫圖片描述

其中藍色部分爲A和B的交集:AB
那麼矩形框A、B的一個重合度IOU計算公式爲:

IOU=(AB)/(AB)

對應的函數爲:

# 重合度
def iou(a, b):
    # a and b should be (x1,y1,x2,y2)

    if a[0] >= a[2] or a[1] >= a[3] or b[0] >= b[2] or b[1] >= b[3]:
        return 0.0
    # 注意,intersection 和 union 需要自定義,這個比較簡單,此處略過
    area_i = intersection(a, b) # 交集
    area_u = union(a, b, area_i) # 並集

    return float(area_i) / float(area_u + 1e-6)

小結

在整個Faster RCNN算法中,有三種尺度。

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

  • 歸一化尺度:輸入特徵提取網絡的大小,在測試時設置,上面的Config類中size to resize the smallest side of the image,self.im_size = 600。anchor在這個尺度上設定。這個參數和anchor的相對大小決定了想要檢測的目標範圍。

  • 網絡輸入尺度:輸入特徵檢測網絡的大小,在訓練時設置。

參考資料

[1] RCNN學習筆記(5):faster rcnn
[2] Fast RCNN算法詳解
[3] 深度學習(十八)基於R-CNN的物體檢測

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章