可变形卷积(DCN)——论文及代码详解

news2024/11/16 21:39:38

文章目录

  • 论文详解
    • Deformable Convolution
    • Deformable RoI Pooling
    • Deformable ConvNets
    • Understanding Deformable ConvNets
  • 代码详解

论文:《Deformable Convolutional Networks》

论文详解

Deformable Convolution


普通卷积的数学表达
普通的二维卷积包括两个步骤:
1)在输入特征图x上使用regular gird R进行采样;
2)以w加权的采样值的总和。网格R定义接收域的大小和扩张。例如,

R = { ( − 1 , − 1 ) , ( − 1 , 0 ) , … , ( 0 , 1 ) , ( 1 , 1 ) } \mathcal{R}=\{(-1,-1),(-1,0), \ldots,(0,1),(1,1)\} R={(1,1),(1,0),,(0,1),(1,1)}
定义了一个dilation=1, 3x3的卷积。
对于输出特征图 y y y上的每个位置 p 0 p_0 p0
Eq(1)

y ( p 0 ) = ∑ p n ∈ R w ( p n ) ⋅ x ( p 0 + p n ) , \mathbf{y}\left(\mathbf{p}_0\right)=\sum_{\mathbf{p}_n \in \mathcal{R}} \mathbf{w}\left(\mathbf{p}_n\right) \cdot \mathbf{x}\left(\mathbf{p}_0+\mathbf{p}_n\right), y(p0)=pnRw(pn)x(p0+pn),

其中 p n p_n pn穷举了 R \mathcal{R} R中的所有位置。


可变形卷积的数学表达
在可变形卷积中,regular grid R用偏移量 { Δ p n ∣ n = 1 , … , N } \left\{\Delta \mathbf{p}_n \mid n=1, \ldots, N\right\} {Δpnn=1,,N}进行增广, 其中 N = ∣ R ∣ N=|\mathcal{R}| N=R

上述的式子就变成了:

Eq(2)

y ( p 0 ) = ∑ p n ∈ R w ( p n ) ⋅ x ( p 0 + p n + Δ p n ) \mathbf{y}\left(\mathbf{p}_0\right)=\sum_{\mathbf{p}_n \in \mathcal{R}} \mathbf{w}\left(\mathbf{p}_n\right) \cdot \mathbf{x}\left(\mathbf{p}_0+\mathbf{p}_n+\Delta \mathbf{p}_n\right) y(p0)=pnRw(pn)x(p0+pn+Δpn)

现在,采样是在不规则和偏移位置 p n + ∆ p n p_n+∆p_n pn+pn上。由于偏移量 ∆ p n ∆p_n pn通常为分数阶,因此通过双线性插值实现式(3)为:

Eq(3)

x ( p ) = ∑ q G ( q , p ) ⋅ x ( q ) \mathbf{x}(\mathbf{p})=\sum_{\mathbf{q}} G(\mathbf{q}, \mathbf{p}) \cdot \mathbf{x}(\mathbf{q}) x(p)=qG(q,p)x(q)

其中 p p p表示任意(分数)位置(对于Eq. (2) p = p 0 + p n + ∆ p n p = p_0 +p_n +∆p_n p=p0+pn+pn), q q q 枚举特征映射x中所有积分空间位置,G(·,·)为双线性插值核。注意G是二维的。它被分成两个一维的核:

Eq(4)

G ( q , p ) = g ( q x , p x ) ⋅ g ( q y , p y ) G(\mathbf{q}, \mathbf{p})=g\left(q_x, p_x\right) \cdot g\left(q_y, p_y\right) G(q,p)=g(qx,px)g(qy,py)

其中 g ( a , b ) = m a x ( 0 , 1 − ∣ a − b ∣ ) g(a, b) = max(0,1−|a−b|) g(a,b)=max(0,1ab)。Eq.(3)的计算速度很快,因为G(q, p)仅在几个q中是非零的。

如图2所示,偏移量是通过在相同的输入特征图上应用卷积层获得的。卷积核与当前卷积层具有相同的空间分辨率和膨胀(图2中也是3 × 3的kernel)。
在这里插入图片描述


个人理解

