天天看點

Faster RCNN原理及Pytorch代碼解讀——RPN(一):Anchor的生成Anchor的生成

這裡省略了特征提取子產品部分,個人感覺沒什麼好講的,就是選用一個網絡充當特征提取器,這個不是我們這個系列的重點,後面講的部分都是以VGG16作為特征提取網絡,需要注意一點就是由于VGG16的網絡設計,經過conv層不改變特征圖的尺寸,經過pool層特征圖尺寸會縮小到原來的一半。VGG16一共有5個pool層,我們選用第4個pool層的輸出作為提取出來的特征圖,這樣相比于原圖就縮小了16倍,即下采樣倍數是16。

為了友善了解,假設輸入圖像的次元為3×600×800,那麼經改特征提取網絡得到的特征圖大小就512×37×50。

Anchor的生成

Anchor(錨框)

了解什麼是Anchor對了解RPN和整個Faster RCNN都十分重要。

Anchor本質上是在原圖上預先定義好(這個預先定義十分關鍵)的一系列大小不一的矩形框,為什麼要引入Anchor呢?這是因為之前的目标檢測都是模型直接回歸邊框的位置,而通過引入Anchor相當于加入了強先驗資訊,然後通過錨框再去篩選與修正,最後再得到預測框。這樣做的好處在與是在Anchor的基礎上做物體檢測,這樣要比從無到有的直接拟合物體的邊框容易一些。具體的做法就是讓模型去預測Anchor與真實邊框的偏移值,而不是直接預測邊框的坐标。

Anchor有了,那怎麼将Anchor跟特征圖聯系在一起呢?具體做法是, 首先對feature map進行3×3的卷積操作, 得到的每一個點的次元是512維, 這512維的資料對應着原始圖檔上的很多不同的大小與寬高區域的特征,這些區域的中心點都相同。 如果下采樣率為預設的16, 則每一個點的坐标乘以16即可得到對應的原圖坐标。如下圖所示

Faster RCNN原理及Pytorch代碼解讀——RPN(一):Anchor的生成Anchor的生成

這樣的話,特征圖的每一個點都對應着9個錨框,是以一共有37×50×9=16650個Anchors。且由于這9個Anchors大小寬高不同, 對應到原圖基本可以覆寫所有可能出現的物體。

下面來看代碼,看完代碼就會知道生成Anchor了。

代碼在lib/modle/rpn/generate_anchors.py中,直接運作作者demo中的generate_anchors.py可以得到以下輸出,其中每一行表示錨框的左上角和右下角坐标。

Faster RCNN原理及Pytorch代碼解讀——RPN(一):Anchor的生成Anchor的生成

主要的函數為:generate_anchors函數

def generate_anchors(base_size=16, ratios=[0.5, 1, 2],
                     scales=2**np.arange(3, 6)):
    """
    Generate anchor (reference) windows by enumerating aspect ratios X
    scales wrt a reference (0, 0, 15, 15) window.
    """

    # 首先建立一個基本anchor為[0, 0, 15, 15],分别是左上角坐标和右下角坐标
    base_anchor = np.array([1, 1, base_size, base_size]) - 1
    # 将基本anchor進行寬高變化,生成三種寬高比的anchor
    ratio_anchors = _ratio_enum(base_anchor, ratios)
    # 将上述anchor再進行尺度變化,得到最終的9種anchors
    anchors = np.vstack([_scale_enum(ratio_anchors[i, :], scales)
                         for i in xrange(ratio_anchors.shape[0])])
    # 傳回對應于feature map大小的anchors
    return anchors
           

參數有三個:

1.base_size=16

這個參數指定了最初的類似感受野的區域大小,因為經過多層卷積池化之後,feature map上一點的感受野對應到原始圖像就會是一個區域,這裡預設設定為16是因為使用了VGG16作為特征提取器,前面說了其下采樣率為16,是以feature map上一點對應到原圖的大小為16x16的區域。當然也可以根據不同的特征提取網絡來設定。

2.ratios=[0.5,1,2]

這個參數指的是要将16x16的區域,按照1:2,1:1,2:1三種比例進行變換。

3.scales=2**np.arange(3, 6)

這個參數是要将輸入的區域,的寬和高進行三種倍數,分别是 2 3 = 8 2^3=8 23=8, 2 4 = 16 2^4=16 24=16, 2 5 = 32 2^5=32 25=32倍的放大,如16x16的區域變成 ( 16 ∗ 8 ) ∗ ( 16 ∗ 8 ) = 128 ∗ 128 (16*8)*(16*8)=128*128 (16∗8)∗(16∗8)=128∗128的區域 ( 16 ∗ 16 ) ∗ ( 16 ∗ 16 ) = 256 ∗ 256 (16*16)*(16*16)=256*256 (16∗16)∗(16∗16)=256∗256的區域, ( 16 ∗ 32 ) ∗ ( 16 ∗ 32 ) = 512 ∗ 512 (16*32)*(16*32)=512*512 (16∗32)∗(16∗32)=512∗512的區域。

寬高變化

def _ratio_enum(anchor, ratios):
    """
    對設定好的Anchor進行寬高變化,得到不同寬高比的Anchor
    """
	# 擷取錨框的寬、高和中心點坐标
    w, h, x_ctr, y_ctr = _whctrs(anchor)
    # 得到錨框尺寸,size為256
    size = w * h
    # 對尺寸進行變換,size_ratios為[128,256,512]
    size_ratios = size / ratios
    # 将尺寸開根号作為錨框的寬
    ws = np.round(np.sqrt(size_ratios))
    # 将寬乘ratios得到錨框的高
    hs = np.round(ws * ratios)
    # 還原為錨框的左上角和右下角坐标的表現形式
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
    return anchors
           
def _whctrs(anchor):
    """
    Return width, height, x center, and y center for an anchor (window).
    """

    w = anchor[2] - anchor[0] + 1
    h = anchor[3] - anchor[1] + 1
    x_ctr = anchor[0] + 0.5 * (w - 1)
    y_ctr = anchor[1] + 0.5 * (h - 1)
    return w, h, x_ctr, y_ctr
           
def _mkanchors(ws, hs, x_ctr, y_ctr):
    """
    Given a vector of widths (ws) and heights (hs) around a center
    (x_ctr, y_ctr), output a set of anchors (windows).
    """

    ws = ws[:, np.newaxis]
    hs = hs[:, np.newaxis]
    anchors = np.hstack((x_ctr - 0.5 * (ws - 1),
                         y_ctr - 0.5 * (hs - 1),
                         x_ctr + 0.5 * (ws - 1),
                         y_ctr + 0.5 * (hs - 1)))
    return anchors
           

尺寸變化

def _scale_enum(anchor, scales):
    """
    Enumerate a set of anchors for each scale wrt an anchor.
    """

    w, h, x_ctr, y_ctr = _whctrs(anchor)
    ws = w * scales
    hs = h * scales
    anchors = _mkanchors(ws, hs, x_ctr, y_ctr)
    return anchors
           

繼續閱讀