Single Shot MultiBox Detector

1. 算法原理

  1. 网络结构
      SSD的输入图像尺寸是 300 × 300 300 \times 300 300×300像素。检测部分使用6个卷积块从主干网络提取的特征中检测6个尺度的8732个默认框内是否存在物体。

  2. 默认框
      默认框只是8732个坐标,格式是[cx, cy, w, h]。这些坐标是归一化坐标,要乘图像边长300才是像素坐标。
      默认框的产生:把 300 × 300 300 \times 300 300×300像素的平面划分为 n × n n \times n n×n l × l l \times l l×l的栅格。以每个栅格的中心为中心产生 x x x个默认框,于是这种划分产生 n × n × x n \times n \times x n×n×x个默认框。 n n n取(38, 19, 10, 5, 3, 1), l l l取(8, 16, 30, 60, 100, 300), x x x取(4, 6, 6, 6, 4, 4),这6种划分共产生6个尺度的8732个默认框。

  3. 识别和定位
      6个卷积块的输出分别输入一个分类器、一个定位器来识别和定位物体,如图2-1。这两个输出就是网络的输出,是对8732个默认框内物体的位置和类别的预测结果。
      定位器的输出特征维度是 ( n , n , 4 x ) (n, n, 4x) (n,n,4x),分类器的输出特征维度是 ( n , n , 21 x ) (n, n, 21x) (n,n,21x)
       n × n n \times n n×n:每个输出像素对应一个栅格。
       4 x 4x 4x:每个输出像素的通道数代表对该栅格中 x x x个默认框内物体的位置检测结果( 4 x 4x 4x个坐标)。
       21 x 21x 21x:每个输出像素的通道数代表对该栅格中 x x x个默认框内物体的类别分类得分( 21 x 21x 21x个得分)。

  4. 样本
      样本:网络输出8732个位置预测和8732个类别预测。
      正样本:和标注框交并比大于0.5的样本是正样本,假设有n个。
      负样本:计算所有样本的分类loss,正样本的分类loss置零(为了排除正样本)。选择分类loss最大的前3n个样本作为负样本。

  5. 损失函数
      分类损失:用正负样本计算交叉熵。
      定位损失:计算正样本和它匹配最好的标注框的L1损失。

2. 网络结构

2.1 主干网络

  SSD的主干网络(图2.1的第1行)基于VGG-16,并做了如下改进:

  • 使用VGG-16的5个卷积块,包括ReLU和最大池化,其中最大池化略有改动。图1.1中的Conv4_3代表第4个卷积块。
  • 添加第6个卷积块,包含1个空洞卷积和1个 1 × 1 1\times1 1×1的卷积。图1.1中的fc7指的就是第6个卷积块:
    代码段-1:
    fc7_1 = nn.Conv2d(512, 1024, kernel_size=3, padding=6, dilation=6)
    fc7_2 = nn.Conv2d(1024, 1024, kernel_size=1) 	
    
  • 继续添加4个卷积块,每块含两层卷积,如图1.1中的Conv6、Conv7、Conv8、Conv9。

SSD网络结构 图 2.1 − 网 络 结 构 图2.1-网络结构 2.1

  我们把conv4_3(卷积块4)、fc7(卷积块6)、conv6_2(卷积块7)、conv7_2(卷积块8)、conv8_2(卷积块9)、conv9_2(卷积块10)这6层输出的特征称为有效特征,输出的层称为6个有效特征层,通道数分别是512、1024、512、256、256、256。网络输入的图像边长是300像素:

  1. 前4个卷积块后的最大池化步长都是2,但有效特征1取自第4个卷积块后、第4个最大池化层前,所以有效特征1的边长是 38 38 38
  2. 第4个卷积块后的最大池化层步长为2。卷积块5后的最大池化步长是1。卷积块6第一个空洞卷积相当于卷积块边长为13、填充为 ⌊ 13 / 2 ⌋ \lfloor{13/2}\rfloor 13/2、步长为1的卷积,第二个卷积核边长和步长都为1。所以有效特征2的边长是 19 19 19
  3. 卷积块7、卷积块8的卷积核边长为3,步长为2填充为1,所以有效特征3的边长是 10 10 10有效特征4的边长是 5 5 5
  4. 卷积块9、卷积块10的卷积核边长为3,步长为1不填充,所以有效特征5的边长是 3 3 3有效特征6的边长是 1 1 1

