Faster R-CNN由深入簡

       現有有很多關於faster-rcnn的文章,要麼就是純理論,要麼就是幹講代碼,讀下來好像若有所思又好像什麼都不會。斷斷續續看了一個月,不要問我爲什麼要這麼長時間,因爲我不會告訴你的......

       本文旨在將前輩們做的工作進行整合,從論文的原理和代碼的實現流程深度剖析faster-rcnn,也當一個學習記錄和一個疑問解答。語言和邏輯有不同的地方望諒解

       pytorch代碼實現參考:https://github.com/chenyuntc/simple-faster-rcnn-pytorch

       其他參考:

       https://www.cnblogs.com/kerwins-AC/p/9728731.html

       https://zhuanlan.zhihu.com/p/31426458(推薦)


目錄

1、特徵提取 

2、RPN網絡

2.1、RPN網絡的向前傳播

2.1.1、anchor-box

2.1.2、anchor的偏移量計算

2.1.3、anchor的前景與背景計算

2.1.4、proposal篩選以及ROI計算

2.1.5、RPN層輸出

3、ROI區域篩選以及標籤分配

4、head層計算

5、損失的計算

5.1、RPN損失

5.1.1、損失計算

5.2、ROI損失



       上圖是Faster-RCNN的整體架構,可以分爲3大部分,特徵提取、RPN網絡、Header。代碼結構中分爲4部分,主要是由於訓練中我們要自己生成標籤。

# 特徵圖提取
features = self.faster_rcnn.extractor(imgs)

# RPN層
rpn_locs, rpn_scores, rois, roi_indices, anchor = \
            self.faster_rcnn.rpn(features, img_size, scale)

# Header層的標籤生成
sample_roi, gt_roi_loc, gt_roi_label = self.proposal_target_creator(
            roi,
            at.tonumpy(bbox),
            at.tonumpy(label),
            self.loc_normalize_mean,
            self.loc_normalize_std)

# Header層
roi_cls_loc, roi_score = self.faster_rcnn.head(
            features,
            sample_roi,
            sample_roi_index)

1、特徵提取 

 

       特徵提取部分使用的VGG16模型,經PxQ的圖像縮放成爲MxN之後輸入到網絡進行特徵提取,所有的conv層的參數都是:kernel_size=3,pad=1,stride=1,也就是每次卷積完特徵圖的size是不會變化的;所有的pooling層參數爲kernel_size=2,pad=0,stride=2,也就是每次經歷池化,特徵圖的大小就減少了一半。經歷過這樣的提取之後可以到一個size = M/16 * N/16的特徵圖,因此feature map上的每個點對應的是原圖中16x16的一塊區域。


2、RPN網絡

       大家都知道faster-rcnn最重要的就是提出了RPN網絡,我自己習慣叫區域生成網絡。之前的網絡是two-stage模型,兩個階段所以效率不高。而RPN的加入實現了end-to-end,並且通過共享特徵圖加速了運算,可謂是膩害了。理論和代碼是如何實現的呢?

        上圖爲RPN的網絡結構。首先在特徵圖的基礎上它又進行了一次3x3的卷積,爲什麼要這麼處理,我也不知道了,也許是更robust。接來下RPN網絡分出兩條支線,上面的一條支線是用來判斷網絡proposal的區域是foreground還是background的,下面的支線用來計算proposal的區域的偏移量,最後進行整合就得到了ROI區域。

       很多教程到這裏就結束了,實現就沒得講了,理論到代碼可是有天差地別的。有的博文幹講代碼,我根本就和這個網絡結構沒法對應上。

       我的問題:1、region-proposal是怎麼proposal的,其中的參數是什麼意義;2、數據的流向是什麼樣的?3、proposals的篩選是在哪一步做的?4、標籤是怎麼打的?......我有一籮筐的問題。


2.1、RPN網絡的向前傳播

rpn_locs, rpn_scores, rois, roi_indices, anchor = \
            self.faster_rcnn.rpn(features, img_size, scale)

我們輸入的是特徵圖以及圖像尺寸,整體的向前傳播如下代碼,我會對向前傳播中的內容做剖析,所以forward代碼中的重要函數會在下文的子標題中找到: 

