Faster RCNN目標檢測之RPN網絡

  • rpn介紹

rpn(Region Proposal Network, 區域候選網絡)是faster rcnn中最重要的改進。它的主要功能是生成區域候選(Region Proposal),通俗來講,區域候選能夠看作是許多潛在的邊界框(也叫anchor,它是包含4個座標的矩形框)。ios

那麼爲何須要區域候選呢,由於在目標檢測以前,網絡並不知道圖片中存在多少個目標物體,因此rpn一般會預先在原圖上生成若干個邊界框,並最終輸出最有可能包含物體的anchor,也稱之爲區域候選,訓練階段會不斷的調整區域候選的位置,使其與真實物體框的誤差最小。git

rpn的結構以下圖所示,能夠看到,backbone輸出的特徵圖通過一個3 * 3卷積以後分別進入了不一樣的分支,對應不一樣的1 * 1卷積。第一個卷積爲定位層,輸出anchor的4個座標偏移。第二個卷積爲分類層,輸出anchor的先後景機率。github

rpn.jpg

  • rpn詳細過程

看完了rpn的大體結構,下面來看rpn的詳細過程。上圖中展現的就不細講了,主要來看一下,rpn是如何生成以及處理anchor的。下圖表示了rpn網絡的詳細結構數組

未命名圖片1.jpg

第一步,生成基礎anchor(base_anchor),基礎anchor的數目 = 長寬比的數目 * anchor的縮放比例數目, 即anchors_num = len(ratios) * len(scales)。這裏,設置了3種長寬比(1:1, 1:2,2:1)和3種縮放尺度(8, 16, 32),所以anchor_num = 9. 下圖表示了其中一個位置對應的9個尺寸的anchor。網絡

未命名圖片2.jpg

第二步,根據base_anchor,對特徵圖上的每個像素,都會以它爲中心生成9種不一樣尺寸的邊界框,因此總共生成60 40 9 = 21600個anchor。下圖所示的爲特徵圖上的每一個像素點在原圖上對應的位置。須要注意的是,全部生成的anchor都是相對於原圖而言的。app

未命名圖片1.png

第三步,也是最後一步,進行anchor的篩選。首先將定位層輸出的座標偏移應用到全部生成的anchor(也就是圖2中anchor to iou),而後將全部anchor按照前景機率/得分進行從高到低排序。如圖2所示,只取前pre_nms_num個anchor(訓練階段),最後anchor經過非極大值抑制(Non-Maximum-Suppression, nms)篩選獲得post_nms_num(訓練階段)個anchor,也稱做roi。ide

  • rpn代碼實現

首先是RegionProposalNetwork類的詳細代碼。post

# ------------------------ rpn----------------------#

import numpy as np
from torch.nn import functional as F
import torch as t
from torch import nn

from model.utils.bbox_tools import generate_anchor_base
from model.utils.creator_tool import ProposalCreator

class RegionProposalNetwork(nn.Module):
    
    """
    Args:
        in_channels (int): 輸入的通道數
        mid_channels (int): 中間層輸出的通道數
        ratios (list of floats): anchor的長寬比
        anchor_scales (list of numbers): anchor的縮放尺度
        feat_stride (int): 原圖與特徵圖的大小比例
        proposal_creator_params (dict): 傳入ProposalCreator類的參數
    """

    def __init__(
            self, in_channels=512, mid_channels=512, ratios=[0.5, 1, 2],
            anchor_scales=[8, 16, 32], feat_stride=16,
            proposal_creator_params=dict(),
    ):
        super(RegionProposalNetwork, self).__init__()

        # 生成數量爲(len(ratios) * len(anchors_scales))的基礎anchor, 基礎尺寸爲16 * feat_stride
        self.anchor_base = generate_anchor_base(
            anchor_scales=anchor_scales, ratios=ratios)
        self.feat_stride = feat_stride
        self.proposal_layer = ProposalCreator(self, **proposal_creator_params)
        n_anchor = self.anchor_base.shape[0]
        self.conv1 = nn.Conv2d(in_channels, mid_channels, 3, 1, 1)

        # 分類層 
        self.score = nn.Conv2d(mid_channels, n_anchor * 2, 1, 1, 0)

        # 迴歸層
        self.loc = nn.Conv2d(mid_channels, n_anchor * 4, 1, 1, 0)

        # 參數初始化
        normal_init(self.conv1, 0, 0.01)
        normal_init(self.score, 0, 0.01)
        normal_init(self.loc, 0, 0.01)

    def forward(self, x, img_size, scale=1.):
        """
        註釋
        * :math:`N` batch size
        * :math:`C` 輸入的通道數
        * :math:`H` and :math:`W` 輸入特徵圖的高和寬
        * :math:`A` 指定每一個像素的anchor數目

        Args:
            x (tensor): backbone輸出的特徵圖. shape -> :math:`(N, C, H, W)`.
            img_size (tuple of ints): 元組 :obj:`height, width`, 縮放後的圖片尺寸.
            scale (float): 從文件讀取的圖片和輸入的圖片的比例大小.

        Returns:
            * **rpn_locs**: 預測的anchor座標位移. shape -> :math:`(N, H W A, 4)`.
            * **rpn_scores**:  預測的前景機率得分. shape -> :math:`(N, H W A, 2)`.
            * **rois**: 篩選後的anchor數組. 它包含了一個批次的全部區域候選. shape -> :math:`(R', 4)`.
            * **roi_indices**: 表示roi對應的批次,shape -> :math:`(R',)`.
            * **anchor**: 生成的全部anchor. \
                shape -> :math:`(H W A, 4)`.

        """
        n, _, hh, ww = x.shape

        # 根據基礎anchor生成全部anchors, 全部的anchor均是在原圖上生成的
        # 一共生成 hh * ww * 9個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))

        # 定位層, rpn_locs --> (batch_size, 36, hh, ww)
        rpn_locs = self.loc(h)
        
        rpn_locs = rpn_locs.permute(0, 2, 3, 1).contiguous().view(n, -1, 4)

        # 分類層, rpn_locs --> (batch_size, 18, hh, ww)
        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()

        # shape --> (batch_size * hh * ww, 9)
        rpn_fg_scores = rpn_fg_scores.view(n, -1)
        
        # shape --> (batch_size * hh * ww, 9, 2)
        rpn_scores = rpn_scores.view(n, -1, 2)

        rois = list()
        roi_indices = list()
        for i in range(n):
        # 分批次處理
            # 根據anchors、預測的位置偏移和前景機率得分來生成候選區域
            """
            一、移除超出區域的anchor,按前景機率排序取出前pre_nums(訓練階段12000,測試階段6000)個anchors。
            二、進行nms,取出前post_nums(訓練階段2000,測試階段300)個anchors
            """
            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  
        
        
