ssd笔记

源码是 https://github.com/aaalds/SSD-pytorch

ssd.py
for layer in enumerate(vgg(base, 3)):
    print(layer)
打印结果

(0, Conv2d(3, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(1, ReLU(inplace=True))
(2, Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(3, ReLU(inplace=True))
(4, MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))
(5, Conv2d(64, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(6, ReLU(inplace=True))
(7, Conv2d(128, 128, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(8, ReLU(inplace=True))
(9, MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))
(10, Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(11, ReLU(inplace=True))
(12, Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(13, ReLU(inplace=True))
(14, Conv2d(256, 256, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(15, ReLU(inplace=True))
(16, MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=True))
(17, Conv2d(256, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(18, ReLU(inplace=True))
(19, Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(20, ReLU(inplace=True))
(21, Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(22, ReLU(inplace=True))
(23, MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))
(24, Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(25, ReLU(inplace=True))
(26, Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(27, ReLU(inplace=True))
(28, Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(29, ReLU(inplace=True))
(30, MaxPool2d(kernel_size=3, stride=1, padding=1, dilation=1, ceil_mode=False))
(31, Conv2d(512, 1024, kernel_size=(3, 3), stride=(1, 1), padding=(6, 6), dilation=(6, 6)))
(32, ReLU(inplace=True))
(33, Conv2d(1024, 1024, kernel_size=(1, 1), stride=(1, 1)))
(34, ReLU(inplace=True))

for layer in enumerate(add_extras(extras, 1024)):
    print(layer)


        
打印结果:

(0, Conv2d(1024, 256, kernel_size=(1, 1), stride=(1, 1)))
(1, Conv2d(256, 512, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)))
(2, Conv2d(512, 128, kernel_size=(1, 1), stride=(1, 1)))
(3, Conv2d(128, 256, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1)))
(4, Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1)))
(5, Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1)))
(6, Conv2d(256, 128, kernel_size=(1, 1), stride=(1, 1)))
(7, Conv2d(128, 256, kernel_size=(3, 3), stride=(1, 1)))
for layer in enumerate(head_[0]):
    print(layer)
(0, Conv2d(512, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(1, Conv2d(1024, 24, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(2, Conv2d(512, 24, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(3, Conv2d(256, 24, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(4, Conv2d(256, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(5, Conv2d(256, 16, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
for layer in enumerate(head_[1]):
    print(layer)
(0, Conv2d(512, 84, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(1, Conv2d(1024, 126, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(2, Conv2d(512, 126, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(3, Conv2d(256, 126, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(4, Conv2d(256, 84, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
(5, Conv2d(256, 84, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))

ssd input_size(3, 300, 300)
调试代码:

if __name__ == '__main__':
    net = build_ssd("train")
    fake_image = torch.randn(1, 3, 300, 300)
    net.eval()

    output = net(fake_image)
    print(output[0].size())
    print(output[1].size())
    print(output[2].size())


输出

torch.Size([1, 8732, 4])
torch.Size([1, 8732, 21])
torch.Size([8732, 4])

debug ssd forward
计算到VGG的第22层,前面22层共下采样三次(300/8 = 38)
x (1, 512, 38, 38)
L2Norm之后,s (1, 512, 38, 38)
把s加入到 sources 中
x继续执行完VGG,从23层到最后,又下采样了一次
x(1, 1024, 19, 19)
又添加到 sources 中

x执行 extras,每隔一次添加到 sources 中
extras 有两次下采样,又有两次不加 pad  的 3*3卷积的骚操作(每次尺寸变换都会添加到 sources 中),
总之, x的最后size为 (1, 256, 1, 1)
sources 里的尺寸为 

torch.Size([1, 512, 38, 38])
torch.Size([1, 1024, 19, 19])
torch.Size([1, 512, 10, 10])
torch.Size([1, 256, 5, 5])
torch.Size([1, 256, 3, 3])
torch.Size([1, 256, 1, 1])

sources 的 len 和 head[0]/head[1]的len是一样的
每一层经过head[0]之后,并维度变换,得出的尺寸为:

torch.Size([1, 38, 38, 16])
torch.Size([1, 19, 19, 24])
torch.Size([1, 10, 10, 24])
torch.Size([1, 5, 5, 24])
torch.Size([1, 3, 3, 16])
torch.Size([1, 1, 1, 16])

每一层经过head[1]之后,并维度变换,得出的尺寸为:

torch.Size([1, 38, 38, 84])
torch.Size([1, 19, 19, 126])
torch.Size([1, 10, 10, 126])
torch.Size([1, 5, 5, 126])
torch.Size([1, 3, 3, 84])
torch.Size([1, 1, 1, 84])

然后 loc 把一切全部展开加一块,长度为 (1, 34928)
conf全部展开,长度加一块为(1, 183372)

到output, loc维度变换为(1, 8732, 4)
conf 维度变换为 (1, 8732, 21)

剩下的一个难题是 priors 是啥
这里有一个product函数,是把两组数搞个排列
'feature_maps': [38, 19, 10, 5, 3, 1],
第一次循环中,f的值是38, product(range(f), repeat=2)会产生巨长的一组数
就是从0,0->0,1->0,2->......->0,37->1,37->.....->37,37
再往下,'steps': [8, 16, 32, 64, 100, 300],
反正大致看下来,steps 和 feature_maps 的各项之和大致是300,也就是image_size
后面的cx,cy,其实是做了一个归一化,这样除一下,最大值也就是1左右
(numpy.array(self.feature_maps) - 0.5)* numpy.array(self.steps)
Out[18]: array([300., 296., 304., 288., 250., 150.])
这个是 anchor 的中心点的坐标

先往下看
'min_sizes': [30, 60, 111, 162, 213, 264],
s_k = 30/300 = 0.1
'max_sizes': [60, 111, 162, 213, 264, 315],
60/300 = 0.2
s_k_prime = sqrt(0.1*0.2) = 0.141
这是比较大的框
比较大的框就是sqrt(min_sizes/300 * max_size / 300)* 300
也就是sqrt(min_sizes * max_sizes)
k = numpy.array(self.min_sizes) * numpy.array(self.max_sizes)
得到numpy.sqrt(k)
Out[12]: 
array([ 42.42640687,  81.60882305, 134.09697983, 185.75790696,
       237.13287414, 288.37475618])
下面是指定比例的框
'aspect_ratios': [[2], [2, 3], [2, 3], [2, 3], [2], [2]],
也就是在 feature_maps 为38, 3, 和 1 的时候,会加一些比例为2的框
而在 feature_maps 为 19, 10 和 5 的时候,会加比例为 2 和比例为 3 的框
总共有 38 * 38 * 4 + 19 * 19 * 6 + 10 * 10 * 6 + 5 * 5 * 6 + 3 * 3 * 4 + 1 * 1 * 4 个框
也就是 8732 个
后面有个 clip 保证值在0到1之间

总之,feature_maps 和 steps 控制了结果的中心点坐标
min_sizes 和 max_sizes 控制了框的大小, aspect_ratios 控制了框的比例

最后,把ssd的输出尺寸强调一下,共输出三个尺寸的结果
坐标调整 torch.Size([1, 8732, 4])
分类 torch.Size([1, 8732, 21])
anchor torch.Size([8732, 4])
__________________________________________________
以上是train阶段的笔记
test阶段如下:
首先执行 test.py 会报错:
Legacy autograd function with non-static forward method is deprecated. 
因为 ssd 在 test 阶段会调用 detect 函数,该函数继承自 Function, torch.autograd
旧版本调用的时候可能是跟 nn.Module 一样,直接把输入传进去当变量就行了, 新版本不行
参考 https://blog.csdn.net/qq_36926037/article/details/108419899
我把
output = self.detect(
改为了
output = self.detect.forward(

然后继续调试
loc 的尺寸为 torch.Size([1, 8732, 4]),我们也可以猜到,他是跟 anchor 一起计算的
怎么计算要看 box_utils.py, decode 函数
这里的 anchor 就是变量 priors
大概是说, 以 anchor 的坐标为基准,加上 loc 的坐标值乘以 anchor 的宽高值,再乘以 0.1,这个是anchor的中心点
而宽高是anchor的宽高,乘以 e 的 loc 的"宽高"乘以0.2次方
而最终返回的 boxes 其实是左上角和右下角坐标,所以是中心点坐标左右上下移动宽高的一半

接下来就会报另外一个错
ValueError: not enough values to unpack (expected 2, got 0)
可以参考
https://blog.csdn.net/ksws0292756/article/details/83512208

文件detection.py第50行
把 if scores.dim() == 0: 改为
if scores.size(0) == 0:
当然也可以改为 if scores.numel() == 0:

下面就是 nms 了
先遍历所有的类,然后把该类的所有框,先按照阈值过滤一遍, 然后进行 nms 删除
nms 的第一步是把所有框的分数排序,他这里是从小到大
当然如果超过阈值的框太多的话呢,也可以删除一部分,他这里是取阈值最大的200个

接下来是另一个报错
RuntimeError: index_select(): functions with out=... arguments don't support automatic differentiation
需要把

torch.index_select(x1, 0, idx, out=xx1)
torch.index_select(y1, 0, idx, out=yy1)
torch.index_select(x2, 0, idx, out=xx2)
torch.index_select(y2, 0, idx, out=yy2)


改为

xx1 = torch.index_select(x1, 0, idx).detach()
yy1 = torch.index_select(y1, 0, idx).detach()
xx2 = torch.index_select(x2, 0, idx).detach()
yy2 = torch.index_select(y2, 0, idx).detach()

接下来又是一个报错
TypeError: clamp(): argument 'min' must be Number, not Tensor

我是改为了下面这样

xx1 = torch.clamp(xx1, min=x1[i].cpu().data)
yy1 = torch.clamp(yy1, min=y1[i].cpu().data)
xx2 = torch.clamp(xx2, max=x2[i].cpu().data)
yy2 = torch.clamp(yy2, max=y2[i].cpu().data)

好,这下可以正常运行了,终于没有bug了
上面是先选中了一个分数最高的框,然后放到keep里面,保存下来,接下来做的事,是把所有剩下来的框跟这个框计算IoU
如果IoU超过了阈值,那么就删除
然后进行下次循环,依次保存阈值最高的框

最后的输出是
y = net(x)
y是(1,21,200,5)
1是batch
21是classes
200是每个class可能的结果数
5的第一个是score,后面四个是坐标

最后,就是画面显示,回到test.py,在coords计算后面加上下面一行

cv2.rectangle(img, (int(coords[0]), int(coords[1])),
              (int(coords[2]), int(coords[3])),
              (255, 255, 0), 2, 8)


然后在for循环的最后加上

cv2.imshow("img", img)
if cv2.waitKey(10000) == 27:
    continue


即可看到画面
默认是等待10秒,如果不想等也可以按ESC键
 

我在我 fork 出来的库上提交了bug修复,可参考 https://github.com/moneypi/SSD-pytorchicon-default.png?t=M276https://github.com/moneypi/SSD-pytorch

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值