def forward(self, x, img_size, scale=1.):
        # x爲vgg提取的特徵圖
        n, _, hh, ww = x.shape
        # 計算所有的anchor
        anchor = _enumerate_shifted_anchor(
            np.array(self.anchor_base),
            self.feat_stride, hh, ww)

        n_anchor = anchor.shape[0] // (hh * ww)
        h = F.relu(self.conv1(x))

        # 計算的 36 個 偏移量
        rpn_locs = self.loc(h)
        # UNNOTE: check whether need contiguous
        # A: Yes
        rpn_locs = rpn_locs.permute(0, 2, 3, 1).contiguous().view(n, -1, 4)
        # 計算的每個錨點對應9個框的的得分
        rpn_scores = self.score(h)
        rpn_scores = rpn_scores.permute(0, 2, 3, 1).contiguous()
        # 得分激活
        rpn_softmax_scores = F.softmax(rpn_scores.view(n, hh, ww, n_anchor, 2), dim=4)
        rpn_fg_scores = rpn_softmax_scores[:, :, :, :, 1].contiguous()
        rpn_fg_scores = rpn_fg_scores.view(n, -1)
        rpn_scores = rpn_scores.view(n, -1, 2)

        rois = list()
        roi_indices = list()
        for i in range(n):
            # 將anchor-box進行偏移得到新的anchor-box,然後根據得分和非最大值抑制或者圖像的大小篩選感興趣區域
            # 大概得到2000個左右的感興趣區域
            roi = self.proposal_layer(
                rpn_locs[i].cpu().data.numpy(),
                rpn_fg_scores[i].cpu().data.numpy(),
                anchor, img_size,
                scale=scale)
            batch_index = i * np.ones((len(roi),), dtype=np.int32)
            rois.append(roi)
            roi_indices.append(batch_index)

        rois = np.concatenate(rois, axis=0)
        roi_indices = np.concatenate(roi_indices, axis=0)
        return rpn_locs, rpn_scores, rois, roi_indices, anchor

2.1.1、anchor-box

       RPN的proposal來自於很多個候選框,那麼我們就要先生成這些候選框。怎麼做的呢?我們在第一個特徵點的位置區作出錨框的中心,並根據不同的尺度和比例進行縮放得到9個不同的錨框,按照[Ymin,Xmin,Ymax,Xmax]的座標形式記錄下來,我們要爲每個特徵點都做出9個錨框,就根據得到的基礎錨框進行平移就好了。

# 基礎錨框生成
def generate_anchor_base(base_size=16, ratios=[0.5, 1, 2],
                         anchor_scales=[8, 16, 32]):
    py = base_size / 2.
    px = base_size / 2.

    anchor_base = np.zeros((len(ratios) * len(anchor_scales), 4),
                           dtype=np.float32)
    for i in six.moves.range(len(ratios)):
        for j in six.moves.range(len(anchor_scales)):
            h = base_size * anchor_scales[j] * np.sqrt(ratios[i])
            w = base_size * anchor_scales[j] * np.sqrt(1. / ratios[i])

            index = i * len(anchor_scales) + j
            anchor_base[index, 0] = py - h / 2.
            anchor_base[index, 1] = px - w / 2.
            anchor_base[index, 2] = py + h / 2.
            anchor_base[index, 3] = px + w / 2.
    return anchor_base


# 平移得到所有特徵點的錨框
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):
    import numpy as xp
    shift_y = xp.arange(0, height * feat_stride, feat_stride)
    shift_x = xp.arange(0, width * feat_stride, feat_stride)
    shift_x, shift_y = xp.meshgrid(shift_x, shift_y)
    shift = xp.stack((shift_y.ravel(), shift_x.ravel(),
                      shift_y.ravel(), shift_x.ravel()), axis=1)

    A = anchor_base.shape[0]
    K = shift.shape[0]
    anchor = anchor_base.reshape((1, A, 4)) + \
             shift.reshape((1, K, 4)).transpose((1, 0, 2))
    anchor = anchor.reshape((K * A, 4)).astype(np.float32)
    return anchor