2.2 loc卷积和conf卷积

  主干网络的6层有效特征将被分别输入到loc卷积和conf卷积以定位和识别目标的位置和类别,如图2.1中向下的箭头。这两个卷积是步长为1的卷积,用于改变通道数。
代码段-2:

mbox = [4, 6, 6, 6, 4, 4]
loc_layers += [nn.Conv2d(vgg[v].out_channels, mbox[k] * 4, kernel_size=3, padding=1)]
conf_layers += [nn.Conv2d(vgg[v].out_channels, mbox[k] * num_classes, kernel_size=3, padding=1)]

  loc.shape = (6,)。loc[i].shape = [batch_size, 横向栅格数, 纵向栅格数, 4*mbox[i]]。

  忽略batch_size维度。把[横向栅格数, 纵向栅格数, 4*mbox[i]]看成多通道二维图像[w, h, 4c]的形式。含义就是:每个像素点的维度是4c(16或24),代表该栅格上4个或6个默认框的坐标。也就是说网络输出的一个像素点代表一个栅格对应的4个或6个默认框的坐标。
  栅格大小和个数如表2.1和图3.1所示。

有效特征mbox[k]loc卷积输出( w w w, h h h, c l c_l cl)conf卷积输出( w w w, h h h, c c c_c cc)原图被划分的栅格(感受野)数原图上的栅格(感受野)尺寸
conv4_3(38,38,512)4(38,38,16)(38,38,84) 38 × 38 38\times38 38×38 8 × 8 8\times8 8×8
fc7(19,19,1024)6(19,19,24)(19,19,126) 19 × 19 19\times19 19×19 16 × 16 16\times16 16×16
conv6_2(10,10,512)6(10,10,24)(10,10,126) 10 × 10 10\times10 10×10 30 × 30 30\times30 30×30
conv7_2(5,5,256)6(5,5,24)(5,5,126) 5 × 5 5\times5 5×5 60 × 60 60\times60 60×60
conv8_2(3,3,256)4(3,3,16)(3,3,84) 3 × 3 3\times3 3×3 100 × 100 100\times100 100×100
conv9_2(1,1,256)4(1,1,16)(1,1,84) 1 × 1 1\times1 1×1 300 × 300 300\times300 300×300

表 − 2.1 表-2.1 2.1

  最终loc卷积的结果被resize成[batch_size, 8732, 4],conf卷积的结果被resize成[batch_size, 8732, 21]。即8732个默认框的坐标、8732个默认框内目标的类别得分。
代码段-3:

# 有效特征将被分别输入到loc卷积和conf卷积
for (x, l, c) in zip(sources, self.loc, self.conf): # 它们仨的shape都是(6,)
	loc.append(l(x).permute(0, 2, 3, 1).contiguous())
	conf.append(c(x).permute(0, 2, 3, 1).contiguous())
# resize
loc = torch.cat([o.view(o.size(0), -1) for o in loc], 1)
conf = torch.cat([o.view(o.size(0), -1) for o in conf], 1)
loc = loc.view(loc.size(0), -1, 4)
conf = conf.view(conf.size(0), -1, Config['num_classes'])

3. 栅格

  SSD算法的思路是把原图划分成若干栅格,如下图中的(b)所示,黑色实线把原图划分成 8 × 8 8\times8 8×8个栅格。然后以每个栅格为中心,产生不同尺度和不同纵横比的默认框(原文:default boxes with different scales and aspect ratios),默认框就是图3.1(b)中的虚线框。
  划分栅格: 根据卷积和感受野的概念,后面特征图上的一个像素是由前面(或更前面)的特征图做为感受野被卷积核卷积得到的。所以不同层特征图上的像素可以看成原图被划分成不同大小的栅格后,从栅格上提取的信息。
