1.先验框生成
Github源码:

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

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):
            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  (小框1)
                # rel size: min_size
                s_k = self.min_sizes[k]/self.image_size
                mean += [cx, cy, s_k, s_k]

                # aspect_ratio: 1  (大框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)
        if self.clip:
            output.clamp_(max=1, min=0)
        return output

源码中的几个参数取值先列举下:

min_dim = 300
feature_maps = [38, 19, 10, 5, 3, 1]
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]]

那么相应feature map上的不同大小的先验框是如何产生的呢?下面做进一步分析:
中心点坐标(cx,cy)产生(归一化):

for i, j in product(range(f), repeat=2):
	……

这段代码显然是遍历了一个feature map中所有像素点。先验框中心点计算:

cx = (j + 0.5) / f_k
cy = (i + 0.5) / f_k

pytorch构建图像vgg16源码_目标识别

以3x3大小的feature map为例,很容易看出中心点是怎么计算的,每个点的坐标都除以feature map的大小即可得到归一化后的结果。

接下来说一下min_sizes的计算:

原文中说到:The scale of the default boxes for each feature map is computed as

pytorch构建图像vgg16源码_目标识别_02


翻译过来就是对于每一个feature map其默认框的尺度按照上面的那个公式计算。那么这个尺度究竟是什么呢?它并不是在每个feature map上的先验框的大小,而是feature map上的先验框映射到原图上的检测框的大小,所以对于同一个feature map不管是什么样的aspect ratio的先验框映射到原图都是大小相同的正方形检测框。

Smin=0.2,Smax=0.9,m指的是feature map的个数,但是m=5 (conv7, conv8_2, conv9_2, conv10_2, conv11_2),其中conv4_3单独设置,其Sk为S0=Smin/2=0.1,由于Sk只是一个映射比例,所以要乘以原图的大小即300才能得到映射尺度。所以conv4_3的映射尺度即为0.1x300=30。为了计算简便,对尺度比例扩大100倍计算,最后再除以100:

首先计算增长步长:

pytorch构建图像vgg16源码_目标识别_03


注意结果要进行向下取整操作。之后按照Sk的公式即可得到:

pytorch构建图像vgg16源码_SSD_04


这还没完,毕竟我们是扩大100倍进行计算的,最终的映射尺度还需要除以100再乘以300,得到的Sk如下:

sk = [30, 60, 111, 162, 213, 264]

所以conv4_3每个先验框映射到原图中为30x30大小的检测框,conv7每个先验框映射到原图中为60x60大小的检测框,conv8_2为111x111大小的检测框,conv9_2为162x162大小的检测框……

下面看一下aspect ratios,先验框的宽高比例实际上有6个值:

{1, 2, 3, 1/2, 1/3, 1'},而aspect ratio=1’的情况需要特定的映射尺度:

pytorch构建图像vgg16源码_ios_05


在源码中Sk+1即为max_sizes,Sk+1的计算可以直接套用Sk的计算公式,而S5+1=S6需要考虑虚拟的k=6来计算。

max_sizes = [60, 111, 162, 213, 264, 315]

所以每个feature map中有两个大小不同的正方形先验框。每个先验框的宽和高的计算公式如下:

pytorch构建图像vgg16源码_SSD_06


二者相乘为Sk的平方,即为先验框映射到原图中的检测框的面积(ar指的是集合aspect ratios中的元素)。这里要注意,conv4_3、conv10_2和conv11_2不包括宽高比为3和1/3的先验框,源码中代码写的非常巧妙:

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

其中aspect_ratios=[[2], [2, 3], [2, 3], [2, 3], [2], [2]].

aspect_ratio=1和1’是所有feature map共有的宽高比,并且前面代码已全部实现,所以aspect_ratios只需要有2和3就可以,而conv4_3、conv10_2和conv11_2只会索引aspect_ratios中的[2]并不会索引其他元素,并且有以下关系成立:

pytorch构建图像vgg16源码_目标识别_07


同理3和1/3的情况,所以aspect_ratios只有2和3足够。