2.1.2、anchor的偏移量計算

self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)

 這裏對應的就是rpn網絡的下面一條支線,即通過卷積計算所有錨框的計算偏移量。爲什麼是計算偏移量?因爲後面還有理論偏移量。


2.1.3、anchor的前景與背景計算

self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)

這就是rpn網絡的上面一條支線了,也就是用來區分proposal是前景還是背景的,當然了這也是計算的前景和背景,因爲我們有理論的前景和背景。


2.1.4、proposal篩選以及ROI計算

上面生成的有20000個框子,實在是太多了,所以要經過一波篩選了,篩選出來的就是proposal區域了。在訓練模式下我們生成2000個proposal框,測試模式下生成200個框。

在上面的anchor的偏移量計算中得到了所有偏移量loc,我們將偏移應用到anchor上,就得了初步調整的bbox。1、把所有邊界超過圖像的bbox全部去掉,2、根據得分排序篩選,3、非最大值抑制篩選出2000個框作爲ROI區域

# 根據偏移量和anchor計算,得到新的邊界框
def loc2bbox(src_bbox, loc):
    if src_bbox.shape[0] == 0:
        return xp.zeros((0, 4), dtype=loc.dtype)

    src_bbox = src_bbox.astype(src_bbox.dtype, copy=False)

    #給出原始bbox框,將其解碼成爲[y, x, height, width]的格式
    src_height = src_bbox[:, 2] - src_bbox[:, 0]
    src_width = src_bbox[:, 3] - src_bbox[:, 1]
    src_ctr_y = src_bbox[:, 0] + 0.5 * src_height
    src_ctr_x = src_bbox[:, 1] + 0.5 * src_width

    dy = loc[:, 0::4]
    dx = loc[:, 1::4]
    dh = loc[:, 2::4]
    dw = loc[:, 3::4]

    #將原始bbox框進行微調,得到新的bbox框,然後保存爲dst_bbox
    ctr_y = dy * src_height[:, xp.newaxis] + src_ctr_y[:, xp.newaxis]
    ctr_x = dx * src_width[:, xp.newaxis] + src_ctr_x[:, xp.newaxis]
    h = xp.exp(dh) * src_height[:, xp.newaxis]
    w = xp.exp(dw) * src_width[:, xp.newaxis]

    dst_bbox = xp.zeros(loc.shape, dtype=loc.dtype)
    dst_bbox[:, 0::4] = ctr_y - 0.5 * h
    dst_bbox[:, 1::4] = ctr_x - 0.5 * w
    dst_bbox[:, 2::4] = ctr_y + 0.5 * h
    dst_bbox[:, 3::4] = ctr_x + 0.5 * w

    return dst_bbox




class ProposalCreator:
    def __init__(self,
                 parent_model,
                 nms_thresh=0.7,        # 非最大值抑制閾值
                 n_train_pre_nms=12000,
                 n_train_post_nms=2000,
                 n_test_pre_nms=6000,
                 n_test_post_nms=300,
                 min_size=16
                 ):
        self.parent_model = parent_model
        self.nms_thresh = nms_thresh
        self.n_train_pre_nms = n_train_pre_nms
        self.n_train_post_nms = n_train_post_nms
        self.n_test_pre_nms = n_test_pre_nms
        self.n_test_post_nms = n_test_post_nms
        self.min_size = min_size

    def __call__(self, loc, score,
                 anchor, img_size, scale=1.):
        roi = loc2bbox(anchor, loc)

        # Clip predicted boxes to image.
        roi[:, slice(0, 4, 2)] = np.clip(
            roi[:, slice(0, 4, 2)], 0, img_size[0])
        roi[:, slice(1, 4, 2)] = np.clip(
            roi[:, slice(1, 4, 2)], 0, img_size[1])

        # Remove predicted boxes with either height or width < threshold.
        min_size = self.min_size * scale
        hs = roi[:, 2] - roi[:, 0]
        ws = roi[:, 3] - roi[:, 1]
        keep = np.where((hs >= min_size) & (ws >= min_size))[0]
        roi = roi[keep, :]
        score = score[keep]

        # Sort all (proposal, score) pairs by score from highest to lowest.
        # Take top pre_nms_topN (e.g. 6000).
        order = score.ravel().argsort()[::-1]
        if n_pre_nms > 0:
            order = order[:n_pre_nms]
        roi = roi[order, :]

        # Apply nms (e.g. threshold = 0.7).
        # Take after_nms_topN (e.g. 300).

        # unNOTE: somthing is wrong here!
        # TODO: remove cuda.to_gpu
        keep = non_maximum_suppression(
            cp.ascontiguousarray(cp.asarray(roi)),
            thresh=self.nms_thresh)
        if n_post_nms > 0:
            keep = keep[:n_post_nms]
        roi = roi[keep]
        return roi