栅格和默认框 图 − 3.1 图-3.1 3.1

4. 默认框

4.1 默认框的大小和位置

默认框的产生:

  默认框如图3.1(b)中的虚线框,是假设的目标边界框位置。每个栅格对应4或6个默认框,栅格和默认框的中心是重合的。
代码段-4:

image_size = "原图边长"
# min_sizes对应边长为[8, 16, 30, 60, 100, 300]的栅格
min_sizes = [30, 60, 111, 162, 213, 264]
max_sizes = [60, 111, 162, 213, 264, 315]

  关于min_sizes和max_sizes是怎么确定的,作者在论文中的计算公式是 s k = s m i n + s m a x − s m i n m − 1 × ( k − 1 ) ,   k ∈ [ 1 , m ] s_{k} = s_{min} + \frac{s_{max} - s_{min}}{m-1} \times (k-1), \ k \in [1,m] sk=smin+m1smaxsmin×(k1), k[1,m],但实际代码并不是这样写的,反正这些都是固定值,这里不细讲。以每个栅格中心为中心生成4或6个默认框的尺寸是:

  1. 一个小正方形,边长为 m i n _ s i z e s [ k ] i m a g e _ s i z e \frac{min\_sizes[k]}{image\_size } image_sizemin_sizes[k]
  2. 一个大正方形,边长为 m i n _ s i z e s [ k ] × m a x _ s i z e s [ k ] i m a g e _ s i z e \frac{\sqrt{min\_sizes[k] \times max\_sizes[k]}}{image\_size } image_sizemin_sizes[k]×max_sizes[k]
  3. 一个横置长方形,边长为 m i n _ s i z e s [ k ] i m a g e _ s i z e × 2 \frac{min\_sizes[k]}{image\_size } \times \sqrt{2} image_sizemin_sizes[k]×2 m i n _ s i z e s [ k ] i m a g e _ s i z e × 1 2 \frac{min\_sizes[k]}{image\_size } \times \frac{1}{\sqrt{2}} image_sizemin_sizes[k]×2 1
  4. 一个纵置长方形,边长为 m i n _ s i z e s [ k ] i m a g e _ s i z e × 1 2 \frac{min\_sizes[k]}{image\_size } \times \frac{1}{\sqrt{2}} image_sizemin_sizes[k]×2 1 m i n _ s i z e s [ k ] i m a g e _ s i z e × 2 \frac{min\_sizes[k]}{image\_size } \times \sqrt{2} image_sizemin_sizes[k]×2
  5. 一个横置长方形,边长为 m i n _ s i z e s [ k ] i m a g e _ s i z e × 3 \frac{min\_sizes[k]}{image\_size } \times \sqrt{3} image_sizemin_sizes[k]×3 m i n _ s i z e s [ k ] i m a g e _ s i z e × 1 3 \frac{min\_sizes[k]}{image\_size } \times \frac{1}{\sqrt{3}} image_sizemin_sizes[k]×3 1
  6. 一个纵置长方形,边长为 m i n _ s i z e s [ k ] i m a g e _ s i z e × 1 3 \frac{min\_sizes[k]}{image\_size } \times \frac{1}{\sqrt{3}} image_sizemin_sizes[k]×3 1 m i n _ s i z e s [ k ] i m a g e _ s i z e × 3 \frac{min\_sizes[k]}{image\_size } \times \sqrt{3} image_sizemin_sizes[k]×3

  分母是图像边长,所以默认框的坐标是归一化的坐标,要乘图像边长才是像素坐标。这样产生的默认款尺寸是:

栅格边长8163060100300
小正方形30x3060x60110x110162x162213x213264x264
大正方形42x4282x82134x134186x186237x237288x288
横置长方形121x4242x8578x157115x229151x301187x373
纵置长方形142x2185x42157x78229x115301x151373x187
横置长方形235x10464x19294x281
纵置长方形2104x35192x64281x94

4.2 默认框的匹配情况

  在6个有效特征层上划分栅格和默认框是为了检测不同大小的目标:在有效特征1上检测最小目标;在有效特征6上检测最大目标。一张图上往往只有个别目标,SSD论文共产生8732个默认框,所以计算出每个默认框与标注框的匹配情况,才知道哪些默认框更有价值。

1. 计算交并比

  计算8732个默认框与n个标注框的交并比:
代码段-5:

def jaccard(box_a, box_b):
    inter = intersect(box_a, box_b)
    # 计算默认框和标注框各自的面积
    area_a = ((box_a[:, 2] - box_a[:, 0]) * (box_a[:, 3] - box_a[:, 1])).unsqueeze(1).expand_as(inter)
    area_b = ((box_b[:, 2] - box_b[:, 0]) * (box_b[:, 3] - box_b[:, 1])).unsqueeze(0).expand_as(inter)
    union = area_a + area_b - inter
    return inter / union

  结果如下。其中ri_j的含义是第i个标注框与第j个默认框的重合程度,即面积交并比。

    overlaps = [[r1_1, r1_2, ..., r1_8732],
                [r2_1, r2_2, ..., r2_8732],
                              ...
                [rn_1, rn_2, ..., rn_8732]]

2. 最好标注框和类别

  默认框匹配是为了找到每个默认框的最好标注框和这个最好标注框内目标的类别。
代码段-6:

def match(idx, gtLocation, gtCategory, defaults, bestGtLocationBS, bestGtCategoryBS, threshold=0.5, variances=[0.1, 0.2]):
    overlaps = jaccard(gtLocation, point_form(defaults))
    # 标注框的最好默认框
    _, bestDefaultIdx_of_gt = overlaps.max(1, keepdim=True)
    bestDefaultIdx_of_gt.squeeze_(1)
    # 默认框的最好重合度和最好标注框
    bestOverlap_of_default, bestGtIdx_of_default = overlaps.max(0, keepdim=True)
    bestOverlap_of_default.squeeze_(0)
    bestGtIdx_of_default.squeeze_(0)
    # 重点默认框(该默认框是某个标注框的最好默认框)
    # 1.为重点默认框设置最大的最好重合度(2)
    bestOverlap_of_default.index_fill_(0, bestDefaultIdx_of_gt, 2)
    # 2.为重点默认框分配合适的标注框
    for j in range(bestDefaultIdx_of_gt.size(0)):
        bestGtIdx_of_default[bestDefaultIdx_of_gt[j]] = j
    # 默认框的最好标注框坐标
    bestGtLocation_of_default = gtLocation[bestGtIdx_of_default]
    bestGtLocation_of_default = encode(bestGtLocation_of_default, defaults, variances)
    # 默认框的最好标注框类别
    bestGtCategory_of_default = gtCategory[bestGtIdx_of_default] + 1
    bestGtCategory_of_default[bestOverlap_of_default < threshold] = 0
    # as one in batch_size
    bestGtLocationBS[idx] = bestGtLocation_of_default
    bestGtCategoryBS[idx] = bestGtCategory_of_default

  实际的默认框是相互重叠的,如图3.1(b),这里简化表示,如图4.1。默认框匹配的目的是找到:

  1. 默认框的最好标注框的坐标bestGtLocation_of_default,shape = [8732, 4]。
  2. 默认框的最好标注框的类别bestGtCategory_of_default,shape = [8732]。