原始图片的大小为 b × c × h × w b\times c\times h\times w b×c×h×w), 记为U(图中的最左边的input feature map)
经过一个普通卷积(上图的conv), 卷积填充为same, 即输出输入大小不变, 对应的输出结果为( b × h × w × 2 N b\times h\times w\times 2N b×h×w×2N), 记为V
输出的结果是指原图片batch中每个像素的偏移量, x方向偏移与y方向的偏移, 因此为2N。(N是指卷积核的大小,如果是3x3的卷积核,则N=9 )
将U中图片的像素索引值(即坐标)与V(坐标偏移量)相加, 得到偏移后的position (即在原始图片U中的坐标值), 需要将position值限定为图片大小以内, 但position只是一个坐标值, 而且还是float类型的, 我们需要这些float类型的坐标值获取像素。
在这里插入图片描述
如上图所示,只有蓝色的和红色的点存在一个像素值(它们的横纵坐标值均为整数)
假设黄色的点是我们偏移后的position, 它的坐标值设为(a,b), 其中a,b均是浮点数,此时黄色的点的位置是不存在像素值的。
那么怎样才可以得到黄色点对应的像素值呢?我们利用黄色点周围的4个像素点(红色的点)进行双线性插值。
红色点对应的坐标是坐标 ( f l o o r ( a ) , f l o o r ( b ) ) , ( ( f l o o r ( a ) , c e i l ( b ) ) , ( ( c e i l ( a ) , f l o o r ( b ) ) , ( ( c e i l ( a ) , c e i l ( b ) ) (floor(a),floor(b)), ((floor(a),ceil(b)), ((ceil(a),floor(b)), ((ceil(a),ceil(b)) (floor(a),floor(b)),((floor(a),ceil(b)),((ceil(a),floor(b)),((ceil(a),ceil(b)) 这四对坐标每个坐标都对应U中的一个像素值, 而我们需要得到(a,b)的像素值, 这里采用双线性差值的方式计算, 因为一方面得到的像素准确, 另一方面可以进行反向传播。双线性插值参考:《超分任务中常见的上采样方式》

在这里插入图片描述
DCN的卷积过程和普通卷积一样,如上图所示,假设有个2x2的kernel, 它也是以一个2x2的滑窗的形式(绿色的框)在原始图片上从左到右,从上到下进行滑动 。
和普通的卷积的区别在于,当滑动到当前位置时,普通卷积会将卷积核(绿点)和原始像素(红点)对应的值相乘后相加,得到输出的值。
可变形卷积是将卷积核(绿点)和采样点(彩色圆圈)对应的值相乘后相加。其中采样点的像素值就是通过上一步的双线性插值得到的。

Deformable RoI Pooling

所有基于区域建议的目标检测方法都采用RoI池化方法。它将任意大小的输入矩形区域转换为固定大小的特征。它将任意大小的输入矩形区域转换为固定大小的特征。

  • RoI Pooling

给定输入的特征图 x x x 和一个大小为 w × h w×h w×h 和左上角为 p 0 p_0 p0的RoI, RoI池化将RoI分成 k × k k×k k×k个 bins ( k k k 是一个自由参数),并输出 k × k k ×k k×k的特征图 y y y。对于第 ( i , j ) (i, j) (i,j)个 bin ( 0 ≤ i , j < k 0≤i, j < k 0i,j<k),

Eq(5)

y ( i , j ) = ∑ p ∈ bin ⁡ ( i , j ) x ( p 0 + p ) / n i j \mathbf{y}(i, j)=\sum_{\mathbf{p} \in \operatorname{bin}(i, j)} \mathbf{x}\left(\mathbf{p}_0+\mathbf{p}\right) / n_{i j} y(i,j)=pbin(i,j)x(p0+p)/nij

其中 n i j n_{ij} nij是bin中的pixel的数量。第 ( i , j ) (i,j) (i,j)个bin span为 ⌊ i w k ⌋ ≤ p x < ⌈ ( i + 1 ) w k ⌉ \left\lfloor i \frac{w}{k}\right\rfloor \leq p_x<\left\lceil(i+1) \frac{w}{k}\right\rceil ikwpx<(i+1)kw, ⌊ j h k ⌋ ≤ p y < ⌊ ( j + 1 ) h k ⌋ \left\lfloor j \frac{h}{k}\right\rfloor \leq p_y< \left\lfloor (j+1) \frac{h}{k}\right\rfloor jkhpy<(j+1)kh

与Eq.(2)相似,在可变形RoI池化中,将偏移量 { ∆ p i j ∣ 0 ≤ i , j < k } \{∆p_{ij}|0≤i, j < k\} {pij∣0i,j<k}加到空间分块位置上。式(5)得到

Eq(6)

y ( i , j ) = ∑ p ∈ bin ⁡ ( i , j ) x ( p 0 + p + Δ p i j ) / n i j \mathbf{y}(i, j)=\sum_{\mathbf{p} \in \operatorname{bin}(i, j)} \mathbf{x}\left(\mathbf{p}_0+\mathbf{p}+\Delta \mathbf{p}_{i j}\right) / n_{i j} y(i,j)=pbin(i,j)x(p0+p+Δpij)/nij

通常, ∆ p i j ∆p_{ij} pij是分数。式(6)通过式(3)和式(4)的双线性插值实现。

图3演示了如何获取偏移量。首先,RoI池化(Eq.(5))生成池化的特征图。从图中,fc层生成归一化的偏移量 ∆ p ^ i j ∆\hat p_{ij} p^ij,然后通过与RoI的宽度和高度的元素级乘积转换为公式(6)中的偏移量∆pij,即 Δ p i j = γ ⋅ Δ p ^ i j ∘ ( w , h ) \Delta \mathbf{p}_{i j}=\gamma \cdot \Delta \widehat{\mathbf{p}}_{i j} \circ(w, h) Δpij=γΔp ij(w,h)。这里γ是一个预定义的标量,用于调节偏移量的大小。经验值为γ = 0.1。为了使偏移学习不受RoI大小的影响,偏移归一化是必要的。

在这里插入图片描述


个人理解
ROI Pooling 将任意大小的特征图转换成固定大小的特征向量。如下图所示,从左到右分别将ROI划分成了1*1个bins、2*2个bins、3*3个bins。就是说,如果我们需要的特征向量大小是 k 2 k^2 k2个,则把原始的RIO划分成 k × k k\times k k×k个bin,然后对每个bin中的值求平均,就得到了对应的特征向量的值。
在这里插入图片描述
如下图所示,假设原始图片大小是4x4(下图蓝色的实心点), 然后我们把原始图片划分成2x2个bins(下图中4个框)。
如果是普通的ROI Pooling,会对每个框中的4个蓝色的实心点对应的像素值求平均。
如果是可变形的ROI Pooling, 会对每个框中的采样点(图中的圆圈)求平均。采样点的像素值和上文中提到的可变形卷积中的一样,也是通过双线性插值得到的。
在这里插入图片描述

  • Position-Sensitive (PS) RoI Pooling

PS ROI Pooling 是fully convolutional的,不同于RoI Pooling。通过一个conv层,所有的输入特征图首先被转换为每个对象类的 k 2 k^2 k2 score map (C对象类总共C + 1),如图4的底部分支所示。不需要区分类别,这样的分数映射被表示为 x i , j {x_{i,j}} xi,j,其中 ( i , j ) (i, j) (i,j)枚举所有的bins。在这些score map上执行池化。第(i, j)个bin的输出值是由对应于该bin的一个得分映射 x i , j x_{i,j} xi,j的总和得到的。简而言之,与式(5)中的RoI池化不同的是,将一般的feature map x替换为特定的positive-sensitive score map x i , j x_{i,j} xi,j

在这里插入图片描述


个人理解
我们通过R-FCN论文中的图来理解位置敏感的ROI Pooling (Position-Sensitive RoI Pooling)
如下图,R-FCN会在共享卷积层的最后再接上一层卷积层,而该卷积层输出的结果就是position-sensitive score map
在这里插入图片描述

position-sensitive score map的height和width和共享卷积层的一样,但是它的channels= k 2 ( C + 1 ) k^2(C+1) k2(C+1)。那么C表示物体类别种数再加上1个背景类别,每个类别都有 k 2 k^2 k2个score maps。
因为特征图划分成 k × k k\times k k×k 个bins, 把每个类别的第 i i i个特征图的第 i i i个bin区域按照bin原本的位置组合在一起,构成一个类别的score map。
score map出现高响应值的位置,表示该位置有该类别的概率很大。每个类别的score map在通道维度连接在一起,就构成了 k × k × ( C + 1 ) k\times k \times (C+1) k×k×(C+1)的张量。

对于每个类别,该类别的 k 2 k^2 k2个值都表示该ROI属于该类别的响应值,则把这个 k 2 k^2 k2个值相加,就得到了该类别的Score, 所有类别的score构成了一个 C + 1 C+1 C+1 的张量,再经过softmax,就可以得到属于各个类别的概率。

Deformable ConvNets

可变形卷积和RoI池化模块的输入和输出与普通版本相同。因此,它们可以很容易地替换现有cnn中的普通版本。在训练中,这些为偏移学习增加的conv层和fc层以零权值初始化。它们的学习率设置为现有层学习率的β倍(β默认为1,fc层在Faster R-CNN中为β = 0.01)。通过式(3)和式(4)中的双线性插值运算进行反向传播训练。得到的cnn称为deformable ConvNets.。

为了将可变形的卷积神经网络与最先进的CNN架构集成在一起,我们注意到这些架构包括两个阶段。首先,深度全卷积网络生成整个输入图像的特征映射。其次,基于任务的浅层网络根据特征映射生成结果。我们将在下面详细说明两个步骤。

  • Deformable Convolution for Feature Extraction

我们采用了两种最先进的特征提取架构:ResNet-101[20]和InceptionResNet[46]的修改版本。两者都是在ImageNet[7]数据集上预训练的。

最初的Inception-ResNet是为图像识别而设计的。它存在特征不对齐的问题,对于密集的预测任务是有问题的。对其进行了修改,以解决对准问题[18]。修改后的版本被称为“Aligned-Inception-ResNet”。请在本文的arxiv在线版本中找到它的详细信息。

两个模型都由几个卷积块、一个平均池和一个用于ImageNet分类的1000路fc层组成。去掉了平均池和fc层。最后加入一个随机初始化的1 × 1卷积,使信道维数减少到1024。在通常的实践中[3,6],最后一个卷积块的有效步长从32像素减小到16像素,以提高feature map的分辨率。具体来说,在最后一个区块的开始,步幅从2改变为1(“conv5”的ResNet-101和Aligned-Inception-ResNet)。为了进行补偿,将该块(内核大小为> 1)中所有卷积滤波器的扩展从1更改为2。

可选地,可变形卷积应用于最后几个卷积层(内核大小为> 1)。我们实验了不同数量的这类层,发现3是不同任务的一个很好的权衡,如表1所示。
在这里插入图片描述

  • Segmentation and Detection Networks

在上述特征提取网络的输出特征映射的基础上,构建任务特定网络。

在下文中,C表示对象类的数量。

DeepLab[4]是一种最先进的语义分割方法。它在特征图上添加1 × 1的卷积层,生成表示逐像素分类分数的(C + 1)图。接下来的softmax输出逐像素概率。

Category-Aware RPN与[42]中的region proposal网络几乎相同,只是用(C + 1)类卷积分类器代替了2类(对象或非对象)卷积分类器

Faster R-CNN[42]是最先进的探测器。在我们的实现中,RPN分支被添加到conv4块的顶部,紧随[42]。在之前的实践中[20,22],在ResNet-101的conv4和conv5块之间插入了RoI池化层,每个RoI留下10层。该设计具有良好的精度,但每roi计算量较高。相反,我们采用了[34]中的简化设计。最后增加RoI池层。在集合RoI特征的基础上,增加了维度1024的两个fc层,然后是边界框回归和分类分支。虽然这样的简化(从10层conv5块到2 fc层)会略微降低精度,但它仍然是一个足够强的基线,在本工作中不需要关注。

可选地,RoI Pooling 层可以更改为Deformable RoI Pooling。

R-FCN[6]是另一种最先进的探测器。它的每roi计算成本可以忽略不计。我们遵循最初的实现。可选地,它的RoI池层可以更改为可变形的位置敏感RoI池。

Understanding Deformable ConvNets

这项工作是建立在增加空间采样位置的卷积和带有额外的偏移的RoI池化并从目标任务的学习偏移。

图5展示了可变形卷积的叠加效果。展示了3层deformabel convolution的叠加效果,由于每层采样 3 × 3 = 9 3\times 3=9 3×3=9个点,每个绿色的激活点,三层下来对应采样 9 3 = 729 9^3=729 93=729个点(红色)。从左到右分别是背景、小物体、大物体的采样效果。
在这里插入图片描述
表2提供了这种自适应变形的定量证据。
在这里插入图片描述
可变形RoI池化的效果类似,如图7所示。网格结构的规律性在标准RoI池中不再适用。相反,部分偏离RoI bin,移动到附近的对象前景区域。增强了定位能力,特别是对非刚体的定位能力。
在这里插入图片描述


思考

为什么叫做可变形卷积呢

在这里插入图片描述
可变形卷积就是给每个卷积核添加一个方向向量,使得卷积核可以自适应的变成任意的形状,因此也叫做”可变形卷积。

可变形卷积有什么用呢?

同一层的CNN的激活单元的感受野尺度相同,传统的方法的感受野位置受限。

不同的位置对应着不同尺度和形变的物体,卷积层需要能够自动的调整尺度和感受野,更好的提取输入的特征
在这里插入图片描述
最上层是在大小不同的两个物体上的一个激活单元,中间层和底层是为了得到上一层激活单元的采样过程。可以看到可变形卷积在采样时可以更贴近物体的形状和尺寸。
在这里插入图片描述

代码详解

class DeformConv2D(nn.Module) 就是定义了二维可变形卷积的类。下面我们分别介绍该类中各个方法。


__init__

  • 功能: 初始化
class DeformConv2D(nn.Module):
    def __init__(self, inc, outc, kernel_size=3, padding=1, bias=None):
        super(DeformConv2D, self).__init__()
        self.kernel_size = kernel_size # 卷积核的大小
        self.padding = padding # 填充大小
        self.zero_padding = nn.ZeroPad2d(padding) # 用0去填充
        self.conv_kernel = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias) # 二维卷积

forward

  • 功能:定义前向传播的过程
    def forward(self, x, offset):
    	'''
    	x: 原始输入 [b,c,h,w]
    	offset: 每个像素点的偏移 [b,2*N,h,w]
    	N:kernel中元素的个数 = k*k
    	offset 和 x的宽高相同,表示的是对应位置像素点的偏移 
    	offset 的第二个维度大小是2N, 当N=9时,排列顺序是(x1, y1, x2, y2, ...x18,y18)
    	xi表示一个大小为[h,w]的张量,表示对于原始图像中的每个点,对于kernel中的第i个元素,在x轴方向的偏移量
    	yi表示一个大小为[h,w]的张量,表示对于原始图像中的每个点,对于kernel中的第i个元素,在y轴方向的偏移量
    	'''
        dtype = offset.data.type()
        ks = self.kernel_size
        N = offset.size(1) // 2
        
		'''
        下面这段代码的整体功能:将offset中第二维度的顺序从[x1, y1, x2, y2, ...] 变成[x1, x2, .... y1, y2, ...]
        '''
        offsets_index = Variable(torch.cat([torch.arange(0, 2*N, 2), torch.arange(1, 2*N+1, 2)]), requires_grad=False).type_as(x).long()
        # 当b=1,N=9时,offsets_index=[ 0,  2,  4,  6,  8, 10, 12, 14, 16,  1,  3,  5,  7,  9, 11, 13, 15, 17]  
        # offsets_index的大小为[18]
        offsets_index = offsets_index.unsqueeze(dim=0).unsqueeze(dim=-1).unsqueeze(dim=-1).expand(*offset.size())
        # 然后unsqueeze扩展维度,offsets_index大小为[1,18,1,1]
        # expand后,offsets_index的大小为[1,18,h,w]
        offset = torch.gather(offset, dim=1, index=offsets_index)
        # 根据维度dim按照索引列表index将offset重新排序,得到[x1, x2, .... y1, y2, ...]这样顺序的offset
        # ------------------------------------------------------------------------

        # 对输入x进行padding
        if self.padding:
            x = self.zero_padding(x)

        # p表示求偏置后,每个点的位置
        p = self._get_p(offset, dtype)# (b, 2N, h, w)
        p = p.contiguous().permute(0, 2, 3, 1) # (b,h,w,2N)
        
        q_lt = Variable(p.data, requires_grad=False).floor()  #floor是向下取整
        q_rb = q_lt + 1 #  上取整
        # +1相当于向上取整,这里为什么不用向上取整函数呢?是因为如果正好是整数的话,向上取整跟向下取整就重合了,这是我们不想看到的。

        # q_lt[..., :N]代表x方向坐标,大小[b,h,w,N], clamp将值限制在0~h-1
        # q_lt[..., N:]代表y方向坐标, 大小[b,h,w,N], clamp将值限制在0~w-1
        # cat后,还原成原大小[b,h,w,2N]
        q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2)-1), torch.clamp(q_lt[..., N:], 0, x.size(3)-1)], dim=-1).long() # 将q_lt中的值控制在图像大小范围内 [b,h,w,2N]
        q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2)-1), torch.clamp(q_rb[..., N:], 0, x.size(3)-1)], dim=-1).long() # 将q_rt中的值控制在图像大小范围内 [b,h,w,2N]
        '''
        获取采样后的点周围4个方向的像素点
        q_lt:  left_top 左上
        q_rb:  right_below 右下
        q_lb:  left_below 左下
        q_rt:  right_top 右上
		'''
        # 获得lb
        q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], -1) # [b,h,w,2N]
        # 获得rt
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], -1) # [b,h,w,2N]
        '''
        插值的时候需要考虑一下padding对原始索引的影响 
        p[..., :N]  采样点在x方向(h)的位置   大小[b,h,w,N]
        p[..., :N].lt(self.padding) : p[..., :N]中小于padding 的元素,对应的mask为true
        p[..., :N].gt(x.size(2)-1-self.padding): p[..., :N]中大于h-1-padding 的元素,对应的mask为true 
        
       	p[..., N:]  采样点在y方向(w)的位置   大小[b,h,w,N]
        p[..., N:].lt(self.padding) : p[..., N:]中小于padding 的元素,对应的mask为true
        p[..., N:].gt(x.size(2)-1-self.padding): p[..., N:]中大于w-1-padding 的元素,对应的mask为true 
        cat之后,大小为[b,h,w,2N]
        '''
        mask = torch.cat([p[..., :N].lt(self.padding)+p[..., :N].gt(x.size(2)-1-self.padding),
                          p[..., N:].lt(self.padding)+p[..., N:].gt(x.size(3)-1-self.padding)], dim=-1).type_as(p) # 
        #mask不需要反向传播
        mask = mask.detach()
		#p - (p - torch.floor(p))相当于torch.floor(p)
        floor_p = p - (p - torch.floor(p))
       
        
        '''
        mask为1的区域就是padding的区域
        p*(1-mask) : mask为0的 非padding区域的p被保留
        floor_p*mask: mask为1的  padding区域的floor_p被保留
		'''
        p = p*(1-mask) + floor_p*mask
        p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2)-1), torch.clamp(p[..., N:], 0, x.size(3)-1)], dim=-1)


        # 双线性插值的系数 大小均为 (b, h, w, N)
        g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:])) 
        # (1+左上角的点x - 原始采样点x)*(1+左上角的点y - 原始采样点y)
        g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:])) 
        # (1-(右下角的点x - 原始采样点x))*(1-(右下角的点y - 原始采样点y))
        g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:])) 
        # (1+左下角的点x - 原始采样点x)*(1+左上角的点y - 原始采样点y)
        g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:])) 
        # (1-(右上角的点x - 原始采样点x))*(1-(右上角的点y - 原始采样点y))

        # (b, c, h, w, N)
        x_q_lt = self._get_x_q(x, q_lt, N) # 左上角的点在原始图片中对应的真实像素值
        x_q_rb = self._get_x_q(x, q_rb, N) # 右下角的点在原始图片中对应的真实像素值
        x_q_lb = self._get_x_q(x, q_lb, N) # 左下角的点在原始图片中对应的真实像素值
        x_q_rt = self._get_x_q(x, q_rt, N) # 右上角的点在原始图片中对应的真实像素值

       
        # 双线性插值算法
        # x_offset : 偏移后的点再双线性插值后的值  大小(b, c, h, w, N)
        x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \
                   g_rb.unsqueeze(dim=1) * x_q_rb + \
                   g_lb.unsqueeze(dim=1) * x_q_lb + \
                   g_rt.unsqueeze(dim=1) * x_q_rt
		'''
        偏置点含有九个方向的偏置,_reshape_x_offset() 把每个点9个方向的偏置转化成 3×3 的形式,
        于是就可以用 3×3 stride=3 的卷积核进行 Deformable Convolution,
        它等价于使用 1×1 的正常卷积核(包含了这个点9个方向的 context)对原特征直接进行卷积。
        '''
        x_offset = self._reshape_x_offset(x_offset, ks) #(b,c,h*ks,w*ks)
        
        out = self.conv_kernel(x_offset)

        return out

