現有有很多關於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(推薦)
目錄
上圖是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())
至此,主體部分就結束了,其他還有很多沒有閱讀完,待補充......