当前位置:   article > 正文

SSD: Single Shot MultiBox Detection 自己的一些理解

multibox detection

SSD: Single Shot MultiBox Detection

论文

翻译

Pytorch 代码

根据这个人的文章,可以把 SSD 代码跑起来:https://zhuanlan.zhihu.com/p/92154612

https://blog.csdn.net/qq_30815237/article/details/90292639

SSD算法是一种直接预测目标类别和bounding box的多目标检测算法。针对不同大小的目标检测,传统的做法是先将图像转换成不同大小(图像金字塔),然后分别检测,最后将结果综合起来(NMS)。SSD算法则利用不同卷积层的 feature map 进行综合也能达到同样的效果。算法的主网络结构是VGG16,将最后两个全连接层改成卷积层,并随后增加了4个卷积层来构造网络结构。对其中5种不同的卷积层的输出feature map分别用两个不同的 3×3 的卷积核进行卷积,一个输出分类用的confidence,每个default box 生成21个类别confidence;一个输出回归用的 localization,每个 default box 生成4个坐标值(x, y, w, h)。此外,这5个feature map还经过 PriorBox 层生成 prior box(生成的是坐标)。上述5个feature map中每一层的default box的数量是给定的(8732个)。最后将前面三个计算结果分别合并然后传给loss层。

负样本获得(这是一个难例挖掘的过程) 在目标检测中我们会事先标记好ground_truth,接下来在图片中随机提取一系列sample,与ground_truth重叠率IoU超过一定阈值的(比如0.5),则认为它是positive sample,否则为negative sample,考虑到实际负样本数>>正样本数,我们为了避免network的预测值少数服从多数而向负样本靠拢,取正样本数:负样本数大约为1:3,显而易见,用来训练网络的负样本为提取的负样本的子集,那么,我们当然选择负样本中容易被分错类的困难负样本来进行网络训练。

困难负样本是指容易被网络预测为正样本的proposal,即假阳性(false positive),如roi里有二分之一个目标时,虽然它仍是负样本,却容易被判断为正样本,这块roi即为hard negative,训练hard negative对提升网络的分类性能具有极大帮助,因为它相当于一个错题集。

如何判断它为困难负样本呢?也很简单,我们先用初始样本集(即第一帧随机选择的正负样本)去训练网络,再用训练好的网络去预测负样本集中剩余的负样本,选择其中得分最高,即最容易被判断为正样本的负样本为困难样本,加入负样本集中,重新训练网络,循环往复,然后我们会发现:我们的网络的分类性能越来越强了。


自己理解:

这里的 defalut box 指的是feature map上,每个小cell(即一个小格子)都有不同宽高比的defalut box,如下图所示,(b ) 8x8 与 (c) 4x4 有四个 defalut box。

ssd,就是用卷积来达到预测的结果。

  1. [nn.Conv2d(v.out_channels, cfg[k] * 4, kernel_size=3, padding=1)] 这里为啥要乘 4
  2.  我们可以在模型图中观察到在conv4_3层,有一层Classifier层,使用一层(3,3,(4*(Classes+4)))
  3. 卷积进行卷积(Classes是识别的物体的种类数,代表的是每一个物体的得分,4为x,y,w,h坐标,
  4. 乘号前边的4为default box的数量)
  5. reference: https://www.cnblogs.com/cmai/p/10076050.html

对这张图中,有些卷积下有两个卷积,比如下图框出:

看了这个参考 reference: https://blog.csdn.net/qq_17232031/article/details/90438676 知道了,是进行两次卷积,比如 Conv8_2 图下有两个 Conv: 1x1x256, Conv: 3x3x512-s2,则代表 第一个卷积输入通道 1024,输出256,第二个卷积输入通道 256,输出通道 512, 其中s2代表stride=2,不写则为1。

  1.  extras = {    '300': [256, 'S', 512, 128, 'S', 256, 128, 256, 128, 256],    '512': [],}
  2. # 这一块其实就是在构建除了 FC6, FC7后面 Conv8_2,Conv9_2,Conv10_2,Conv11_2的卷积。

  1.  def multibox(vgg, extra_layers, cfg, num_classes):
  2.      loc_layers = []
  3.      conf_layers = []
  4.      vgg_source = [21, -2]
  5.      for k, v in enumerate(vgg_source):
  6.          loc_layers += [nn.Conv2d(vgg[v].out_channels,
  7.                                   cfg[k] * 4, kernel_size=3, padding=1)]
  8.          conf_layers += [nn.Conv2d(vgg[v].out_channels,
  9.                          cfg[k] * num_classes, kernel_size=3, padding=1)]
  10.      for k, v in enumerate(extra_layers[1::2], 2):
  11.          loc_layers += [nn.Conv2d(v.out_channels, cfg[k]
  12.                                   * 4, kernel_size=3, padding=1)]
  13.          conf_layers += [nn.Conv2d(v.out_channels, cfg[k]
  14.                                    * num_classes, kernel_size=3, padding=1)]
  15.      return vgg, extra_layers, (loc_layers, conf_layers
 

上面右上角网址为:https://blog.csdn.net/qq_17232031/article/details/90438676

min_sizes,max_sizes 是指什么,每个default box的大小?怎么确定的?

通过参考了这篇文章:https://blog.csdn.net/gbyy42299/article/details/81235891 知道了,min_size, max_sizes 指的是default box的边长,即最小边长和最大边长。

怎么确定的? prior boxes 怎么生成的?

论文作者分了一个区域,[0.2,0.9]进行等分,且有一个step,这个step怎么计算的,是通过如下:

  1.  mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'conv9_2']
  2.  min_ratio = 20 ####这里即是论文中所说的Smin=0.2,Smax=0.9的初始值,
  3.  max_ratio = 90
  4.  #math.floor()函数表示:求一个最接近它的整数,它的值小于或等于这个浮点数。
  5.  step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))
  6.  # step = [20, 37, 54, 71, 88, 105]
  7.  # 这里算下来是17.5,由于floor,所以取17
  8.  # 如果按 09-0.2算,取 0.17,即 ratio = [0.2, 0.37, 0.54, 0.71, 0.88, 1.05]