_get_p_n

  • 功能:求每个点的偏置方向
    #功能:求每个点的偏置方向
    def _get_p_n(self, N, dtype):
    	# N=kernel_size*kernel_size
        p_n_x, p_n_y = np.meshgrid(range(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1),
                          range(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1), indexing='ij')
        # (2N, 1)
        p_n = np.concatenate((p_n_x.flatten(), p_n_y.flatten()))
        p_n = np.reshape(p_n, (1, 2*N, 1, 1))
        p_n = Variable(torch.from_numpy(p_n).type(dtype), requires_grad=False)

        return p_n # [1,2*N,1,1]

meshgrid后,p_n_x和p_n_y的值为
在这里插入图片描述
flatten后,p_n_x和p_n_y的值为
在这里插入图片描述
concatenate后,p_n的值为
在这里插入图片描述
reshape,p_n的值为:
在这里插入图片描述
当kernel_size=3时,N=3*3=9 , p_n的大小是[1,18,1,1] (即[1,2*N,1,1])
其中在第二个维度,排列的顺序是·(x1,x2,…,x9,y1,y2,…y9) 。其中xi,yi分别表示kernel中的第i个元素相对于中心点在x方向和y方向的偏移量。


_get_p_0

  • 功能: 求每个点的坐标
    @staticmethod
    #功能:求每个点的坐标
    def _get_p_0(h, w, N, dtype):
        p_0_x, p_0_y = np.meshgrid(range(1, h+1), range(1, w+1), indexing='ij')
        p_0_x = p_0_x.flatten().reshape(1, 1, h, w).repeat(N, axis=1) # (1,N,1,1)
        p_0_y = p_0_y.flatten().reshape(1, 1, h, w).repeat(N, axis=1) # (1,N,1,1)
        p_0 = np.concatenate((p_0_x, p_0_y), axis=1)
        p_0 = Variable(torch.from_numpy(p_0).type(dtype), requires_grad=False)

        return p_0 # (1,2*N,1,1)

假设h=3, w=4
meshgrid后,p_0_x, p_0_y的值为
在这里插入图片描述
p_0_x,p_0_y的大小均为[h,w]
flatten后,大小变成[h*w]
reshape后,大小变成[1,1,h,w]
repeat后大小变为[1,9,3,4]
p_0_x的值为:
在这里插入图片描述
p_0_y的值为:
在这里插入图片描述
把p_0_x和p_0_y在dim=1上concatenate后,p_0的大小为[1,18,3,4] , 即[b,2*N,h,w]


_get_p
功能:求最后的偏置后的点的位置 = 每个点的坐标(p_0)+偏置方向(p_n)+偏置(offset)

    #求最后的偏置后的点=每个点的坐标+偏置方向+偏置
    def _get_p(self, offset, dtype):
        '''
        offset: 每个像素点的偏移 [b,2*N,h,w]
    	N:kernel中元素的个数 = k*k
    	offset 和 x的宽高相同,表示的是对应位置像素点的偏移 
    	offset 的第二个维度大小是2N, 当N=9时,排列顺序是(x1, y1, x2, y2, ...x18,y18)
    	xi表示一个大小为[h,w]的张量,表示对于原始图像中的每个点,对于kernel中的第i个元素,在x轴方向的偏移量
    	yi表示一个大小为[h,w]的张量,表示对于原始图像中的每个点,对于kernel中的第i个元素,在y轴方向的偏移量
        '''
        N, h, w = offset.size(1)//2, offset.size(2), offset.size(3)
        p_n = self._get_p_n(N, dtype)  # 偏置方向:(1, 2N, 1, 1)
        p_0 = self._get_p_0(h, w, N, dtype) # 每个点的坐标:(1, 2N, h, w)
        p = p_0 + p_n + offset # 最终点的位置
        return p # (1,2N,h,w)

_get_x_q

  • 功能: 求采样点周围4个点在原始图像中对应的真实像素值
    #求出p点周围四个点的像素
    def _get_x_q(self, x, q, N):
    	# x:[b,c,h',w']
    	# q:[b,h,w,2N] 
    	# q可能为q_lt,q_rt,q_lb,q_rb
        b, h, w, _ = q.size()
        padded_w = x.size(3) # w'
        c = x.size(1)
        x = x.contiguous().view(b, c, -1) # (b, c, h*w)
        # 将图片压缩到1维,方便后面的按照index索引提取
		
		# q[...,:N]  (b,h,w,N) 原始图像中(h_i,w_j)的点在偏移后,向左上角取整对应的点,在N个区域中,x方向的偏移量
		# q[...,:N]  (b,h,w,N) 原始图像中(h_i,w_j)的点在偏移后,向左上角取整对应的点,在N个区域中,y方向的偏移量
		# index:  (b,h,w,N) 原始图像中(h_i,w_j)的点在偏移后,向左上角取整对应的点,在N个区域中,x*w + y
        index = q[..., :N]*padded_w + q[..., N:]  #    大小(b, h, w, N)
        # 这个目的就是将index索引均匀扩增到图片一样的h*w大小
        
        index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)
        '''
        unsqueeze后 (b,1,h,w,N)
        expand后 (b,c,h,w,N)
        view后 (b, c, h*w*N) 其中每一个值对应一个index
        '''
        #双线性插值法就是4个点再乘以对应与 p 点的距离。获得偏置点 p 的值,这个 p 点是 9 个方向的偏置所以最后的 x_offset 是 b×c×h×w×9。
        x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)
        # x :(b,c,h*w)
        # gather后: (b,c,h*w*N)
        # view后:(b,c,h,w,N)
        return x_offset #(b,c,h,w,N) 左上角的点在原始图像中对应的像素值