2.1.5、RPN層輸出

rpn層輸出很多內容,rpn_locs:所有anchor-box的計算偏移量; rpn_scores:所有anchor的計算得分;rois:生成的感興趣區域;roi_indices:感興趣區域的索引; anchor:生成的所有錨框。

這裏的輸出在計算損失時是要使用的,所以不能忘了他們是怎麼來的.....之前看的很亂就是不知道數據是哪裏來的。


3、ROI區域篩選以及標籤分配

RPN層生成2000個roi區域還是太多了,我們得再篩選篩選得到128個送到header層中去計算。

sample_roi, gt_roi_loc, gt_roi_label = self.proposal_target_creator(
            roi,
            at.tonumpy(bbox),
            at.tonumpy(label),
            self.loc_normalize_mean,
            self.loc_normalize_std)

我們輸入的是RPN層輸出的ROI、數據集中的bbox(ground truth)、和數據集中的標籤。

我們在之前的計算中並不知道RPN層生成的ROI區域到底在真實圖像中屬於哪個bbox(換句話說就是不知道ROI和哪個bbox離得最近),這裏就給每個ROI區域分配一個最合適的bbox。

首先我們計算每個roi相對每個bbox的iou(交併比),找到最大的索引作爲真實標籤,並且保存最大的iou值,然後根據iou值選取128個樣本作爲樣本送入header層中計算,這128個樣本中有64個正樣本(前景)和64個負樣本(背景)。接下來計算精心帥選出的樣本相對其bbox的偏移量作爲真實偏移量。

最後返回取到的128個樣本,真實標籤以及真實偏移量。

