ssd 的anchor生成詳解


最近面試,被各種問,特別被問到一些很細節的東西我一知半解,特尷尬。遂下定決心看懂並記住每一個細節!不怕被問!
ssd的anchor是如何生成的?
首先需要了解一些參數數值的意義:

# SSD300 CONFIGS
voc = {
    'num_classes': 21,
    'lr_steps': (80000, 100000, 120000),
    'max_iter': 1200000,
    'feature_maps': [38, 19, 10, 5, 3, 1],
    'min_dim': 300,
    'steps': [8, 16, 32, 64, 100, 300],
    'min_sizes': [30, 60, 111, 162, 213, 264],
    'max_sizes': [60, 111, 162, 213, 264, 315],
    'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
    'variance': [0.1, 0.2],
    'clip': True,
    'name': 'VOC',
}

這里的min_sizes和max_sizes是根據公式生成的:

直接上anchor的生成代碼:prior_box.py

from __future__ import division
from math import sqrt as sqrt
from itertools import product as product
import torch

# SSD300 CONFIGS
# voc = {
#     'num_classes': 21,
#     'lr_steps': (80000, 100000, 120000),
#     'max_iter': 1200000,
#     'feature_maps': [38, 19, 10, 5, 3, 1],
#     'min_dim': 300,
#     'steps': [8, 16, 32, 64, 100, 300],
#     'min_sizes': [30, 60, 111, 162, 213, 264],
#     'max_sizes': [60, 111, 162, 213, 264, 315],
#     'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
#     'variance': [0.1, 0.2],
#     'clip': True,
#     'name': 'VOC',
# }


class PriorBox(object):
    """Compute priorbox coordinates in center-offset form for each source
    feature map.
    """
    def __init__(self, cfg):
        super(PriorBox, self).__init__()
        self.image_size = cfg['min_dim']
        # number of priors for feature map location (either 4 or 6)
        self.num_priors = len(cfg['aspect_ratios'])
        self.variance = cfg['variance'] or [0.1]
        self.feature_maps = cfg['feature_maps']
        self.min_sizes = cfg['min_sizes']
        self.max_sizes = cfg['max_sizes']
        self.steps = cfg['steps']
        self.aspect_ratios = cfg['aspect_ratios']
        self.clip = cfg['clip']
        self.version = cfg['name']
        for v in self.variance:
            if v <= 0:
                raise ValueError('Variances must be greater than 0')

    def forward(self):
        mean = []
        for k, f in enumerate(self.feature_maps): #[38, 19, 10, 5, 3, 1],
            for i, j in product(range(f), repeat=2):
                f_k = self.image_size / self.steps[k]
                # unit center x,y
                cx = (j + 0.5) / f_k
                cy = (i + 0.5) / f_k

                # aspect_ratio: 1
                # rel size: min_size
                s_k = self.min_sizes[k]/self.image_size
                mean += [cx, cy, s_k, s_k]

                # aspect_ratio: 1
                # rel size: sqrt(s_k * s_(k+1))
                s_k_prime = sqrt(s_k * (self.max_sizes[k]/self.image_size))
                mean += [cx, cy, s_k_prime, s_k_prime]

                # rest of aspect ratios
                for ar in self.aspect_ratios[k]:
                    mean += [cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]
                    mean += [cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]
        # back to torch land
        output = torch.Tensor(mean).view(-1, 4) ##[8732,4]
        if self.clip:
            output.clamp_(max=1, min=0)
        return output

這里注意init里面初始化的一些參數就是上面voc里面的。這里主要是用到了
'feature_maps': [38, 19, 10, 5, 3, 1], 是6個feature map的大小
'steps': [8, 16, 32, 64, 100, 300], 是下采樣的倍數
'min_sizes': [30, 60, 111, 162, 213, 264], 相對於300大小
'max_sizes': [60, 111, 162, 213, 264, 315],相對於300大小
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
'clip': True,
然后我們來解析anchor是如何生成的。
上面用product生成笛卡爾坐標,遍歷每個像素位置,也就是說為每個像素位置配上anchor。
關於product,https://www.cnblogs.com/yanghailin/p/14769384.html,可以看這里3.itertools.product
或者這里簡單介紹一下:
product(A,B)函數,返回A和B中的元素組成的笛卡爾積的元組,
itertools.product(*iterables, repeat=1)
iterables是可迭代對象,repeat指定iterable重復幾次,即:
product(A,repeat=3)等價於product(A,A,A)

import itertools
for item in itertools.product([1,2,3,4],[100,200]):
    print(item)
    '''
(1, 100)
(1, 200)
(2, 100)
(2, 200)
(3, 100)
(3, 200)
(4, 100)
(4, 200)
    '''
 for k, f in enumerate(self.feature_maps): #[38, 19, 10, 5, 3, 1],
            for i, j in product(range(f), repeat=2):

cx,cy是feature map上面的每個點,然后再配上w和h。
注意anchor的四個值都是相對值,都是0-1的小數。

