python ssd目标检测_SSD目标检测笔记

c04b41a2058504a0b3c0c200f6519007.png

又看了一遍代码之后,SSD的思路真的是简单粗暴呀。(新增DSSD模型结构,见本文最后)

1、数据增强方式:

转换为浮点数 -> boxes要乘以原图的w和h -> 随机distort像素值 -> 随机扩充图像,扩充的部分使用均值代替-> 随机裁剪(设置gt_box与crop_rect之间的最小IoU,只保留gt_box中心落在crop_rect之间的,最后再取最大最小坐标值作为新的box值,最后减去rect左上角的坐标值) -> 水平翻转 -> boxes再除以原图w和h/归一化到01之间 -> image resize成300(boxes没有变) -> image减去像素均值

2、数据读取部分:

(1)、构建dataset部分:

修改__getitem__(self, index),找到一个annotations中的xml文件,imread对应的图片,读取target格式为[[x1, y1, x2, y2, ind], ...]其中四个box坐标都归一化到0-1之间了。根据1transform之后,img转换为rgb通道格式,转变为torch Tensor数据,交换channel顺序为channel first。

(2)、DataLoader部分:

使用了collate_fn参数,因为输入的target尺寸不一样。

collate_fn函数输入的是一个batch大小的tuple,每个tuple中包含一个大小为2的tuple,第一部分是FloatTensor[3, 300, 300]的img,第二部分是ndarrays(n_boxes, 5)。函数Function是将一个batch_size大小的imgs append到一个list中,后按照第0个维度stack起来,target就是只append到一个list中。

最后在next(iter(data_loader))中输出的images和targets分别是一个[batch_size, 3, 300, 300]大小的FloatTensor和一个长度为batch_size大小的list

最后只需要再将两部分转换成Variable(),其中targets部分设置参数volatile=True means 在BP过程中排除子图,即requires_grad=False

3、模型构建部分

参考图1网络结构图并参照我在测试的时候输出的这两个文件[网络结构、网络参数][bx2x]就可以简单明了的理解SSD的网络结构,很简单!

7c5eb2a40e967d8c054372bd0e7f458f.png
图1、SSD网络结构图

在图中6个横线的位置处,每一处接了一个3×3的卷积后输出一个loc和conf,按照[4, 6, 6, 6, 4, 4]即每个位置上预测的default box个数,它们的输出尺寸分别为:

-----multibox----
loc : torch.Size([16, 38, 38, 16])
conf: torch.Size([16, 38, 38, 84])
loc : torch.Size([16, 19, 19, 24])
conf: torch.Size([16, 19, 19, 126])
loc : torch.Size([16, 10, 10, 24])
conf: torch.Size([16, 10, 10, 126])
loc : torch.Size([16, 5, 5, 24])
conf: torch.Size([16, 5, 5, 126])
loc : torch.Size([16, 3, 3, 16])
conf: torch.Size([16, 3, 3, 84])
loc : torch.Size([16, 1, 1, 16])
conf: torch.Size([16, 1, 1, 84])

最后分别将6个loc和6个conf矩阵torch.cat起来,维度分别为[16, 8732, 4][16, 8732, 21]

第二个维度正好是生成的总的prior_box的个数,稳!

SSD的output是一个tuple包含三部分,分别是上面计算的loc和conf两个,还有一个是生成的priors的坐标。

在6个不同大小的feature map上分别生成不同大小的prior_box,每个像素点的位置上生成[cx, cy, Sk, 根号下Sk* Sk+1的正方形,Sk/根号下2或三:Sk*根号下2或3的矩形]比例,其中都除以feature map的大小,归一化到0-1之间。输出的size为[8732, 4]

恢复训练resume部分:

self.load_state_dict(torch.load(base_file,
                     map_location=lambda storage, loc: storage))

4、MultiBoxLoss损失函数部分

52139fd8277c0848e0b27c9e31f26bb6.png

记住,回归的是gt_boxprior_box的偏差。

只对与gt_box的IoU大于0.5的prior_box取为pos,对应的conf_t为labels的值,其他的为0,得到的num_pos大概有278个,对真实的偏差和预测的偏差在pos_idx位置上的值求smooth l1损失值。

计算smooth l1损失的时候需要过滤掉背景类,就是这么精髓!

pos = conf_t > 0
pos_idx = pos.unsqueeze(pos.dim()).expand_as(loc_data)
loc_p = loc_data[pos_idx].view(-1, 4)
loc_t = loc_t[pos_idx].view(-1, 4)
loss_l = F.smooth_l1_loss(loc_p, loc_t, size_average=False)

# 这个地方我不知道为什么这么求
batch_conf = conf_data.view(-1, self.num_classes)
loss_c = log_sum_exp(batch_conf) - batch_conf.gather(1, conf_t.view(-1, 1))

怎样对先验框进行匹配?SSD在训练的时候只需要输入图像和图像中每个目标对应的ground truth. 先验框与ground truth 的匹配遵循两个原则:

(1)找到与每个ground truth的IOU最大的先验框作为正样本。

(2)对于(1)中每个剩下的没有与任何ground truth匹配到的先验框,找到与其IOU最大的ground truth,若其与该ground truth的IOU值大于某个阈值(一般设为0.5),则该先验框对应的预测边界框与该ground truth匹配。

为了使正负样本尽量均衡(一般保证正负样本比例约为1:3),SSD采用hard negative mining, 即对负样本按照其预测背景类的置信度进行降序排列,选取置信度较小的top-k作为训练的负样本。

再具体实现的过程中,是对预测的值中loss_c排在前3*num_pos的index取为neg,大概一共有278×4=1112个需要用来求confidence_loss的值,就是求一个cross_entropy([1112, 21], [1112])

DSSD改进之处

fe01e22f35fd5e5d13989e4c44f7672a.png
图2、DSSD模型(下)与SSD模型(上)对比

为了解决SSD算法检测小目标困难的问题,DSSD算法将SSD算法基础网络从VGG-16更改为ResNet-101,增强网络特征提取能力,其次参考FPN算法思路利用Deconvolution结构将图像深层特征从高维空间传递出来,与浅层信息融合,联系不同层级之间的图像语义关系,设计预测模块结构,通过不同层级特征之间融合特征输出预测物体类别信息。

DSSD算法中有两个特殊的结构:Prediction模块Deconvolution模块。前者在每一个预测层后增加残差模块,使得高分辨率图片的检测精度比原始SSD提升明显。后者则增加了三个Batch Normalization层和三个3×3卷积层,其中卷积层起到了缓冲的作用,防止梯度对主网络影响太剧烈,保证网络的稳定性。

a8408293e2da0ecf9ff9dd44bc1a1477.png
图3、Prediction模块

05ae5bb33251127ec4575b49df4ab2ae.png
图4、Deconvolution模块

代码中的遇到的问题汇总:

问题1[2]:

if torch.cuda.device_count() > 1:应该得加一个判断的,
# torch.cuda.device_count()
    torch.nn.DataParallel(net)
if torch.cuda.is_available():
   model.cuda()

问题2: torch.backends.cudnn.benchmark = true何时使用问题[3]

一般来讲,应该遵循以下准则:

1、如果网络的输入数据维度或类型上变化不大,设置 torch.backends.cudnn.benchmark = true 可以增加运行效率;

2、如果网络的输入数据在每次 iteration 都变化的话,会导致 cnDNN 每次都会去寻找一遍最优配置,这样反而会降低运行效率。

问题3:有关inplace参数的问题[4]

ReLU函数有个inplace参数,如果设为True,它会把输出直接覆盖到输入中,这样可以节省内存/显存。之所以可以覆盖是因为在计算ReLU的反向传播时,只需根据输出就能够推算出反向传播的梯度。但是只有少数的autograd操作支持inplace操作(如variable.sigmoid_()),除非你明确地知道自己在做什么,否则一般不要使用inplace操作。

积累1:torch.gather(input, dim, index)函数

按照给定dim,这里dim的意思是沿着第几个维度变化的方向,按照index来索引数值。例如一个二维矩阵,dim=1的时候,表示按照每一行的index来索引数值。index的size不用一定要和input一致。

积累2:为什么计算log_sum_exp()函数[5]

当需要计算

时,由于在 64-bit 系统中,因为下溢(underflow) 和上溢(overflow) 的存在,

采取的技巧是:

一种典型的情况是:

这样保证了取指数的时候最大值为0.

积累3:用两次sort函数(排序)找出矩阵每个元素在升序或降序排列中的位置[6]

对于一个二维矩阵,使用两次sort函数(对原矩阵sort(1, descending=True)、第一次sort之后的index.sort(1))后,index(第二个变量)的输出是原矩阵一个由大到小的映射,映射的值为整数,位置保持不变。

f19836abb235ae561be5c50554366c15.png

论文中注意的点(待补充):

1、change pool5 from 2x2-s2 to 3x3-s1, and use the `a trous algorithm to fill the “holes”. 这样做之后可以让pool5的feature maps 保持较大的尺寸,有利于小物体的检测。 后面的卷积层为了能够使用原来的参数初始化必须使用带孔的卷积,可以保证卷积层的一个神经元的感受野保持不变[1]。

[1]、SSD 里的 atrous - z1102252970的专栏 - CSDN博客

参考资料:

[1]、http://pycoders-weekly-chinese.readthedocs.io/en/latest/issue6/a-guide-to-pythons-magic-methods.html

[2]、https://www.jianshu.com/p/0bdf846dc1a2

[3]、https://blog.csdn.net/zlrai5895/article/details/81209889

[4]、https://www.cnblogs.com/hellcat/p/8474254.html

[5]、https://blog.csdn.net/zziahgf/article/details/78489562

[6]、https://blog.csdn.net/LXX516/article/details/78804884

[7]、https://github.com/amdegroot/ssd.pytorch

[8]、https://arxiv.org/abs/1512.02325

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值