什么是SSD算法?

SSD(Single Shot MultiBox Detector)是一种实时对象检测算法。

它的主要特点是:
使用一个卷积神经网络在一次前馈中对多尺度的特征图进行预测。
默认框定义在不同尺度和不同方块上的,然后预测这些默认框中是否包含物品及其类别。
通过匹配和非极大抑制获得最终的检测结果。

它使用了VGG-16作为骨干模型,然后在conv4_3,conv7,conv8_2,conv9_2和conv10_2这几个层的特征图上进行预测。

  • conv4_3用于检测小尺度物体,conv10_2用于检测大尺度物体。
  • 每个特征图上的预测结果都包含4个部分:是否包含物体、物体类预测、框的位置预测和框的尺度预测。

SSD使用了几个超参数来控制用于预测的特征图尺度,以及每个特征图上的默认框(也称先验框)尺度。主要有:

  • min_dim: 输入图像的最小尺寸
  • max_dim: 输入图像的最大尺寸
  • scales:用于预测的特征图的尺度,对应不同的层
  • aspect_ratios: 每个特征图上的默认框的宽高比

SSD的代码实现如下:

python
import torch
import torch.nn as nn
import torchvision.models as models
class SSD(nn.Module):
    def __init__(self, num_classes):
        super(SSD, self).__init__()
        # 获得VGG-16模型的各个部分
        self.vgg = models.vgg16(pretrained=True).features
        # 在conv4_3之后添加额外的卷积层
        self.conv4_3_norm = nn.Conv2d(512, 1024, kernel_size=3, padding=1)
        self.conv4_3_norm_relu = nn.ReLU(inplace=True)
        # 在其它层之后也添加卷积层进行预测
        self.conv7_norm = nn.Conv2d(1024,256, kernel_size=3, padding=1)
        self.conv7_norm_relu = nn.ReLU(inplace=True)
        self.conv8_2_norm = nn.Conv2d(512,256, kernel_size=3, padding=1)
        self.conv8_2_norm_relu = nn.ReLU(inplace=True)
        self.conv9_2_norm  = nn.Conv2d(256,256, kernel_size=3, padding=1)
        self.conv9_2_norm_relu = nn.ReLU(inplace=True)
        self.conv10_2_norm = nn.Conv2d(256,256, kernel_size=3, padding=1)
        self.conv10_2_norm_relu = nn.ReLU(inplace=True)
        # 分类器和边界框回归器
        self.num_classes = num_classes
        self.loc_preds = nn.ModuleList()
        self.cl_preds = nn.ModuleList()
    def forward(self, x):
        x = self.vgg(x)
        # conv4_3预测小物体
        conv4_3_norm = self.conv4_3_norm(x)
        conv4_3_norm_relu = self.conv4_3_norm_relu(conv4_3_norm)
        loc_pred_conv4_3, cl_pred_conv4_3 = self.get_preds(conv4_3_norm_relu, 4)
        # conv7预测中等大小物体
        conv7_norm = self.conv7_norm(x)
        conv7_norm_relu = self.conv7_norm_relu(conv7_norm)
        loc_pred_conv7, cl_pred_conv7 = self.get_preds(conv7_norm_relu, 6)
        # conv8_2, conv9_2, conv10_2 一一对应的预测
        loc_pred_conv8_2, cl_pred_conv8_2 = self.get_preds(self.conv8_2_norm_relu 



# conv10_2预测大物体
        conv10_2_norm = self.conv10_2_norm(x)
        conv10_2_norm_relu = self.conv10_2_norm_relu(conv10_2_norm)
        loc_pred_conv10_2, cl_pred_conv10_2 = self.get_preds(conv10_2_norm_relu, 6)

        # 合并各个层的预测结果
        loc_preds = torch.cat([loc_pred_conv4_3, loc_pred_conv7, 
                               loc_pred_conv8_2, loc_pred_conv9_2, 
                               loc_pred_conv10_2], 1)
        cl_preds = torch.cat([cl_pred_conv4_3, cl_pred_conv7, 
                              cl_pred_conv8_2, cl_pred_conv9_2,
                              cl_pred_conv10_2], 1)

        return loc_preds, cl_preds

get_preds方法用于在每个特征图上得到定位和分类的预测结果:
python
def get_preds(self, x, num_anchors):
    batch_size = x.size(0)
    # 创建预测结果的初始化梯度
    loc_preds = torch.zeros(batch_size, num_anchors * 4, 
                           x.size(2), x.size(3))  
    cl_preds = torch.zeros(batch_size, num_anchors * self.num_classes, 
                           x.size(2), x.size(3))

    for ndx in range(num_anchors):
        loc_preds[..., ndx * 4:(ndx + 1) * 4] = self.loc_preds[ndx](x)
        cl_preds[..., ndx * self.num_classes : (ndx + 1) * self.num_classes] = self.cl_preds[ndx](x)

    return loc_preds, cl_preds

在forward方法中,SSD利用不同尺度的特征图获得检测框和类预测,这些结果被拼接成最终的预测结果。get_preds方法在每个特征图上为不同尺度的先验框获得定位和分类预测。

SSD通过一张输入图像的一次前向传播就完成了检测预测,这也是其名称的由来。但它也面临匹配先验框与真实框的难度,以及特征融合的挑战。理解SSD的结构与实现可以帮助我们设计高效的检测模型。

SSD属于一类以检测为目标的单阶段检测方法,其与Faster R-CNN等两阶段方法相比,存在速度和精度的权衡。