mean一般而言是4個,但是aspect_ratios個數是2的話,就是6個。
這里for循環多一輪,就會多出2個,所以是6個

for ar in self.aspect_ratios[k]:
  mean += [cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]
  mean += [cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]

s_k = self.min_sizes[k]/self.image_size
s_k_prime = sqrt(s_k * (self.max_sizes[k]/self.image_size))

[cx, cy, s_k, s_k]
[cx, cy, s_k_prime, s_k_prime]
[cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]  ##ar取2或者3
[cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]

從上面計算規則來看,前面兩個是正方形,一個大的一個小的正方形。
然后后面兩個是矩形,一個長的矩形,一個寬的矩形。
當k=0,f=38的時候,featuremap的大小是38,ar只有一個2,所以是一個點對應4個anchor。畫圖如下:

然后feature map 等於38,19, 10, 5, 3, 1也是類似的,只不過
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
19, 10, 5的時候aspect_ratios個數為2,一個點就會對應6個anchor。
所以,整個6個大循環下來,得到的anchor的總的個數為:

還有一點就是
'min_sizes': [30, 60, 111, 162, 213, 264], 相對於300大小
'max_sizes': [60, 111, 162, 213, 264, 315],相對於300大小
相對於300大小,因為看到代碼里面
s_k = self.min_sizes[k]/self.image_size
都是除以的self.image_size(300)。
然后'feature_maps': [38, 19, 10, 5, 3, 1], 是6個feature map的大小
其實這里就可以看出,在feature等於38的尺寸上面,是最大的尺寸,找的最小的min_sizes是30(相對於300),所以可以看到是在淺層特征上面找小目標!

然后anchor的格式就是下面這些值。

tensor([[0.0133, 0.0133, 0.1000, 0.1000],
        [0.0133, 0.0133, 0.1414, 0.1414],
        [0.0133, 0.0133, 0.1414, 0.0707],
        ...,
        [0.5000, 0.5000, 0.9612, 0.9612],
        [0.5000, 0.5000, 1.2445, 0.6223],
        [0.5000, 0.5000, 0.6223, 1.2445]])

最后,這里詳細講解了anchor的生成方式,其實,真的,all in code。
這里再詳細不過了,要是下次再被問到,不要答不上來哦~
主要就是這4個,記住就好:

[cx, cy, s_k, s_k]
[cx, cy, s_k_prime, s_k_prime]
[cx, cy, s_k*sqrt(ar), s_k/sqrt(ar)]  ##ar取2或者3
[cx, cy, s_k/sqrt(ar), s_k*sqrt(ar)]

~~~~~2021年06月11日09:42:46 更新~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
這里有一個很重要的細節!我是看到后面計算loss的時候才回過頭來看的。因為后面看到truths直接與這里生成的anchor做相交運算。

overlaps = jaccard(
        truths,
        point_form(priors)
    )

我就在想怎么能直接做交並比呢,因為truths相對於300300的比例,難道這里的anchor也是相對於300300的嗎?
然后仔細看這里的代碼,發現一個細節!
就是

 for k, f in enumerate(self.feature_maps): #[38, 19, 10, 5, 3, 1],
            for i, j in product(range(f), repeat=2):
                f_k = self.image_size / self.steps[k]
                # unit center x,y
                cx = (j + 0.5) / f_k
                cy = (i + 0.5) / f_k

                # aspect_ratio: 1
                # rel size: min_size
                s_k = self.min_sizes[k]/self.image_size
                mean += [cx, cy, s_k, s_k]

第一循環是step=8, f_k = self.image_size / self.steps[k]-->f_k = 300/8=37.5
所以可以看到cx = (j + 0.5) / f_k 和 cy = (i + 0.5) / f_k都是相對於第一層feature map大小38的。
但是看到w和h都是相對於self.image_size(300)的! s_k = self.min_sizes[k]/self.image_size。
然后整出的anchor mean += [cx, cy, s_k, s_k]可以直接和在300上面的比例直接做交並比運算。想想確實是可以的!
因為你300下采樣8倍的feature map38上面,38每移動一個點在300上面相當於移動了8個點。所以,位置是相對的,映射到300上面就是乘以倍數移動。舉例

j=3時候
cx=(j+0.5)/37.5=0.09333       0.09333*300=27.999
j=4時候
cx=(j+0.5)/37.5=0.12       0.12*300=36

可見,36-28=8,j變化了1,在300上面就變化了8,就是300在下采樣8倍38的大小上,每移動1位就相當於在300上移動了8。
所以后面把在38大小上面的比例用在300上面,就是默認這種行為(每移動1位就相當於在300上移動了8)。但是他對應的寬和高還是按照300的比例來的。這里沒有毛病。
只是位置有個倍移,寬和高還是按照300上面的比例來。
所以這里得到的anchor可以直接和groundtruth做交並比。

額外鏈接:
深入理解anchor https://blog.csdn.net/qianqing13579/article/details/82106664


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM