先声明这篇只是快速的复习用, 用词比较自我加随性,多见谅
代码解说的地方很多参考了这个连结
首先DCN有两个版本, 大家都知道的v1就是单纯的加了offset偏移, 另一个v2就是从v1的基础上再加上了modulation, 也就是对学习对所有位置的权重
DCN v1 作用
- v1: 多用了一个卷积学习出偏移offset的值, 相较于原先标准的卷积在感受野上有多更多分布的采样点, 原来标准的卷积不就是一个矩形的样子? 这样能学的特征非常的受到限制, 所以可变卷积就tm的问世了
公式
标准卷积(方便对照)
DCNv1
- p表示为特征图上每一个点
- y(p0)就表示 output feature map
- w 表示权重, 就是conv kernel 上的每一个值
- Δ P n \Delta P_n ΔPn表示经由Conv学习到的偏值
- x 包含了输入特征图上的原采样点加上了学习到的偏值, 然后经过线性插值的过程
看最上边那张图, 经过conv_offset(就是最上图那个绿色的卷积)这个卷积学习出来的通道数为2N, 为什么是2, 因为学习出来的偏移有两个direction的, 一个为x, 一个为y, 一个通道负责一个方向, 那么N是表示采样点的个数, 比如用conv3x3去学习出offset, 那么就有2x3x3个点
这边记得从conv_offset学习出来的偏值通常会是float, 因为坐标需要整形
所以需要用一个bilinear interpolation来对学习出来的offset进行预处理
以下公式为线性插值方法
- G ( q , p ) G(q, p) G(q,p) 就是bilinear interpolation function
- 这里的p就是 ( p 0 + p n + Δ p n ) (p_0 + p_n + \Delta p_n) (p0+pn+Δpn)
接下来讲一下代码的实现思路
参考 代码
init的部分把相关信息配置好
p_conv就是用来学习offset的,这里仔细看outchannel的参数定义为2kernelsizekernelsize, 就是前面说到的2x3x3
m_conv就是DCNv2用来学习所以采样点的权重的
然后看到执行过程
看到经过了p_conv 得到offset, 如果有经过m_conv则会用一个sigmoid压缩成[0, 1]之间
这边看到用了一个 forward中用了_get_p
函数, 里面又用到了_get_p_0
, 所以tm的两个都要看才行
先tm的看_get_p_n
函数, 主要是计算出所有卷积核的中心坐标, 为啥可以刚好计算出卷积核的中心坐标??
看下图, 假设一个input fmap(5x7), conv为3x3, 则输出的outmap为2x3, 则输出map上的每一个元素值就刚好等于卷积核中心的”位置(坐标)“了,
继续接着看代码, 用torch.meshgrid
函数计算出卷积核上的九个坐标, 也就是(-1, 1)~…(1, 1)等9个, 最后噼里啪啦得到p_n
形状是(1, 2N, 1, 1), 这里的2N就是前面说到2N
再来看到_get_p_0
这个函数, 用来获取卷积核中心的坐标就是他妈的公式上的p0
传入的是output map的h跟w
最后看到_get_p
, 这个函数求出来的就是
p
0
+
p
n
+
Δ
p
n
p0 + pn + \Delta p_n
p0+pn+Δpn
再来复习一下p0就是卷积核中心点
pn就是周围的点, 如果是(-1, 1), 那最终就是
0 + (-1, 1)+ offset了
最后因为取出来的offset是float, 需要用bilinear interpolation进行插值转换成整数, 坐标毕竟是整数
线性插值法请参考博客 博客
以下是截取参考链接的
根据线性插值理论我们需要获取4个整数坐标,所以在获取所有可变卷积核的坐标p后,通过floor()获取不超过本身的最大整数,也就是左上角坐标q_lt,同时为了防止偏移量过大移出feature map的范围通过torch.clamp对卷积核坐标做了限制。接下来只要通过左上角坐标就可以推出左下角(q_lb),右上角(q_rt),右下角(q_rb)坐标。
获取4个点的坐标后根据上图公式可以计算出像素值前面的系数(即 [公式] 这部分),代码中这4个系数分别为g_lt,g_rb,g_lb,g_rt。现在只获取了坐标值,我们最终木的是获取相应坐标上的值,这里我们通过self._get_x_q()获取相应值。如下
_get_x_q
可以从求出的四个坐标求出相应的值
接下来就自己看个明白或者看一开始的参考链接了
不然我也只是照抄而已, 老实说实际算法也没必要完全理解, 知道怎么用就行
这篇纯属记录一下这个算法思路
写些打加~~~