#爲roi區域分配gt-box
#call方法爲每一個生成區域生成對應的目標區域
class ProposalTargetCreator(object):
    # n_sample 生成區域的個數
    # pos_ratio 被標記爲前景的分數
    # pos_iou_thresh Iou閾值
    def __init__(self,
                 n_sample=128,
                 pos_ratio=0.25, pos_iou_thresh=0.5,
                 neg_iou_thresh_hi=0.5, neg_iou_thresh_lo=0.0
                 ):
        self.n_sample = n_sample
        self.pos_ratio = pos_ratio
        self.pos_iou_thresh = pos_iou_thresh
        self.neg_iou_thresh_hi = neg_iou_thresh_hi
        self.neg_iou_thresh_lo = neg_iou_thresh_lo  # NOTE:default 0.1 in py-faster-rcnn

    def __call__(self, roi, bbox, label,
        loc_normalize_mean=(0., 0., 0., 0.),
        loc_normalize_std=(0.1, 0.1, 0.2, 0.2)):
        # roi (array) :感興趣區域
        # bbox :gt-box的座標
        # label :gt-box的標籤,標籤的範圍爲 0 - (classes-1)
        n_bbox, _ = bbox.shape

        roi = np.concatenate((roi, bbox), axis=0)

        pos_roi_per_image = np.round(self.n_sample * self.pos_ratio)    # 128個圖像中正標籤的個數
        
        # 計算所有的roi和gt的iou,返回值iou.shape = (N, K),意思是每個roi對所有的gt-box進行了計算iou
        iou = bbox_iou(roi, bbox)
        gt_assignment = iou.argmax(axis=1)   # 給每個roi分配gt-box,並且計算對應的iou
        max_iou = iou.max(axis=1)
        # Offset range of classes from [0, n_fg_class - 1] to [1, n_fg_class].
        # The label with value 0 is the background.
        gt_roi_label = label[gt_assignment] + 1   # 先給所有的roi賦對應的標籤值

        # Select foreground RoIs as those with >= pos_iou_thresh IoU.
        pos_index = np.where(max_iou >= self.pos_iou_thresh)[0]     #max_iou大於最大值的是正標籤
        # 實際正標籤的個數和我們設置的正標籤的個數比較,對應文中如果不夠就用負樣本填充,如果夠那就隨機選取
        pos_roi_per_this_image = int(min(pos_roi_per_image, pos_index.size))    
        if pos_index.size > 0:
            pos_index = np.random.choice(
                pos_index, size=pos_roi_per_this_image, replace=False)

        # Select background RoIs as those within
        # [neg_iou_thresh_lo, neg_iou_thresh_hi).
        # 負樣本是一個範圍,根據這個範圍查找負樣本
        neg_index = np.where((max_iou < self.neg_iou_thresh_hi) &
                             (max_iou >= self.neg_iou_thresh_lo))[0]
        # 驗證了論文中寫的內容,負樣本個數爲總的個數減去正樣本的個數
        neg_roi_per_this_image = self.n_sample - pos_roi_per_this_image
        neg_roi_per_this_image = int(min(neg_roi_per_this_image,
                                         neg_index.size))
        # 隨機選取負樣本
        if neg_index.size > 0:
            neg_index = np.random.choice(
                neg_index, size=neg_roi_per_this_image, replace=False)

        # The indices that we're selecting (both positive and negative).
        # 取出所有作爲正樣本和負樣本的標籤值
        keep_index = np.append(pos_index, neg_index)
        gt_roi_label = gt_roi_label[keep_index]
        # 將負樣本的標籤都設置爲0
        gt_roi_label[pos_roi_per_this_image:] = 0  # negative labels --> 0
        # 選取樣本的區域框
        sample_roi = roi[keep_index]

        # Compute offsets and scales to match sampled RoIs to the GTs.
        # 取樣和對應的最大的gt-box計算偏移量
        gt_roi_loc = bbox2loc(sample_roi, bbox[gt_assignment[keep_index]])
        gt_roi_loc = ((gt_roi_loc - np.array(loc_normalize_mean, np.float32)
                       ) / np.array(loc_normalize_std, np.float32))
        # 返回取得的128個樣本,對應的標籤(背景爲0),對應的偏移量

        # 返回值1、roi區域的取樣(128個);返回值2、roi區域對應的偏移量;返回值3、roi區域對應的label(背景爲0)
        return sample_roi, gt_roi_loc, gt_roi_label

4、head層計算

roi_cls_loc, roi_score = self.faster_rcnn.head(
            features,
            sample_roi,
            sample_roi_index)

輸入:特徵圖、128個roi樣本、roi樣本的索引,最後輸出計算分類得分和計算位置偏移量

這裏涉及到共享特徵的問題,體現在哪裏呢?我們的ROI樣本的大小是在實際圖像中的大小MxN,由於之前有4次池化,所以我們把ROI的大小縮小16倍得到其在feature map上對應的部分。

得到了ROI對應的feature,我們要對其進行ROI pooling?因爲我們要實現固定長度的輸出,所以必須這麼做。將對應的feature map區域分爲wxh的網格,對每一個進行max-pooling處理,那麼特徵圖就被轉化成了固定大小了。


5、損失的計算

