狗都能看懂的可变形卷积详解

Deformable Convolution Networks

论文地址:https://arxiv.org/pdf/1703.06211
官方源码:https://github.com/msracver/Deformable-ConvNets/tree/master

Deformable Convolution

conv
文章提出了可变形卷积和可变形ROI采样。原理是一样的,这里先讲解一下可变形卷积,传统的卷积如上图a所示吗,采样点是固定的9个位置,bcd是对应的可变形卷积,b是比较常见的状态,9个采样点位置随机。cd是可变形卷积比较特殊的情形。

compare.png
这么做有什么好处呢?如上图所示,左边是普通卷积,卷积位置固定,模型关注的位置被限定成一个方形。右边是可变形卷积,由于偏移量是一个可学习参数,模型关注的形状可以是任意外形。

可变形卷积是怎么做的呢?从下图可以看到,只要计算出一个offsets偏移即可。

d-conv
如果不考虑batch和channel的维度,假设有一个 H × W H \times W H×W的feature map,对于任意一个采样点的偏移,都需要x方向和y方向的偏移,仅仅这样还不够。由于卷积核是 k × k k \times k k×k大小的,卷积核上的每个采样点都要有偏移。所以每个点必须要有 2 × k × k 2 \times k \times k 2×k×k个offsets。所以生成的offsets的尺寸为 H × W × 2 k 2 H \times W \times 2k^2 H×W×2k2

在mmcv中使用可变形卷积,先利用常规的conv计算出offsets变量:

import torch
import torch.nn as nn
from mmcv.ops import DeformConv2d

class DeformableConvNet(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=3):
        super(DeformableConvNet, self).__init__()
        self.offsets = nn.Conv2d(in_channels, 2 * kernel_size * kernel_size, kernel_size=kernel_size, padding=1)
        self.deform_conv = DeformConv2d(in_channels, out_channels, kernel_size=kernel_size, padding=1)

    def forward(self, x):
        offsets = self.offsets(x)  # 生成偏移量
        x = self.deform_conv(x, offsets)  # 可变形卷积操作
        return x

# 示例
input = torch.randn(1, 3, 64, 64)
model = DeformableConvNet(in_channels=3, out_channels=64, kernel_size=3)
output = model(input)

Deformable RoI Pooling

ROI-Pooling.png
应用到ROI Pooling也是一样的,而且ROI Pooling相较于普通的Pooling,不需要进行移动和多次采样。所以直接是用一个全连接层,输出的offsets特征通道数是 2 × h × w 2 \times h \times w 2×h×w,其中 h h h w w w是输出ROI的特征图的大小,2表示x,y方向的偏移。具体来说,输出的offsets tensor形状为 ( N , 2 × h × w , H , W ) (N,2 \times h \times w, H,W) (N,2×h×w,H,W),其中 N N N是batch的大小。

文章最后给出了验证集中的样例对比。左边第一张图是普通卷积,由于采样点位置很固定,那么模型实际上的关注点都是很分散的,模型无法聚焦到主体位置上。如果把普通卷积换成可变形卷积,效果就是右边两张图。由于可变形卷积的偏移量是可学习的,经过训练后,模型的关注点都会集中到主要物体上。

image.png

### 可变形卷积神经网络概述 #### 概念 可变形卷积(Deformable Convolutional Networks, DCN)是对传统卷积操作的一种扩展,旨在解决固定感受野无法有效捕捉复杂几何变换的问题。通过引入额外的学习参数——偏移量(offset),使得卷积核可以在空间上自适应调整位置,从而更好地匹配目标物体的形状变化[^1]。 #### 原理 在标准卷积中,滤波器以固定的网格形式作用于输入特征图上的特定区域。而可变形卷积则允许这些采样点的位置发生位移,即每个像素点处都有一个对应的二维偏移向量Δp_k=(δx_k, δy_k)^T。具体来说,在执行一次卷积运算之前先计算出一组偏移值,并将其加到原本应该访问的位置上去读取数据。这样做的好处是可以让模型自动学习如何改变自己的视窗大小和方向来适应不同尺度或姿态的目标对象[^2]。 为了实现这一点,通常会在原有基础上增加一层专门用于预测偏移场的小型子网。该子网接受相同尺寸但通道数更多的输入作为条件信息源,经过几轮简单的线性组合后输出所需维度的结果给主干部分使用。值得注意的是,由于涉及到连续坐标的插值问题,实际应用时常采用双线性差值法近似求解最终响应强度[^3]。 ```python import torch.nn as nn class DeformConv(nn.Module): def __init__(self, inc, outc, kernel_size=3, padding=1, stride=1, bias=None, modulation=False): super(DeformConv, self).__init__() self.kernel_size = kernel_size self.padding = padding self.stride = stride self.zero_padding = nn.ZeroPad2d(padding) self.conv = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias) self.p_conv = nn.Conv2d(inc, 2*kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride) nn.init.constant_(self.p_conv.weight, 0) self.p_conv.register_backward_hook(self._set_lr) self.modulation = modulation if modulation: self.m_conv = nn.Conv2d(inc, kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride) nn.init.constant_(self.m_conv.weight, 0) self.m_conv.register_backward_hook(self._set_lr) @staticmethod def _set_lr(module, grad_input, grad_output): grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input))) grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output))) return grad_input, grad_output def forward(self, x): offset = self.p_conv(x) if self.modulation: m = torch.sigmoid(self.m_conv(x)) dtype = offset.data.type() ks = self.kernel_size N = offset.size(1) // 2 if self.padding: x = self.zero_padding(x) # (b, 2N, h, w) p = self._get_p(offset, dtype) # (b, h, w, 2N) p = p.contiguous().permute(0, 2, 3, 1) q_lt = p.detach().floor() q_rb = q_lt + 1 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_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_lb = torch.stack([q_lt[..., :N], q_rb[..., N:]], -1) q_rt = torch.stack([q_rb[..., :N], q_lt[..., N:]], -1) # clip p p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2)-1), torch.clamp(p[..., N:], 0, x.size(3)-1)], dim=-1) # bilinear kernel (b, h, w, N) g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:])) g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:])) g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:])) g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:])) # (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) # (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 if self.modulation: m = m.expand(-1,-1,m.size(2)*m.size(3)) m = m.reshape(m.size(0),m.size(1)//ks//ks,ks,ks,m.size(2),m.size(3)).transpose(2,4).contiguous() m = m.view(m.size(0),m.size(1)*m.size(4),m.size(2)*m.size(3),m.size(5)).transpose(2,3).reshape(m.size(0),m.size(1)*m.size(3),m.size(2)*m.size(4)) mask = m.masked_fill(m<0.5,0.).masked_fill(m>=0.5,1.) x_offset *= mask x_offset = self._reshape_x_offset(x_offset, ks) out = self.conv(x_offset) return out def _get_p_n(self, N, dtype): p_n_x, p_n_y = torch.meshgrid( torch.arange(-(self.kernel_size-1)//2, (self.kernel_size
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值