_reshape_x_offset()

  • 功能:每个点9个方向的偏置转化成 3×3 的形式
    #_reshape_x_offset() 把每个点9个方向的偏置转化成 3×3 的形式
    @staticmethod
    def _reshape_x_offset(x_offset, ks):
    	# x_offset : (b, c, h, w, N)
    	# ks: kernel_size
    	# N=ks*ks
        b, c, h, w, N = x_offset.size()
        '''
        当ks=3,N=9时:
        s=0 [...,0:3]  (b,c,h,w,3)->(b,c,h,w*3)
        s=3 [...,3:6]  (b,c,h,w,3)->(b,c,h,w*3)
        s=6 [...,6:9]  (b,c,h,w,3)->(b,c,h,w*3)
        cat 后 (b,c,h,w*9)
        view 后(b,c,h*3,w*3)
		'''
        x_offset = torch.cat([x_offset[..., s:s+ks].contiguous().view(b, c, h, w*ks) for s in range(0, N, ks)], dim=-1)
        x_offset = x_offset.contiguous().view(b, c, h*ks, w*ks) # (b,c,h*3,w*3)

        return x_offset #(b,c,h*ks,w*ks)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/890362.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql 数据备份和恢复

操作系统&#xff1a;22.04.1-Ubuntu mysql 版本&#xff1a;8.033 binlog 介绍 binlog 是mysql 二进制日志 binary log的简称&#xff0c;可以简单理解为数据的修改记录。 需要开启binlog,才会产生文件&#xff0c;mysql 8.0 默认开启,开启后可以在 /var/lib/mysql &#xff…

