在最近自学李沐沐神的《动手学深度学习》中,一直在研究这个方法里的代码,属实是差点把我整崩溃了,在网上找了许多参考还是看的很崩溃,在近一周的折磨中找到了一个理解的方法,那就是设置自定义初始值去逐句分析,怕自己忘记,来发一个自己理解的笔记。
import torch
height,width = 3,5
sizes=[0.25,0.5,0.75]
ratios=[1,2,0.5]
num_sizes,num_ratios=len(sizes),len(ratios)
boxes_per_pixel=(num_sizes+num_ratios-1)
导入包,定义初始高宽为3x5,缩放比(占原图的大小)和宽高比
boxes_per_pixel即每组锚框的数量,也就是每个基本单位——像素生成的锚框数量,原因在视频里都有讲到
size_tensor = torch.tensor(sizes)
ratio_tensor = torch.tensor(ratios)
将列表转换成tensor格式
size_tensor,ratio_tensor
'''输出'''
(tensor([0.2500, 0.5000, 0.7500]), tensor([1.0000, 2.0000, 0.5000]))
每个像素的大小是1,那么第一个像素的中心即为(0.5,0.5)
可以认为是在每个像素的中心生成锚框
offset_h,offset_w=0.5,0.5
之后的操作像是归一化
steps_h = 1.0/height
# x轴
steps_w = 1.0/width
steps_h,steps_w
'''结果'''
(0.3333333333333333, 0.2)
经过这一步后,steps_h,steps_w就保存了高宽归一化之后每走一个像素需要移动的距离
比如宽是5,就把1平均分成五份,距离就是0.2
下一步是这个操作
center_h=(torch.arange(height)+offset_h)*steps_h
center_w=(torch.arange(width)+offset_w)*steps_w
torch.arange方法是生成[0,1,…i-1]的方法,具体的可以百度,这里就只讲这段代码执行后的结果
我们先看前面一部分
center_h=(torch.arange(height)+offset_h)
center_w=(torch.arange(width)+offset_w)
center_h,center_w
'''结果'''
(tensor([0.5000, 1.5000, 2.5000]),
tensor([0.5000, 1.5000, 2.5000, 3.5000, 4.5000]))
很显然,保存了高宽方向的每个像素中心点的坐标
例如高为3,那么每个像素中心的纵坐标一定是0.5、1.5或者2.5
center_h*=steps_h
center_w*=steps_w
再经过以上代码,就把这些中心坐标也进行了归一化
center_h,center_w
'''结果'''
(tensor([0.1667, 0.5000, 0.8333]),
tensor([0.1000, 0.3000, 0.5000, 0.7000, 0.9000]))
之后的代码更像是要为最后的组合做准备
shift_y,shift_x=torch.meshgrid(center_h,center_w,indexing='ij')
这一步将每个锚框中心点坐标的高宽以一种特殊的样子保存了下来
shift_y,shift_x
'''结果'''
(tensor([[0.1667, 0.1667, 0.1667, 0.1667, 0.1667],
[0.5000, 0.5000, 0.5000, 0.5000, 0.5000],
[0.8333, 0.8333, 0.8333, 0.8333, 0.8333]]),
tensor([[0.1000, 0.3000, 0.5000, 0.7000, 0.9000],
[0.1000, 0.3000, 0.5000, 0.7000, 0.9000],
[0.1000, 0.3000, 0.5000, 0.7000, 0.9000]]))
首先形状都与输入的形状相同,3x5,第一个张量每行相同,共三行;第二个张量每列相同,共五列。至于为什么这么做,在后面有我的理解。
下一步是:
w = torch.cat((size_tensor * torch.sqrt(ratio_tensor[0]),
sizes[0] * torch.sqrt(ratio_tensor[1:])))
h = torch.cat((size_tensor/torch.sqrt(ratio_tensor[0]),
size_tensor[0]/torch.sqrt(ratio_tensor[1:])))
torch.cat是组合张量的方法
锚框的宽高计算式分别是hs根号r,hs/根号r,具体证明过程不做介绍
在上面的代码中,仅仅计算了s根号r和s/根号r这一部分,也就是还在归一化中,之后恢复正常大小时再乘h
至于为什么要用sizes与ratios的第一个数相乘+sizes的第一个与ratios去掉第一个相乘,再课程中都有讲过,也就是这里boxes_per_pixel的由来
w,h
'''结果'''
tensor([0.2500, 0.5000, 0.7500, 0.3536, 0.1768])
tensor([0.2500, 0.5000, 0.7500, 0.1768, 0.3536])
就出现了五个锚框的宽和高
假设每个锚框的中心在原点,那么这个锚框的右上角的坐标为(-w/2,h/2)
那下面的代码就好理解了
anchor_manipulations = torch.stack((-w, -h, w, h)).T.repeat(
height * width, 1) / 2
anchor_manipulations
'''结果'''
tensor([[-0.1250, -0.1250, 0.1250, 0.1250],
[-0.2500, -0.2500, 0.2500, 0.2500],
[-0.3750, -0.3750, 0.3750, 0.3750],
[-0.1768, -0.0884, 0.1768, 0.0884],
[-0.0884, -0.1768, 0.0884, 0.1768],
[-0.1250, -0.1250, 0.1250, 0.1250],
[-0.2500, -0.2500, 0.2500, 0.2500],
[-0.3750, -0.3750, 0.3750, 0.3750],
[-0.1768, -0.0884, 0.1768, 0.0884],
[-0.0884, -0.1768, 0.0884, 0.1768],
[-0.1250, -0.1250, 0.1250, 0.1250],
[-0.2500, -0.2500, 0.2500, 0.2500],
.........
每行都是一个锚框的左宽,下高,右宽,上高(中心为原点)
每五行一个循环,代表每个像素有五个锚框
torch.stack((-w, -h, w, h)).T生成了五行张量,每行为一个锚框的信息,repeat(height * width, 1)将这五行复制了height * width次,因为height * width的图片中就有height * width个像素。
在下一步操作之前我们先来看这一行代码
out_grid = torch.stack([shift_x, shift_y, shift_x, shift_y],
dim=1)
out_grid
'''结果'''
tensor([[0.1000, 0.1667, 0.1000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.7000, 0.1667, 0.7000, 0.1667],
[0.9000, 0.1667, 0.9000, 0.1667],
[0.1000, 0.5000, 0.1000, 0.5000],
[0.3000, 0.5000, 0.3000, 0.5000],
[0.5000, 0.5000, 0.5000, 0.5000],
[0.7000, 0.5000, 0.7000, 0.5000],
[0.9000, 0.5000, 0.9000, 0.5000],
[0.1000, 0.8333, 0.1000, 0.8333],
[0.3000, 0.8333, 0.3000, 0.8333],
[0.5000, 0.8333, 0.5000, 0.8333],
[0.7000, 0.8333, 0.7000, 0.8333],
[0.9000, 0.8333, 0.9000, 0.8333]])
结合前文中求得的shift_x与shift_y(分别代表了锚框的宽和高),可以看出这一步恰好就表示出来3*5=15个像素的中心点位置的横坐标、纵坐标、横坐标、纵坐标
再用下面的方法将每个像素坐标乘5
out_grid = out_grid.repeat_interleave(boxes_per_pixel, dim=0)
out_grid
'''结果'''
tensor([[0.1000, 0.1667, 0.1000, 0.1667],
[0.1000, 0.1667, 0.1000, 0.1667],
[0.1000, 0.1667, 0.1000, 0.1667],
[0.1000, 0.1667, 0.1000, 0.1667],
[0.1000, 0.1667, 0.1000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.3000, 0.1667, 0.3000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.5000, 0.1667, 0.5000, 0.1667],
[0.7000, 0.1667, 0.7000, 0.1667],
[0.7000, 0.1667, 0.7000, 0.1667],
......
每五行代表了一个像素的坐标
最后
output = out_grid+anchor_manipulations
像素的中点横坐标减去锚框的宽,中点的纵坐标减去锚框的高,便是锚框的左下坐标;
像素的中点横坐标加上锚框的宽,中点的纵坐标加上锚框的高,便是锚框的右上坐标。
output 的形状便是(boxes_per_pixel,4)
以上便是我对这个函数研究一周时间的个人理解,肯定包含了许多错误的地方,希望能帮助到大家的同时大家也能指出我的错误。