在这里插入图片描述
图 4.1 最 好 标 注 框 图4.1最好标注框 4.1

  为了找到每个默认框的最好标注框的坐标和最好标注框的类别,先要找到每个默认框的最好标注框,可以分为下面3步。
  第1步:找标注框的最好默认框。对每个标注框,从所有默认框中找到与它交并比最大的默认框做为它的最好默认框。对应代码第4行。

  在图4.1中,标注框0的最好默认框是默认框3,标注框1的最好默认框是默认框4,标注框2的最好默认框是默认框5。即bestDefaultIdx_of_gt = [3, 4, 5]。

  第2步:找默认框的最好标注框。对每个默认框,从所有标注框中找到与它交并比最大的标注框做为它的最好标注框。对应代码第7行。

  在图-3.1中,默认框2、默认框3的最好标注框都是标注框0,默认框4、默认框5的最好标注框都是标注框2,即bestGtIdx_of_default = [0, 0, 0, 0, 2, 2, 0, 0, 1, 0, 0, 0]。
  红色数字代表的的标注框并没有意义,比如bestGtIdx_of_default[0] = 0。默认框0和所有标注框都不重合,它的最好标注框却是0。这是因为选择最好标注框时从所有标注框中选择和默认框0交并比最大的那个标注框。既然所有标注框和默认框0的交并比都是0,那么就选中了标注框0。

  第3步:如果标注框 j j j的最好默认框是默认框 i i i,那么设置默认框 i i i的最好标注框是标注框 j j j。对应代码14、15行。

  如果一个默认框 i i i是某个标注框 j j j的最好默认框,暂时把这样的默认框称为重点默认框,那么用这个默认框 i i i去回归标注框 j j j是最合适的。所以应该设置默认框 i i i的最好标注框是标注框 j j j。最终bestGtIdx_of_default = [0, 0, 0, 0, 1, 2, 0, 0, 1, 0, 0, 0]。
  本来默认框4的最好标注框是标注框2,这一步使默认框4的最好标注框是标注框1。这是因为从标注框1的角度看,只有默认框4和它重合度最好。

3. 正负样本

  正负样本指的都是默认框。正样本是那些与自己的最好标注框的交并比大于0.5的默认框,如图4.1中的默认框2、默认框8。获取负样本的方法:

  1. 通过交并比筛选出 n u m _ p o s num\_pos num_pos个正样本。
  2. 计算所有默认框的分类 l o s s loss loss
  3. 正样本的分类 l o s s loss loss置零。
  4. 分类 l o s s loss loss从大到小排序。
  5. 根据排序后的 l o s s loss loss选出前 3 n u m _ p o s 3num\_pos 3num_pos个默认框做为负样本。

  选择 l o s s loss loss大的样本目的是加快学习速度: l o s s loss loss大梯度就大。获取正负样本的其它方法

  1. 计算所有默认框的分类 l o s s loss loss并从大到小排序,选择前 64 64 64个默认框做为候选样本。
  2. 通过交并比筛选出候选正样本和候选负样本,两者和为8732。
  3. 从候选正样本中去掉不在64个候选样本中的样本,得到正样本。
  4. 从候选负样本中去掉不在64个候选样本中的样本,得到负样本。

  这种方法是为了筛选出 l o s s loss loss大的正样本。因为候选正样本远少于候选负样本,所以对 l o s s loss loss排序后得到的前 64 64 64个候选样本中的候选正样本可能极少,这就造成正样本远少于负样本。得到 n u m _ p o s num\_pos num_pos个正样本和 64 − n u m _ p o s 64-num\_pos 64num_pos个负样本。

5. 损失函数

  损失函数包含分类损失和定位损失,定义如下:
代码段-7:

def forward(self, predictions, targets):
    logicLocation, logicCategory, defaultbox = predictions
    batch_size = logicLocation.shape[0]
    num_defaultbox = (defaultbox.shape[0])  # 8732
    # 1.找出8732个默认框与它的最好标注框的坐标差,找出8732个默认框的最好标注框的类别
    bestGtLocationBS = torch.Tensor(batch_size, num_defaultbox, 4)
    bestGtCategoryBS = torch.LongTensor(batch_size, num_defaultbox)
    for idx in range(batch_size):
        gtLocation = targets[idx][:, :-1].data
        gtCategory = targets[idx][:, -1].data
        defaults = defaultbox.data
        match(idx, gtLocation, gtCategory, defaults, bestGtLocationBS, bestGtCategoryBS, self.threshold, self.variance)
    if Config['cuda']:
        bestGtLocationBS = bestGtLocationBS.cuda()
        bestGtCategoryBS = bestGtCategoryBS.cuda()
    bestGtLocationBS = Variable(bestGtLocationBS, requires_grad=False)
    bestGtCategoryBS = Variable(bestGtCategoryBS, requires_grad=False)
    # 2.标记正样本
    pos = bestGtCategoryBS > 0
    # 3.位置Loss:只计算正样本
    positiveIdx = pos.unsqueeze(pos.dim()).expand_as(logicLocation)
    positiveLoc = logicLocation[positiveIdx].view(-1, 4)
    bestGtLocationBS = bestGtLocationBS[positiveIdx].view(-1, 4)
    loss_l = F.smooth_l1_loss(positiveLoc, bestGtLocationBS, size_average=False)
    # 4.分类Loss
    loss_c = F.cross_entropy(logicCategory.view(-1, self.num_classes), bestGtCategoryBS.view(-1), reduction='none')
    loss_c = loss_c.view(batch_size, -1)
    loss_c[pos] = 0
    # 4.1获得每一张图新的softmax的结果
    _, loss_idx = loss_c.sort(1, descending=True)
    _, idx_rank = loss_idx.sort(1)
    # 4.2设置负样本数量是正样本的3倍、标记负样本
    num_pos = pos.long().sum(1, keepdim=True)
    num_neg = torch.clamp(self.negpos_ratio * num_pos, max=pos.size(1) - 1)
    neg = idx_rank < num_neg.expand_as(idx_rank)
    # 4.3计算正样本和负样本的分类loss(剩下的样本既不属于正样本也不属于负样本)
    positiveIdx = pos.unsqueeze(2).expand_as(logicCategory)
    negativeIdx = neg.unsqueeze(2).expand_as(logicCategory)
    lgCategory = logicCategory[(positiveIdx + negativeIdx).gt(0)].view(-1, self.num_classes)
    gtCategory = bestGtCategoryBS[(pos + neg).gt(0)]
    loss_c = F.cross_entropy(lgCategory, gtCategory, size_average=False)
    # 5.后处理
    N = num_pos.data.sum()
    loss_l /= N
    loss_c /= N
    return loss_l, loss_c

5.1 位置识别

  定位是对正样本 s m o o t h _ l 1 smooth\_l1 smooth_l1损失,如代码段-7第24行。

  • p o s i t i v e L o c . s h a p e = [ n u m _ p o s , 4 ] positiveLoc.shape = [num\_pos, 4] positiveLoc.shape=[num_pos,4]代表正样本位置的预测坐标。
  • b e s t G t L o c a t i o n B S . s h a p e = [ n u m _ p o s , 4 ] bestGtLocationBS.shape = [num\_pos, 4] bestGtLocationBS.shape=[num_pos,4]代表正样本位置的真实坐标。

注意: b e s t G t L o c a t i o n B S bestGtLocationBS bestGtLocationBS来自代码段-6 m a t c h match match函数的第18行。准确地说,它并不表示坐标,而是表示样本坐标与真实坐标的差。这种差通过代码段-8计算,具体内容在下一小节。

5.2 编码器

  论文中编码器的公式:
编码器代码段-8:

def encode(matched, priors, variances):
    # matched = boxes 默认框的最好标注框
    # priors = [d_cx, d_cy, d_w, d_h]
    # variances = [0.1, 0.2]
    # loc = [g_cx, g_cy, g_w, g_h]
    g_cxcy = (matched[:, :2] + matched[:, 2:])/2 - priors[:, :2]
    g_cxcy /= (variances[0] * priors[:, 2:])
    g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:]
    g_wh = torch.log(g_wh) / variances[1]
    loc = torch.cat([g_cxcy, g_wh], 1)
    return loc