这样,图像的大小为300,所以 300 * ratio就得到每个默认的defalut的边长,然后每个defalut边长有一个最大的边长 Sk+1=sqrt(Smin*Smax) = sqrt(30x60)(这里Smin=30, Smax=60),别的就按宽高比来计算了。

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

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

在开源的代码https://github.com/amdegroot/ssd.pytorch中,作者是直接把这些值写道配置文件中,这样做个人感觉不好,让读者不知道怎么算的,有点懵。

另外,论文作者说是从0.2到0.9等分,但是这里还有个0.1的,以及1.05的,这就。。。


作者这段讲的很好,就是默认宽高:

比如Conv4_3,Smin_size = 30,

Sk+1 = sqrt(min_sizexmax_size) = sprt(30*60)=42.42,

这个宽高比为2,所以 :

w/h = ratio=2,

Smin_sizexSmin_size = w*h = h x h x ratio => h = Smin_size / sqrt(ratio)

w = Smin_size x sqrt(2)

然后宽高是2,那也有对应的矩形,就宽高为 1/2,就是矩形旋转90度就可以了,这样就是两个矩形,所以有4个,如果还有一个ratio,那么矩形就有6个。这还是很好理解,但是不理解的就是,在一个点算默认的4个或者6个矩形时,都是用 Smin_size进行计算的,不是很懂。

原文链接:https://blog.csdn.net/gbyy42299/article/details/81235891


困惑:

1.每一层卷积输出都是有很多channel,比如下图,38X38x512,为啥最终算这一层个数为 38x38x4=5776,这个只是算了一个channel,还是最终结果就是一个channel,那只有一个channel,那在哪里进行把多个channel转为一个channel?

答:因为每个3x3的卷积通道数与作为feature map层的通道数一样,这样算下来结果就只有一个通道数了。

对于代码中的encode,其实原理和faster-rcnn一样,如下图,只不过不要anchor,而是直接让预测和ground truth比较:

 

# ensure every gt matches with its prior of max overlap

for j in range(best_prior_idx.size(0)):    
        best_truth_idx[best_prior_idx[j]] = j

虽然作者注释了,但是我还是没看懂,为啥要把best_truth_idx的值改为对应的 0-size(0)
 本来是不理解上面的,但是看了下面图就理解了,因为作者返回的是[obj_num, prior_num],因为是按dim=1(行)best_prior_idx取的,就是每行最大值,每一行对应一个obj,所以可以赋值0-best_prior_idx.size(0)
 而且这个附近的代码,作者把gt与prior结合的很好,很巧妙。

best_truth_overlap.index_fill_(0, best_prior_idx, 2) 这个地方,index_fill_是按照指定的dim进行对应的idx位置处的值替换为2,这里dim=0,best_prior_idx的size为1,默认只能传dim=[-1,0]这两个值,貌似-1与0是等价效果,总感觉自己理解的不是很透彻,有点模糊。

  1.  def encode(matched, priors, variances):
  2.      """Encode the variances from the priorbox layers into the ground truth boxes
  3.     we have matched (based on jaccard overlap) with the prior boxes.
  4.     Args:
  5.         matched: (tensor) Coords of ground truth for each prior in point-form
  6.             Shape: [num_priors, 4].
  7.         priors: (tensor) Prior boxes in center-offset form
  8.             Shape: [num_priors,4].
  9.         variances: (list[float]) Variances of priorboxes
  10.     Return:
  11.         encoded boxes (tensor), Shape: [num_priors, 4]
  12.     """
  13.  ​
  14.      # dist b/t match center and prior's center
  15.      g_cxcy = (matched[:, :2] + matched[:, 2:])/2 - priors[:, :2]
  16.      # encode variance
  17.      g_cxcy /= (variances[0] * priors[:, 2:])
  18.      # match wh / prior wh
  19.      g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:]
  20.      g_wh = torch.log(g_wh) / variances[1]
  21.      # return target for smooth_l1_loss
  22.      return torch.cat([g_cxcy, g_wh], 1)  # [num_priors,4]
  23.  上面是算的平滑L1损失公式,从下图可以看出,平滑L1损失算的还是预测的与(ground truth与default prior box的offset)之间的差距,差距越小,则
  24.  不是很理解,作者这里加的 variances 到底用来干嘛!!!!!!!!!!!!!!!!!!!!!!!!!!