世界上最著名的密码学夫妻的历史

Alice和Bob是密码学领域里最著名的虚拟夫妻&#xff0c;自1978年“诞生”以来&#xff0c;到走进二十一世纪的移动互联网时代&#xff0c;作为虚构的故事主角&#xff0c;Alice和Bob不仅在计算机理论、逻辑学、量子计算等与密码学相关的领域中得到应用&#xff0c;他们的名字也…

ESP-C3入门21. I2C接口点亮1306驱动的OLED屏

ESP-C3入门21. 点亮1306驱动的OLED屏 一、Espressif/ssd1306 驱动简介1. 驱动介绍2. OLED充电泵概念 二、I2C 通讯步骤1. 初始化 I2C 总线 (i2c_master_init()函数)&#xff1a;2. 创建 I2C 命令句柄 (i2c_cmd_handle_t cmd i2c_cmd_link_create())&#xff1a;3. 发送启动信号…

异步更新队列 - Vue2 响应式

前言 这篇文章分析了 Vue 更新过程中使用的异步更新队列的相关代码。通过对异步更新队列的研究和学习&#xff0c;加深对 Vue 更新机制的理解 什么是异步更新队列 先看看下面的例子&#xff1a; <div id"app"><div id"div" v-if"isShow&…

有哪些好用的设计图工具?

设计图纸制作软件是高级学习数字设计的最佳选择&#xff0c;无论你是想通过设计图纸制作软件创建一个明亮的设计&#xff0c;还是与其他设计师分享和交流。本文将介绍十个易于使用的设计图纸制作软件&#xff0c;其中大多数是初学者和高级艺术家&#xff0c;具有完整的绘图、照…

科研论文配图绘制指南——基于Python—第一章

目录 第一章1.1科研论文配图的绘制基础1.2科研论文配图的配色基础1.2.1 色轮配色原理1.2.3 颜色主题1.2.4 配色工具 总结 第一章 1.1科研论文配图的绘制基础 科研配图包括线性图、灰度图、照片彩图和综合配图四种类型&#xff0c;最经常使用为线型图。 建议使用EPS、PDF等矢量…

C语言:每日一练(选择+编程)

目录 选择题&#xff1a; 题一&#xff1a; 题二&#xff1a; 题三&#xff1a; 题四&#xff1a; 题五&#xff1a; 编程题&#xff1a; 题一&#xff1a;打印1到最大的n位数 示例1 思路一&#xff1a; 题二&#xff1a;计算日期到天数转换 示例1 思路一&#xf…

SwiftUI 动画进阶:实现行星绕圆周轨道运动

0. 概览 SwiftUI 动画对于优秀 App 可以说是布帛菽粟。利用美妙的动画我们不仅可以活跃界面元素,更可以单独打造出一整套生动有机的世界,激活无限可能。 如上图所示,我们用动画粗略实现了一个小太阳系:8大行星围绕太阳旋转,而卫星们围绕各个行星旋转。 在本篇博文中,您将…

