Deformable Convolutions
Deformable convolutions (Dai et al., Microsoft Research, 2017) augment standard convolutional neural networks with the ability to learn where to look, not just what to look for. In a standard convolution, the sampling grid is a fixed rectangular pattern — the same $3 \times 3$ kernel is applied at the same relative offsets to every spatial location. Deformable convolutions add learnable 2D offsets to each sampling location, allowing the receptive field to adapt to the shape, scale, and pose of objects in the image.
This seemingly small change has significant practical impact: it allows networks to model complex geometric transformations without requiring data augmentation or specialized architectures for each transformation type.
Standard vs. Deformable Convolution
Standard convolution at output location $\mathbf{p}_0$:
$$y(\mathbf{p}0) = \sum{\mathbf{p}_n \in \mathcal{R}} w(\mathbf{p}_n) \cdot x(\mathbf{p}_0 + \mathbf{p}_n)$$
where $\mathcal{R} = {(-1,-1), (-1,0), \ldots, (1,1)}$ is the fixed $3 \times 3$ sampling grid.
Deformable convolution adds a learned offset $\Delta\mathbf{p}_n$ to each sampling location:
$$y(\mathbf{p}0) = \sum{\mathbf{p}_n \in \mathcal{R}} w(\mathbf{p}_n) \cdot x(\mathbf{p}_0 + \mathbf{p}_n + \Delta\mathbf{p}_n)$$
The offset $\Delta\mathbf{p}_n \in \mathbb{R}^2$ is fractional — the displaced position $\mathbf{p}_0 + \mathbf{p}_n + \Delta\mathbf{p}_n$ is not generally on an integer pixel location. Bilinear interpolation resolves this:
$$x(\mathbf{p}) = \sum_{\mathbf{q}} G(\mathbf{q}, \mathbf{p}) \cdot x(\mathbf{q})$$
where $G(\mathbf{q}, \mathbf{p}) = \max(0, 1 - |q_x - p_x|) \cdot \max(0, 1 - |q_y - p_y|)$ is the bilinear kernel and $\mathbf{q}$ sums over all integer locations.
The offsets are predicted by a separate lightweight convolutional branch applied to the same input feature map — making the entire operation end-to-end trainable via backpropagation through bilinear interpolation.
Deformable ConvNets v2: Modulated Deformable Convolutions
DCNv2 (Zhu et al., 2019) extends DCNv1 with modulation scalars $\Delta m_k \in [0, 1]$ per sampling point — allowing the network to not only shift where it samples but also to suppress or amplify the contribution of each location:
$$y(\mathbf{p}0) = \sum{k=1}^{K} w_k \cdot x(\mathbf{p}_0 + \mathbf{p}_k + \Delta\mathbf{p}_k) \cdot \Delta m_k$$
With both offsets and modulation, the network can effectively ignore irrelevant sampling locations ($\Delta m_k \approx 0$) and focus on the most informative ones. This is analogous to spatial attention but tightly integrated into the convolution operation.
Implementation in PyTorch
PyTorch’s torchvision.ops.deform_conv2d provides an efficient CUDA implementation. Here is a module wrapping it:
import torch
import torch.nn as nn
from torchvision.ops import deform_conv2d
class DeformableConv2d(nn.Module):
"""
Modulated Deformable Convolution (DCNv2).
Learns per-sample-location offsets AND modulation scalars.
"""
def __init__(self, in_channels: int, out_channels: int,
kernel_size: int = 3, stride: int = 1,
padding: int = 1, bias: bool = True):
super().__init__()
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
# Main convolution weights
self.weight = nn.Parameter(
torch.empty(out_channels, in_channels, kernel_size, kernel_size)
)
self.bias = nn.Parameter(torch.zeros(out_channels)) if bias else None
# Offset + mask prediction: 2*k*k offsets (x,y per location) + k*k masks
self.offset_mask_conv = nn.Conv2d(
in_channels,
3 * kernel_size * kernel_size, # 2 for offsets (x,y), 1 for mask per location
kernel_size=kernel_size,
stride=stride,
padding=padding
)
nn.init.kaiming_uniform_(self.weight, nonlinearity="relu")
nn.init.zeros_(self.offset_mask_conv.weight)
nn.init.zeros_(self.offset_mask_conv.bias) # Initialize offsets to zero = standard conv
def forward(self, x: torch.Tensor) -> torch.Tensor:
B, C, H, W = x.shape
k2 = self.kernel_size * self.kernel_size
# Predict offsets and modulation masks from input
offset_mask = self.offset_mask_conv(x)
# Split: first 2*k^2 channels are offsets, last k^2 are masks
offset = offset_mask[:, :2 * k2, :, :] # (B, 2*k^2, H, W)
mask = offset_mask[:, 2 * k2:, :, :].sigmoid() # (B, k^2, H, W), range [0,1]
return deform_conv2d(
input=x,
offset=offset,
weight=self.weight,
bias=self.bias,
stride=self.stride,
padding=self.padding,
mask=mask
)
class DeformableResBlock(nn.Module):
"""
ResNet bottleneck block with deformable convolution replacing the 3x3 conv.
Commonly used to upgrade ResNet-50/101 backbones in detectors.
"""
def __init__(self, channels: int):
super().__init__()
self.conv1 = nn.Conv2d(channels, channels // 4, 1)
self.bn1 = nn.BatchNorm2d(channels // 4)
# Replace standard 3x3 with deformable 3x3
self.deform_conv = DeformableConv2d(channels // 4, channels // 4, 3, padding=1)
self.bn2 = nn.BatchNorm2d(channels // 4)
self.conv3 = nn.Conv2d(channels // 4, channels, 1)
self.bn3 = nn.BatchNorm2d(channels)
self.relu = nn.ReLU(inplace=True)
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = x
out = self.relu(self.bn1(self.conv1(x)))
out = self.relu(self.bn2(self.deform_conv(out)))
out = self.bn3(self.conv3(out))
return self.relu(out + residual)
Visualizing Learned Offsets
The sampling patterns learned by deformable convolutions are interpretable — and reveal that the network learns semantically meaningful receptive fields:
import matplotlib.pyplot as plt
import numpy as np
def visualize_offsets(offset: torch.Tensor, img: torch.Tensor,
location: tuple = (50, 50), kernel_size: int = 3):
"""
Visualize the deformed sampling grid at a specific image location.
offset: (B, 2*k^2, H, W) — learned offsets
location: (row, col) — the output location to inspect
"""
row, col = location
k = kernel_size
k2 = k * k
# Standard grid (no deformation)
offsets_y = np.array([-1, -1, -1, 0, 0, 0, 1, 1, 1])
offsets_x = np.array([-1, 0, 1, -1, 0, 1, -1, 0, 1])
# Extract learned offsets at this location
off = offset[0, :, row, col].detach().cpu().numpy()
delta_y = off[:k2]
delta_x = off[k2:2*k2]
standard_y = row + offsets_y
standard_x = col + offsets_x
deformed_y = row + offsets_y + delta_y
deformed_x = col + offsets_x + delta_x
img_np = img[0].permute(1, 2, 0).detach().cpu().numpy()
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for ax, (pts_y, pts_x, title) in zip(axes, [
(standard_y, standard_x, "Standard Grid"),
(deformed_y, deformed_x, "Deformed Grid")
]):
ax.imshow(img_np)
ax.scatter(pts_x, pts_y, c="red", s=80, zorder=5)
ax.scatter([col], [row], c="blue", s=120, marker="*", zorder=6, label="center")
ax.set_title(title)
ax.legend()
plt.tight_layout()
plt.savefig("deformable_offsets.png", dpi=100)
When visualized on object detection tasks, the sampling points of deformable convolutions group around object parts — for example, centering on the joints of a person, the wheels of a car, or the edges of a text bounding box.
Applications and Impact
Object detection: Deformable convolutions are integrated into most top-performing detection architectures. Faster R-CNN and FCOS with DCNv2 backbones consistently outperform their standard CNN counterparts on COCO by 2–4 AP points.
Semantic segmentation: DeepLab and similar architectures use deformable convolutions to adaptively capture multi-scale context — complementing or replacing dilated (atrous) convolutions.
Deformable DETR: Extends the DETR (Detection Transformer) architecture with multi-scale deformable attention, attending to a small set of learned key sampling points around each query rather than all spatial locations — reducing the $O(H^2W^2)$ attention complexity that made vanilla DETR slow to converge.
Pose estimation: Adaptively sampling around body joints and limbs improves robustness to unusual poses.
Deformable Attention vs. Deformable Convolution
Deformable attention (used in Deformable DETR) is conceptually related but distinct:
- Deformable convolution: Applies fixed learned weights at shifted locations — local, translation-equivariant.
- Deformable attention: Each query predicts reference points and attends to them with content-dependent attention weights — global, not equivariant, each query can look anywhere.
Both represent the same insight — adaptive, input-conditioned sampling patterns outperform rigid grids — but operate in fundamentally different architectural contexts.
Deformable convolutions remain a practical, low-overhead upgrade for any CNN backbone when geometric invariance is important. Replacing the $3 \times 3$ convolutions in the last few stages of a ResNet backbone with DCNv2 adds fewer than 1% parameters while consistently improving accuracy on detection and segmentation benchmarks.