5.1、RPN損失

        # ------------------ RPN losses ------------------- #
        # rpn向前傳播會生成rpn_locs,即偏移量, rpn_scores, 即分類(前景/背景), anchor。
        # 我們需要對應的標籤,即利用bbox和anchor生成前景和背景的256個標籤以及對應的偏移量
        gt_rpn_loc, gt_rpn_label = self.anchor_target_creator(
            at.tonumpy(bbox),
            anchor,
            img_size)
        gt_rpn_label = at.totensor(gt_rpn_label).long()  
        gt_rpn_loc = at.totensor(gt_rpn_loc)       
        
        # 使用rpn層生成的偏移量和相對gt-box的偏移量計算位置損失     
        rpn_loc_loss = _fast_rcnn_loc_loss(              
            rpn_loc,
            gt_rpn_loc,
            gt_rpn_label.data,
            self.rpn_sigma)

        # NOTE: default value of ignore_index is -100 ...
        # 使用rpn層生成的標籤和相對gt-box的標籤計算標籤損失, 標籤值爲-1的被忽略,總數爲256
        rpn_cls_loss = F.cross_entropy(rpn_score, gt_rpn_label.cuda(), ignore_index=-1)
        _gt_rpn_label = gt_rpn_label[gt_rpn_label > -1]
        _rpn_score = at.tonumpy(rpn_score)[at.tonumpy(gt_rpn_label) > -1]
        self.rpn_cm.add(at.totensor(_rpn_score, False), _gt_rpn_label.data.long())

通過閱讀之前的部分可以發現:RPN部分有計算的偏移量和計算的分類得分,但是沒有對應的label標籤啊,這可怎麼計算損失呢?我們一起看call函數:

call方法的輸入爲bbox:數據集中的邊界框;anchor:在RPN中生成的所有的20000多個錨框;img_size:圖像大小

首先根據圖像大小將超出圖像的anchor全部去除,然後對剩下的錨框打標籤。

首先計算篩選出的錨框對與所有bbox的iou,如何打標籤的呢?很多博文中解釋的很詳細,將最大iou小於背景閾值的直接設置爲背景(0),將對應於每個bbox的最大iou的anchor設置爲1(保證每個bbox都有對應的正的anchor),將最大iou大於前景閾值的設置爲1。真實標籤到這裏就打完了。

爲篩選後的anchor計算對應的真實偏移量loc

到這裏計算的所有的內容都是篩選之後的anchor,我們需要把這些映射到所有的anchor上。

class AnchorTargetCreator(object):
    # 給錨框分配gt-bbox
    def __init__(self,
                 n_sample=256,
                 pos_iou_thresh=0.7, neg_iou_thresh=0.3,
                 pos_ratio=0.5):
        self.n_sample = n_sample
        self.pos_iou_thresh = pos_iou_thresh
        self.neg_iou_thresh = neg_iou_thresh
        self.pos_ratio = pos_ratio

    def __call__(self, bbox, anchor, img_size):
        img_H, img_W = img_size

        n_anchor = len(anchor)
        # 將超出圖片範圍的anchor全部去掉
        inside_index = _get_inside_index(anchor, img_H, img_W)
        anchor = anchor[inside_index]
        # 返回值爲每個anchor對應的標籤以及(背景/前景),以及每個anchor對應的gt-bbox的索引
        argmax_ious, label = self._create_label(
            inside_index, anchor, bbox)

        # compute bounding box regression targets
        # 爲每個anchor計算對應的偏移量
        loc = bbox2loc(anchor, bbox[argmax_ious])

        # map up to original set of anchors
        # 將結果映射到所有的anchor上,所以label.size = n_anchor和location.size = n_anchor, 4
        label = _unmap(label, n_anchor, inside_index, fill=-1)
        loc = _unmap(loc, n_anchor, inside_index, fill=0)

        return loc, label

    def _create_label(self, inside_index, anchor, bbox):
        # label: 1 is positive, 0 is negative, -1 is dont care
        label = np.empty((len(inside_index),), dtype=np.int32)
        label.fill(-1)

        argmax_ious, max_ious, gt_argmax_ious = \
            self._calc_ious(anchor, bbox, inside_index)

        # assign negative labels first so that positive labels can clobber them
        label[max_ious < self.neg_iou_thresh] = 0

        # positive label: for each gt, anchor with highest iou
        label[gt_argmax_ious] = 1

        # positive label: above threshold IOU
        label[max_ious >= self.pos_iou_thresh] = 1

        # subsample positive labels if we have too many
        # 選擇128個正樣本,128個負樣本,label總數爲len(inside——index)
        n_pos = int(self.pos_ratio * self.n_sample)
        pos_index = np.where(label == 1)[0]
        if len(pos_index) > n_pos:
            disable_index = np.random.choice(
                pos_index, size=(len(pos_index) - n_pos), replace=False)
            label[disable_index] = -1

        # subsample negative labels if we have too many
        #
        n_neg = self.n_sample - np.sum(label == 1)
        neg_index = np.where(label == 0)[0]
        if len(neg_index) > n_neg:
            disable_index = np.random.choice(
                neg_index, size=(len(neg_index) - n_neg), replace=False)
            label[disable_index] = -1

        return argmax_ious, label

    def _calc_ious(self, anchor, bbox, inside_index):
        # ious between the anchors and the gt boxes
        # ious.shape = N,K 
        ious = bbox_iou(anchor, bbox)
        argmax_ious = ious.argmax(axis=1)
        max_ious = ious[np.arange(len(inside_index)), argmax_ious]
        gt_argmax_ious = ious.argmax(axis=0)
        gt_max_ious = ious[gt_argmax_ious, np.arange(ious.shape[1])]
        gt_argmax_ious = np.where(ious == gt_max_ious)[0]

        return argmax_ious, max_ious, gt_argmax_ious


