看RPN網絡基本是從faster-RCNN來的。看的有點迷糊。後來看了幾篇博客感覺有點懂了。記錄下來防止自己忘了吧。儘量用大白話。瞭解RPN網絡的整個應用流程。
1.明確輸入輸出:輸入是CNN卷積後的特徵圖 輸出是候選框圖 如下圖。知乎上盜了張圖。 感覺這個是最形象的
圖1
2.先形象解釋一下:左側是特徵圖 ,立方體厚度決定於特徵圖維度。右側紅框就是經過該網絡後提取出的感興趣ROI區域。 這個特徵圖生成方法很多了,例如 ZF,VGG ,Resnet 什麼的都可以。這裏就不細說了。RPN不關注具體目標是啥,是車,是人或者狗,樓房都可以。它只是將框內認爲是目標,框外認爲是背景,做了個二分類。至於框內目標具體是啥,最終是交給分類網絡去做。結果會略顯潦草,後面會對邊框進行提煉。
3.具體做法:以ZF網絡計算的特徵圖作爲輸入來說吧(不同網絡體現在輸入上是特徵圖的尺寸不同,RPN實現都是一致的),當然不同網絡的檢測結果差異是網絡本身決定的了。
3.1.計算RPN的輸入特徵圖(又見無恥的盜圖....) 對應於圖1的最左側
圖2
首先輸入圖像大小是224*224*3。 然後開始各種卷積 池化。卷積核大小,個數,步長,填充參數都在圖上可見。最終,rpn是選擇conv5的結果作爲其輸入,也就是13*13*256這個特徵圖是RPN網絡的輸入。具體這個卷積流程去參考其他文章即可。本文重點介紹RPN的處理流程。
3.2.生成Anchors 對應於圖1的中間部分
首先牢記:特徵圖上的每個點生成一個Anchors,特徵圖大小與Anchors對應。文中是用9個錨點。所謂Anchors 就理解成9個不同的框好了(框的屬性自然就是長寬了,再進一步就是四個點的座標嘍),特徵圖上的每個點對應於一個Anchors。這九個不同的框按大小,比例生成的,論文中有介紹。這個框的數據(框的左上角座標(x0,y0),右下角座標(x1,y1)這四個值都是對應於原圖的)對應於原圖,就是圖1右側那個紅框。下面這個就是原圖,紅色點就是特徵圖上的點對應於原圖的位置。(接着盜圖)。它部署 9 個錨點框:3 個不同寬高比 * 3 個不同大小的錨點框 = 9個錨點框。每一個位置使用 9 個錨點,每個位置會生成 2×9 個目標分數和 4×9 個座標分數。顯然,通過一箇中心定義9個不同的框,就是爲實現多尺度這個想法。當然這樣做獲得檢測框很不準確,後面會做2次bounding box regression修正檢測框位置。
圖3
下面是卷積的具體過程。
先上總圖
圖4
a.conv fetaure map到 intermediate layer的卷積過程:
在特徵圖上用sliding window 3*3的窗口滑動。這個窗口提出來的圖 conv feature map大小是3*3*256,上面那個圖只顯示了1通道的,在它下面還有255層。那麼對於特徵圖上某個點來說,就要用256個大小爲3*3*256的卷積覈對這個窗口進行卷積,卷積過程顯然是對應通道(256channel)卷積,獲得1*256維的向量,然後累加獲得1*1向量,卷積核有256個,這樣的向量就有256個最終結果就是1*256(這個256是卷積核個數)。結果對應於下圖intermediate layer。整張特徵圖卷積結果如下圖
b.intermediate layer的256維向量後面對應兩條分支:
cls layer分支是目標和背景的二分類(classification),因爲k等於9,所以通過1×1×256×18的卷積核得到 2*9 = 18個分數,分別是目標和背景的評分。這次卷積總的流程如下圖。最右側的18層中,每兩層代表了某個anchor的特徵圖上所有點的前景背景概率值。
reg layer分支。如果候選框是目標區域,就去判斷該目標區域的候選框位置在哪,這個時候另一條分支就過1×1×256×36的卷積得到4*9個值,每個框包含4個值(x,y,w,h),就是9個候選區域對應的框應該偏移的具體位置Δxcenter,Δycenter,Δwidth,Δheight。如果候選框不是目標區域,就直接將該候選框去除掉,不再進行後續位置信息的判斷操作。這裏預測的值都是通過模型不斷訓練得到的。顯然最右側的18層中,每4層代表了某個anchor的特徵圖上所有點的偏移量的值。
3.3獲得最終結果: 對應於圖1的右側部分
先上個圖。裏面綠色框就是標註框,是我們樣本中框好的,代表了飛機的真實存在框範圍,紅色框可以看成是候選框的初值,針對特徵圖上的某個點,這樣的框有9個。
圖5
首先是分類問題,簡而言之就是存在與否問題。上面紅色就是此次認爲紅色框中存在灰機了。對應於cls layer層的某個錨點的某種形狀的框,其判斷爲目標的概率非常大。我們訓練模型,讓cls layer更好的區分前景(灰機),背景(非灰機)。在訓練之初我們先對前景和遠景分別用正樣本(1)和負樣本(0)進行標籤工作
正樣本標籤: 與某一個GT有最高的IOU重疊的Anchor box。與任意一個GT的IOU重疊部分大於0.7的Anchor box 。
負樣本標籤: 給與所有GT包圍盒的IOU重疊部分都小於0.3的Anchor box。
然後通過損失函數softmax loss(cross entropy loss)進行計算,這裏的監督標籤就是GT(1,0),它不斷告訴訓練出的結果對還是不對。就這樣訓練到最後我們的RPN分類訓練完成。這個步驟獲得了紅色框
然後是迴歸問題,簡而言之就是修正邊框而已。上面的藍色就是在紅色框基礎上去修正得來的,對應於reg layer層。我們通過不斷訓練它,讓其得到的四個值,x,y,w,h能與真實的GT座標不斷靠攏,能更接近真實目標的座標。看上面那個飛機圖,Region Porposal 是我們RPN給出的一個候選區域初值。把上面紅色框圖的信息給下一次最終的分類模塊,它可能識別不出飛機。因爲框的太少了。 所以在訓練的時候,就需要儘可能的使我們的Anchor box的框與真實的GT的框接近。但是我們知道想讓A-box=GT,是不太現實的,因爲我們沒法做到100%。
圖6
所以如上圖所示,我們的目標就是希望從現有的原始紅色的Region Proposal最終得到藍色的預測GT,而這個藍色GT其實與綠色的真實的標定GT非常接近了已經。總而言之,我們如果得到了預測GT,那麼目標就達成了。這個步驟對紅色框估計獲得了藍色框、這個框顯然就是圖1中那幾個紅色的框框了。
多邊框迴歸的參考文獻列在下面了。詳情見它。簡而言之就是已經有個初始框,但是它不準,不準的因素有4個, x,y方向的偏移,x,y方向的縮放比例。然後就用神經網絡去訓練。讓紅色框與綠色框儘可能接近,那麼對應的這4個參數就是需要訓練的值唄
RPN具體的pytorch代碼如下。可對照代碼和上面理論去對應。
from __future__ import absolute_import
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from model.utils.config import cfg
from .proposal_layer import _ProposalLayer
from .anchor_target_layer import _AnchorTargetLayer
from model.utils.net_utils import _smooth_l1_loss
import numpy as np
import math
import pdb
import time
class _RPN(nn.Module):
""" region proposal network """
def __init__(self, din):
super(_RPN, self).__init__()
self.din = din # get depth of input feature map, e.g., 256 FT
self.anchor_scales = cfg.ANCHOR_SCALES
self.anchor_ratios = cfg.ANCHOR_RATIOS
self.feat_stride = cfg.FEAT_STRIDE[0]
# define the convrelu layers processing input feature map
self.RPN_Conv = nn.Conv2d(self.din, 256, 3, 1, 1, bias=True)
# define bg/fg classifcation score layer
self.nc_score_out = len(self.anchor_scales) * len(self.anchor_ratios) * 2 # 2(bg/fg) * 9 (anchors)
self.RPN_cls_score = nn.Conv2d(256, self.nc_score_out, 1, 1, 0)
# define anchor box offset prediction layer
self.nc_bbox_out = len(self.anchor_scales) * len(self.anchor_ratios) * 4 # 4(coords) * 9 (anchors)
self.RPN_bbox_pred = nn.Conv2d(256, self.nc_bbox_out, 1, 1, 0)
# define proposal layer
self.RPN_proposal = _ProposalLayer(self.feat_stride, self.anchor_scales, self.anchor_ratios)
# define anchor target layer
self.RPN_anchor_target = _AnchorTargetLayer(self.feat_stride, self.anchor_scales, self.anchor_ratios)
self.rpn_loss_cls = 0
self.rpn_loss_box = 0
@staticmethod
def reshape(x, d):
input_shape = x.size()
x = x.view(
input_shape[0],
int(d),
int(float(input_shape[1] * input_shape[2]) / float(d)),
input_shape[3]
)
return x
def forward(self, base_feat, im_info, gt_boxes, num_boxes):
batch_size = base_feat.size(0)
# return feature map after convrelu layer
rpn_conv1 = F.relu(self.RPN_Conv(base_feat), inplace=True)
# get rpn classification score
rpn_cls_score = self.RPN_cls_score(rpn_conv1)
rpn_cls_score_reshape = self.reshape(rpn_cls_score, 2)
rpn_cls_prob_reshape = F.softmax(rpn_cls_score_reshape, 1)
rpn_cls_prob = self.reshape(rpn_cls_prob_reshape, self.nc_score_out)
# get rpn offsets to the anchor boxes
rpn_bbox_pred = self.RPN_bbox_pred(rpn_conv1)
# proposal layer
cfg_key = 'TRAIN' if self.training else 'TEST'
rois = self.RPN_proposal((rpn_cls_prob.data, rpn_bbox_pred.data,
im_info, cfg_key))
self.rpn_loss_cls = 0
self.rpn_loss_box = 0
# generating training labels and build the rpn loss
if self.training:
assert gt_boxes is not None
rpn_data = self.RPN_anchor_target((rpn_cls_score.data, gt_boxes, im_info, num_boxes))
# compute classification loss
rpn_cls_score = rpn_cls_score_reshape.permute(0, 2, 3, 1).contiguous().view(batch_size, -1, 2)
rpn_label = rpn_data[0].view(batch_size, -1)
rpn_keep = Variable(rpn_label.view(-1).ne(-1).nonzero().view(-1))
rpn_cls_score = torch.index_select(rpn_cls_score.view(-1,2), 0, rpn_keep)
rpn_label = torch.index_select(rpn_label.view(-1), 0, rpn_keep.data)
rpn_label = Variable(rpn_label.long())
self.rpn_loss_cls = F.cross_entropy(rpn_cls_score, rpn_label)
fg_cnt = torch.sum(rpn_label.data.ne(0))
rpn_bbox_targets, rpn_bbox_inside_weights, rpn_bbox_outside_weights = rpn_data[1:]
# compute bbox regression loss
rpn_bbox_inside_weights = Variable(rpn_bbox_inside_weights)
rpn_bbox_outside_weights = Variable(rpn_bbox_outside_weights)
rpn_bbox_targets = Variable(rpn_bbox_targets)
self.rpn_loss_box = _smooth_l1_loss(rpn_bbox_pred, rpn_bbox_targets, rpn_bbox_inside_weights,
rpn_bbox_outside_weights, sigma=3, dim=[1,2,3])
return rois, self.rpn_loss_cls, self.rpn_loss_box
參考的文章太多了,哎 就厚着臉皮寫個原創吧 哈哈。
參考文獻:
1.https://blog.csdn.net/w437684664/article/details/104238521
2.https://www.jianshu.com/p/468e08f739bd
3.https://www.jianshu.com/p/643cdcf674fc
4.https://blog.csdn.net/YZXnuaa/article/details/79221189
5.https://blog.csdn.net/qq_36269513/article/details/80421990
6.https://blog.csdn.net/zijin0802034/article/details/77685438/ 邊框迴歸的
7.https://blog.csdn.net/wangwei19871103/article/details/100929543 VGG主幹網絡與RPN的結合 基於keras的