e n c o d e : { l c x = 10 × g c x − d c x d w l c y = 10 × g c y − d c y d h l w = 5 × l o g ( g w d w ) l h = 5 × l o g ( g h d h ) l o c = [ l c x ,   l c y ,   l w ,   l h ] encode : \begin{cases} l_{cx} &= 10 \times \frac{g_{cx} -d_{cx}}{d_w} \\ l_{cy} &= 10 \times \frac{g_{cy} -d_{cy}}{d_h} \\ l_w &= 5 \times log(\frac{g_w}{d_w}) \\ l_h &= 5 \times log(\frac{g_h}{d_h}) \\ loc &= [l_{cx},\ l_{cy},\ l_w,\ l_h] \end{cases} encode:lcxlcylwlhloc=10×dwgcxdcx=10×dhgcydcy=5×log(dwgw)=5×log(dhgh)=[lcx, lcy, lw, lh]

5.3 分类

  分类是对正样本和负样本交叉熵损失,如代码段-7第41行。

  • l g C a t e g o r y . s h a p e = [ n u m _ p o s + n u m _ n e g , 21 ] lgCategory.shape = [num\_pos + num\_neg, 21] lgCategory.shape=[num_pos+num_neg,21]代表正负样本 i i i 的预测类别。
  • g t C a t e g o r y . s h a p e = [ n u m _ p o s + n u m _ n e g ] gtCategory.shape = [num\_pos + num\_neg] gtCategory.shape=[num_pos+num_neg]代表正负样本 i i i 的真实类别。

6. 预测

6.1 解码器

代码段-9:

def decode(loc, priors, variances):
    # loc = [l_cx, l_cy, l_w, l_h]
    # priors = [d_cx, d_cy, d_w, d_h]
    # variances = [0.1, 0.2]
    boxes = torch.cat((
        priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
        priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
    boxes[:, :2] -= boxes[:, 2:] / 2
    boxes[:, 2:] += boxes[:, :2]
    return boxes

d e c o d e : { g c x = d c x + 0.1 ⋅ l c x ⋅ d w g c y = d c y + 0.1 ⋅ l c y ⋅ d h g w = d w ⋅ e x p ( 0.2 ⋅ l w ) g h = d h ⋅ e x p ( 0.2 ⋅ l h ) b o x e s = [ g c x − g w 2 ,   g c y − g h 2 ,   g c x + g w 2 ,   g c y + g h 2 ] decode : \begin{cases} g_{cx} &= d_{cx} + 0.1 \cdot l_{cx} \cdot d_w \\ g_{cy} &= d_{cy} + 0.1 \cdot l_{cy} \cdot d_h \\ g_w &= d_{w} \cdot exp(0.2 \cdot l_{w}) \\ g_h &= d_{h} \cdot exp(0.2 \cdot l_{h}) \\ boxes &= [g_{cx} - \frac{g_w}{2},\ g_{cy} - \frac{g_h}{2},\ g_{cx} + \frac{g_w}{2},\ g_{cy} + \frac{g_h}{2}] \end{cases} decode:gcxgcygwghboxes=dcx+0.1lcxdw=dcy+0.1lcydh=dwexp(0.2lw)=dhexp(0.2lh)=[gcx2gw, gcy2gh, gcx+2gw, gcy+2gh]
  即,boxes是中心点为 ( g c x ,   g c y ) (g_{cx},\ g_{cy}) (gcx, gcy),宽为 g w g_w gw,高为 g h g_h gh的框。

7. 资源

  1. 源码:[github]
  2. [模型和数据],提取码:gr1w。

8. 参考

  1. 经典SSD算法出自ECCV2016,论文,源码基于Caffe
  2. Tensorflow版的SSD源码
  3. Bubbliiiing博主的基于Pytorch的博客讲解代码
  4. Bubbliiiing博主的基于Tensorflow和keras的博客讲解代码
  5. 正负样本选择
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值