def _unmap(data, count, index, fill=0):
    # Unmap a subset of item (data) back to the original set of items (of
    # size count)

    if len(data.shape) == 1:
        ret = np.empty((count,), dtype=data.dtype)
        ret.fill(fill)
        ret[index] = data
    else:
        ret = np.empty((count,) + data.shape[1:], dtype=data.dtype)
        ret.fill(fill)
        ret[index, :] = data
    return ret


def _get_inside_index(anchor, H, W):
    # Calc indicies of anchors which are located completely inside of the image
    # whose size is speficied.
    index_inside = np.where(
        (anchor[:, 0] >= 0) &
        (anchor[:, 1] >= 0) &
        (anchor[:, 2] <= H) &
        (anchor[:, 3] <= W)
    )[0]
    return index_inside

5.1.1、損失計算

計算損失,輸入分爲被預測的偏移量(所有的錨框的偏移量),真實偏移量,真實標籤【因此計算時需要映射到全部分anchor上】。對於偏移量的損失,只有正樣本的偏移損失會被計算,但是損失均值時,除以的是篩選出的樣本總數。

rpn_loc_loss = _fast_rcnn_loc_loss(              
            rpn_loc,
            gt_rpn_loc,
            gt_rpn_label.data,
            self.rpn_sigma)
def _fast_rcnn_loc_loss(pred_loc, gt_loc, gt_label, sigma):
    in_weight = t.zeros(gt_loc.shape).cuda()
    # Localization loss is calculated only for positive rois.
    # 只有正樣本的偏移損失會被計算,但是計算平均值時負樣本的總數會被計算進去,標籤爲-1的會被忽略
    # NOTE:  unlike origin implementation, 
    # we don't need inside_weight and outside_weight, they can calculate by gt_label
    in_weight[(gt_label > 0).view(-1, 1).expand_as(in_weight).cuda()] = 1
    loc_loss = _smooth_l1_loss(pred_loc, gt_loc, in_weight.detach(), sigma)
    # Normalize by total number of negtive and positive rois.
    loc_loss /= ((gt_label >= 0).sum().float()) # ignore gt_label==-1 for rpn_loss
    return loc_loss

5.2、ROI損失

在head層的計算中已經得到了計算分類得分和計算位置偏移量,在第3節中proposal的篩選即roi標籤節中得到了真實的計算分類得分。這樣就可以直接計算損失了,和RPN層的損失相似。

 # 使用fast-rcnn層輸出的位置,和gt-box的位置計算位置損失,同樣只計算前景區域偏移的損失
        roi_loc_loss = _fast_rcnn_loc_loss(
            roi_loc.contiguous(),
            gt_roi_loc,
            gt_roi_label.data,
            self.roi_sigma)

        # 使用fast-rcnn層輸出的分類,和gt-box的標籤計算分類損失,計算所有的標籤損失
        roi_cls_loss = nn.CrossEntropyLoss()(roi_score, gt_roi_label.cuda())

至此,主體部分就結束了,其他還有很多沒有閱讀完,待補充......

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