华为网络篇 RIP路由标记-31

难度2复杂度2 目录 一、实验原理 二、实验拓扑 三、实验步骤 四、实验过程 总结 一、实验原理 路由标记tag是用于进行路由过滤的&#xff0c;它给相关的路由打标记&#xff0c;然后应用于路由策略中&#xff0c;路由器会根据策略进行路由过滤。比如&#xff0c;给静态路由…

题目:售货员的难题(状压dp)

售货员的难题 题目描述输入输出格式输入格式&#xff1a;输出格式&#xff1a; 输入输出样例输入样例#1&#xff1a;输出样例#1&#xff1a; 思路AC代码&#xff1a; 题目描述 某乡有n个村庄( 1 < n < 16 )&#xff0c;有一个售货员&#xff0c;他要到各个村庄去售货&am…

Smartbi 李代:人尽其才、数尽其用,Smartbi Eagle智慧数据运营平台全新亮相

数据是企业数字化转型的基石&#xff0c;也是赢得未来的核心资产和竞争力。数字化转型的关键&#xff0c;是在全公司建立一种数据驱动的组织和机制&#xff0c;营造数据文化的氛围&#xff0c;让更多的用户、在更多的场景中&#xff0c;有意愿、有能力使用数据&#xff0c;从而…

【C++】面向对象编程引入 ③ ( 面向过程编程的结构化程序设计方法 | 结构化程序设计方法概念 / 特点 / 优缺点 | 面向对象编程引入 )

文章目录 一、面向过程编程的结构化程序设计方法1、结构化程序设计方法概念2、结构化程序设计方法特点3、结构化程序设计方法优缺点 二、面向对象编程引入 一、面向过程编程的结构化程序设计方法 如果使用 面向过程语言 ( 如 : C 语言 ) , 开发 大型 项目 , 一般使用 结构化程序…

Apache SeaTunnel社区迎来新Committer!

采访&编辑 | Debra Chen 个人简介 姓名&#xff1a;马骋原公司&#xff1a;恒生电子 GitHub ID&#xff1a;rewerma个人擅长研究领域&#xff1a;java中间件、微服务、大数据等 您为社区提交了什么贡献&#xff1f;具体方案可以描述一下吗&#xff1f; 为SeatTunnel提交…

案例21 基于Spring Boot+Redis实现图书信息按书号存储案例

1. 案例需求 基于Spring BootRedis实现图书信息按书号存储和取出功能&#xff0c;数据存储至Redis。 2. 创建Spring Boot项目 创建Spring Boot项目&#xff0c;项目名称为springboot-redis02。 3. 选择依赖 ​ pom.xml文件内容如下所示&#xff1a; <?xml version&quo…

入门Web自动化测试之元素定位的配置管理

之前我们讲过Selenium使用教程&#xff0c;这一篇我们来学习元素定位的配置管理。 目的 Web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”&#xff0c;通常都会作为广大测试从业者的首选学习对象&#xff0c;相较于C/S架构的自动化来说&#xff0c;B/S有着其无…

AI巨浪下,数据技术如何驱动智能未来?

引言 数据技术是大数据时代的核心驱动力&#xff0c;也是推动各行各业数字化转型和智能化升级的关键因素。随着云计算、人工智能、区块链等新兴技术的不断发展和融合&#xff0c;数据技术也呈现出多模态、混合处理、自动化管理等新的趋势和特点。 8 月 19 日&#xff08;周六&…

UniApp 制作高德地图插件

1、下载Uni插件项目 在Uni官网下载Uni插件项目&#xff0c;并参考官网插件项目创建插件项目. 开发者须知 | uni小程序SDK 如果下载下来项目运行不了可以参考下面链接进行处理 UniApp原生插件制作_wangdaoyin2010的博客-CSDN博客 2、引入高德SDK 2.1 在高德官网下载对应SD…

e6zzseo:跨境独立站还能做起来吗?

跨境独立站指的是在其他国家或地区创建和运营自己的电子商务网站。虽然跨境独立站在理论上是可行的&#xff0c;但成功实施和运营它可能面临一些挑战。以下是e6zzseo分析的一些考虑因素和建议&#xff0c;以帮助你更好地评估是否可以成功运营跨境独立站&#xff1a; 做跨境独立…

【0基础入门Python笔记】python 之基础语法、基础数据类型、复合数据类型及基本操作

python 基础&#xff08;一&#xff09; 基础语法规则基础数据类型数字类型&#xff08;Numbers&#xff09;字符串类型&#xff08;String&#xff09;布尔类型&#xff08;Boolean&#xff09; 复合数据类型List&#xff08;列表&#xff09;Tuple&#xff08;元组&#xff0…

excel常见的数学函数篇2

二、数学函数 1、ABS(number)&#xff1a;返回数字的绝对值 语法&#xff1a;ABS(数字)&#xff1b;返回数字的绝对值&#xff1b;若引用单元格&#xff0c;把数字换为单元格地址即可 2、INT(number)&#xff1a;向小取整 语法&#xff1a;INT(数字)&#xff1b;若引用单元格…