def normal_init(m, mean, stddev, truncated=False):
    """
    權重初始化
    """
    # x is a parameter
    if truncated:
        m.weight.data.normal_().fmod_(2).mul_(stddev).add_(mean)  # not a perfect approximation
    else:
        m.weight.data.normal_(mean, stddev)
        m.bias.data.zero_()
        
# 根據基礎anchor生成全部anchor
def _enumerate_shifted_anchor(anchor_base, feat_stride, height, width):
    """
    return
        shape -> (height * width * 9, 4)
    """
    shift_y = np.arange(0, height * feat_stride, feat_stride)
    shift_x = np.arange(0, width * feat_stride, feat_stride)
    
    # 根據特徵圖大小,在原圖上構建網格
    shift_x, shift_y = np.meshgrid(shift_x, shift_y)
    shift = np.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

而後是ProposalCreator類的代碼,它負責rpn網絡的anchor篩選,輸出區域候選(roi)測試

# -------------------- ProposalCreator ---------------#

class ProposalCreator:
    """
    Args:
        nms_thresh (float): 調用nms使用的iou閾值
        n_train_pre_nms (int): 在訓練階段,調用nms以前,保留的分值最高的前多少個anchor
        n_train_post_nms (int): 在訓練階段,調用nms以後,保留的分值最高的前多少個
        n_test_pre_nms (int): 在測試階段,調用nms以前,保留的分值最高的前多少個anchor
        n_test_post_nms (int): 在測試階段,調用nms以後,保留的分值最高的前多少個anchor
        min_size (int): 尺寸閾值,小於該尺寸則丟棄。
    """

    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.):
        """
        :math:`R` anchor總數目. 
        
        Args:
            loc (array): 預測的座標偏移,shape -> :math:`(R, 4)`.
            score (array): 預測的前景機率,shape -> :math:`(R,)`.
            anchor (array): 生成的anchor,shape -> :math:`(R, 4)`.
            img_size (tuple of ints): 元組,縮放前的圖片尺寸 :obj:`height, width`.
            scale (float): 

        Returns:
            array:

        """
        # NOTE: 測試時,須要
        # faster_rcnn.eval()
        # 設置 self.traing = False
        if self.parent_model.training:
            n_pre_nms = self.n_train_pre_nms
            n_post_nms = self.n_train_post_nms
        else:
            n_pre_nms = self.n_test_pre_nms
            n_post_nms = self.n_test_post_nms

        # 將anchor轉換爲候選.
        roi = loc2bbox(anchor, loc)

        
        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])

        # 丟棄尺寸小於最小尺寸閾值的anchor
        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]

        # 按照前景機率從大到小排序
        # 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, :]

        keep = non_maximum_suppression(
            np.ascontiguousarray(np.asarray(roi)),
            thresh=self.nms_thresh)
        if n_post_nms > 0:
            keep = keep[:n_post_nms]
        roi = roi[keep]
        return roi
  • 細節
  1. 爲何不是直接預測anchor的中心座標以及長寬或者4個座標,而是預測anchor的座標偏移(圖中的px,py,pw,ph)呢?
    未命名圖片.png
    a. 如直接預測中心座標以及長寬或者是預測4個座標,則大部分預測都是無效預測,由於網絡預測的輸出並不太可能知足這種約束條件。
    b. 圖片中的物體一般大小不一,形狀也大不相同,直接預測中心座標以及長寬或者是4個座標,範圍過大,這使得網絡難以訓練。
    c. 而座標偏移一方面大小較小,同時,座標偏移有良好的數學公式,可以方便的求導。
  2. iou的計算

    image.png

def bbox_iou(bbox_a, bbox_b):
    """
    return:
        array: shape -> (bbox_a.shape[0], bbox_b.shape[1])
    """
    if bbox_a.shape[1] != 4 or bbox_b.shape[1] != 4:
        raise IndexError

    # 上邊界和左邊界
    tl = np.maximum(bbox_a[:, None, :2], bbox_b[:, :2])
    # 下邊界和右邊界
    br = np.minimum(bbox_a[:, None, 2:], bbox_b[:, 2:])
    area_i = np.prod(br - tl, axis=2) * (tl < br).all(axis=2)
    
    area_a = np.prod(bbox_a[:, 2:] - bbox_a[:, :2], axis=1)
    area_b = np.prod(bbox_b[:, 2:] - bbox_b[:, :2], axis=1)
    return area_i / (area_a[:, None] + area_b - area_i)

有關RPN偏差,後續會在faster rcnn偏差那一節詳細說明。spa

Reference:
http://www.telesens.co/2018/0...

https://towardsdatascience.co...

https://tryolabs.com/blog/201...

https://github.com/chenyuntc/...

相關文章
相關標籤/搜索