SSD作者也是用prior default box与 gt 的 jaccard(交并比),找到与每个gt最匹配的prior box,以及每个prior box与哪个gt最匹配,然后根据每个prior box对应哪个gt,然后把对应的gt与prior box求offset,这样求出后是所有的prior default box与对应jaccard最大的gt,接着根据jaccard大于设定阈值的作为pos,把这些pos对应的offset找到,用于后面跟预测出的loc进行求 Smooth Loss。总的来说,8732个prior default box不是所有的都拿来求loss,而是这8732个与gt最匹配的几个进行参与求loss。

参考:https://zhuanlan.zhihu.com/p/338498464

首先,softmax定义如下,就是每个类型的预测值进行 e^x_i 计算,然后除以总的e^x_i(i=1,2,3...n),如下图,图片来源之:https://www.zhihu.com/question/23765351

也就是说,上述就是softmax公式,然后对softmax进行log就算,就是:log(softmax),如下图

图片来自:https://zhuanlan.zhihu.com/p/79854543

所以,最后公式为:

但是最后的公式我不认同作者,感觉他少了个log

1.先把所有conf进行logsoftmax进行处理 置信度误差越大,实际上就是预测背景的置信度越小。 这句话应该是对负样本而已,负样本就是背景,之前很难理解这句话,因为如果是正样本,那误差越大,则背景的置信度越大,后来发现,这个是对负样本而言,负样本误差越大,则预测背景置信度越小,如:背景概率为1,但是预测的误差0.9,那预测背景的置信度为0.1,这就对应了。

而且也很难理解代码中为啥要有 loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1))这一块,其实这个就是对应原文中,"Instead ofusing all the negative examples, we sort them using the highest confidence loss for each default box and pick the top ones so that the ratio between the negatives and positives isat most 3:1." 仔细从 we sort them ...开始看,因为真的很巧妙,"我们对他们排序,对什么排序?对default box进行highest confidence loss计算后的结果进行排序",也就是说,不是对default box进行置信度排序,而是对这些default box先参与highest confidence loss计算,对计算后的结果进行排序。 所以,代码中:loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1))就是对应 highest confidence loss

以上内容是参考https://zhuanlan.zhihu.com/p/142630197而总结出,这个博主讲的比较好,赞!!!

困惑

但是这个两个sort我是没弄明白,因为第一次排序就可以得到最大的值,而且作者肯定也知道,但是为啥还要两次,没理解。。。

  1. # 两次sort排序,能够得到每个元素在降序排列中的位置idx_rank
  2.  _, loss_idx = loss_c.sort(1, descending=True)
  3.  _, idx_rank = loss_idx.sort(1)

参考:

https://github.com/amdegroot/ssd.pytorch github

https://blog.csdn.net/wfei101/article/details/79888594 论文翻译的比较好,还有自己理解,颜色分明

https://www.cnblogs.com/king-lps/p/8981222.html 讲回归encode部分

https://blog.csdn.net/qq_30815237/article/details/90292639 讲解的很详细

https://blog.csdn.net/Gentleman_Qin/article/details/84403313

https://blog.csdn.net/zjc910997316/article/details/94898369 是一个告诉哪些博客讲解比较好的

https://blog.csdn.net/qq_17232031/article/details/90438676 backbone各个层,以及哪一层输出,用代码写的,一目了然,很清晰

https://www.cnblogs.com/cmai/p/10080005.html 还可以

https://blog.csdn.net/dcrmg/article/details/79254654 主要看vgg网络结构图

https://blog.csdn.net/h__ang/article/details/90316220 这个网址 SSD 网络层画的比较好

https://zhuanlan.zhihu.com/p/79854543 分析的比较全面

https://blog.csdn.net/qq_30815237/article/details/90292639?utm_term=ssd%E7%9A%84%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0&utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2~all~sobaiduweb~default-3-90292639&spm=3001.4430   他画的图比较好

https://zhuanlan.zhihu.com/p/79854543   这个人讲解的也很详细

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/291173
推荐阅读
相关标签
  

闽ICP备14008679号