源自Deformable Convolutional Networks的一种可变形卷积实现解析
衍生记录:深度学习pytorch之简单方法自定义9类卷积即插即用
文章目录
- 概述
- 1. 可变形卷积的背景
- 2. DeformConv2D概述
- 2.1 构造函数分析
- 2.2 前向传播函数解析
- 2.2.1 偏移量的计算与应用
- 2.2.2 目标位置的计算
- 2.2.3 四个角的插值
- 2.2.4 双线性插值的权重
- 2.2.5 特征图的采样与卷积操作
- 3. 可变形卷积的优势
- 4. 完整代码
- 5. 总结
概述
在深度学习中的计算机视觉任务中,卷积神经网络(CNN)是最常见且强大的模型之一。然而,传统的卷积操作在处理具有几何变换(如旋转、平移、缩放等)或变形的图像时,通常表现得不够灵活。为了解决这一问题,可变形卷积(Deformable Convolution)应运而生。可变形卷积(Deformable Convolution),来源于微软亚洲研究院(MSRA)在 ICCV 2017 发表的论文:《Deformable Convolutional Networks》(作者:Jifeng Dai 等,论文地址:arXiv:1703.06211)。
本文将详细解析 DeformConv2D 类,它是可变形卷积的一种实现,帮助我们更好地理解可变形卷积的工作原理。
提示:DeformConv2D完整代码见4.完整代码
1. 可变形卷积的背景
传统卷积神经网络(CNN)中的卷积操作通过在图像上滑动固定大小的卷积核来提取局部特征。每个卷积核的权重是固定的,这使得卷积操作对输入图像的空间位置不够敏感。因此,在面对具有复杂空间变换(如变形物体、物体位移等)的图像时,传统的卷积核可能无法有效地捕捉到这些变化。
可变形卷积通过引入动态调整卷积核位置的机制,解决了这一问题。它能够根据输入图像的特征自适应地调整卷积核的采样位置,从而更好地适应输入的几何变换。
2. DeformConv2D概述
DeformConv2D 类是实现可变形卷积操作的核心组件。它的主要功能是允许卷积核在图像中根据学习到的偏移量进行自适应调整。通过这种方式,卷积操作能够更加灵活地适应图像中的空间变形,提高了网络在处理复杂图像时的表现。

3×3标准卷积和可变形卷积的采样位置示意图。(a)标准卷积的规则采样网格(绿色点)。 (b)可变形卷积中变形的采样位置(深蓝色点)和增强的偏移量(浅蓝色箭头)。 (c)(d)是(b)的特殊情况,显示可变形卷积如何对尺度、(各向异性)纵横比和旋转进行广义化。
2.1 构造函数分析
DeformConv2D 的构造函数如下:
def __init__(self, inc=3, outc=16, kernel_size=3, padding=1, bias=None):super(DeformConv2D, self).__init__()self.offset = nn.Conv2d(inc, 18, kernel_size=kernel_size, padding=padding)self.kernel_size = kernel_sizeself.padding = paddingself.zero_padding = nn.ZeroPad2d(padding)self.conv_kernel = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)
inc和outc:输入和输出通道数。kernel_size和padding:卷积核的大小和填充方式。self.offset:用于计算偏移量的卷积层,它输出每个像素的偏移量。输出通道数为18,表示每个像素有9个x方向和9个y方向的偏移量。self.conv_kernel:最终执行标准卷积操作的卷积层。
2.2 前向传播函数解析
DeformConv2D 的前向传播函数实现了变形卷积的主要步骤,下面我们一一解析:
def forward(self, x):offset = self.offset(x) # 计算偏移量offset = offset.permute(0, 2, 3, 1) # 调整偏移量的形状if self.padding > 0:x = self.zero_padding(x) # 如果有填充,执行填充操作# 计算目标位置 pp_0 = self._get_p_0(x) # 输入图像每个像素的位置p_n = self._get_p_n() # 卷积核的相对位置网格p = self._get_p(p_0, offset, p_n) # 每个像素的目标位置# 计算四个角的插值位置q_lt, q_rb, q_lb, q_rt = self._get_q(p)mask = self._get_mask(p) # 掩码,标记哪些区域无效g_lt, g_rb, g_lb, g_rt = self._get_g(p, q_lt, q_rb, q_lb, q_rt) # 双线性插值的权重# 从输入图像中采样数据x_q = self._get_x_q(x, q_lt, q_rb, q_lb, q_rt)# 重塑采样后的数据并执行卷积操作x_offset = self._reshape_x_offset(x_q, g_lt, g_rb, g_lb, g_rt)out = self.conv_kernel(x_offset)return out
2.2.1 偏移量的计算与应用
通过 self.offset(x) 计算每个像素的偏移量,偏移量表示了卷积核应该滑动到哪里。然后,偏移量的维度调整为 (batch_size, height, width, 18),其中18是每个像素在x方向和y方向的偏移量。
2.2.2 目标位置的计算
输入图像的每个像素都有一个对应的目标位置 p,它由原始位置 p_0 和通过偏移量计算的变化量构成。公式如下:
p = p 0 + Δ p p = p_0 + \Delta p p=p0+Δp
其中,p_0 是像素的原始位置,Δp 是偏移量。
2.2.3 四个角的插值
在目标位置 p 处,可能不落在整数位置。因此,需要通过四个邻近整数位置(左上 q_lt,右下 q_rb,左下 q_lb 和右上 q_rt)进行双线性插值。
2.2.4 双线性插值的权重
双线性插值的权重 g_lt、g_rb、g_lb 和 g_rt 通过目标位置与邻近整数位置的距离计算得出。通过这些权重,可以计算目标位置的值。
2.2.5 特征图的采样与卷积操作
在计算出权重后,我们可以从输入图像中根据目标位置 p 进行采样。然后,通过 _reshape_x_offset 函数将采样结果整理成适合卷积操作的格式,最后用 self.conv_kernel 执行标准的卷积操作。
3. 可变形卷积的优势
传统卷积核对图像的每个位置进行固定的采样,而可变形卷积通过学习得到的偏移量使得卷积核可以根据图像的内容动态地调整其采样位置。这带来了以下几个优势:
-
更好的空间适应性:对于具有旋转、平移等空间变换的图像,可变形卷积能够自适应调整卷积核位置,从而提高模型的表现。
-
提高了图像变形的容忍度:在处理复杂的图像变形时(如物体的非刚性变形),传统卷积可能难以有效捕捉这些变化,而可变形卷积通过自适应调整,能够更好地应对这些挑战。
-
提升了模型的灵活性和表达能力:可变形卷积使得网络可以更好地捕捉图像中的复杂变化模式,尤其是在目标检测、图像分割等任务中表现尤为出色。
4. 完整代码
使用代码,其中inc为输入通道,outc为输出通道:
model = DeformConv2D(inc=3, outc=32, kernel_size=3, padding=1, bias=None)x = torch.zeros(16, 3, 224, 224)outs = model(x)
定义代码:
from torch.autograd import Variable, Function
import torch
from torch import nn
import numpy as npclass DeformConv2D(nn.Module):def __init__(self, inc=3, outc=16, kernel_size=3, padding=1, bias=None):super(DeformConv2D, self).__init__()self.offset = nn.Conv2d(inc, 18, kernel_size=kernel_size, padding=padding)self.kernel_size = kernel_sizeself.padding = paddingself.zero_padding = nn.ZeroPad2d(padding)self.conv_kernel = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)def forward(self, x):offset = self.offset(x)dtype = offset.data.type()ks = self.kernel_sizeN = offset.size(1) // 2# Change offset's order from [x1, x2, ..., y1, y2, ...] to [x1, y1, x2, y2, ...]# Codes below are written to make sure same results of MXNet implementation.# You can remove them, and it won't influence the module's performance.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()offsets_index = offsets_index.unsqueeze(dim=0).unsqueeze(dim=-1).unsqueeze(dim=-1).expand(*offset.size())offset = torch.gather(offset, dim=1, index=offsets_index)# ------------------------------------------------------------------------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 = Variable(p.data, requires_grad=False).floor()q_rb = q_lt + 1q_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.cat([q_lt[..., :N], q_rb[..., N:]], -1)q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], -1)# (b, h, w, N)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.detach()floor_p = p - (p - torch.floor(p))p = p*(1-mask) + floor_p*maskp = 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_rtx_offset = self._reshape_x_offset(x_offset, ks)out = self.conv_kernel(x_offset)return outdef _get_p_n(self, N, dtype):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@staticmethoddef _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)p_0_y = p_0_y.flatten().reshape(1, 1, h, w).repeat(N, axis=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_0def _get_p(self, offset, dtype):N, h, w = offset.size(1)//2, offset.size(2), offset.size(3)# (1, 2N, 1, 1)p_n = self._get_p_n(N, dtype)# (1, 2N, h, w)p_0 = self._get_p_0(h, w, N, dtype)p = p_0 + p_n + offsetreturn pdef _get_x_q(self, x, q, N):b, h, w, _ = q.size()padded_w = x.size(3)c = x.size(1)# (b, c, h*w)x = x.contiguous().view(b, c, -1)# (b, h, w, N)index = q[..., :N]*padded_w + q[..., N:] # offset_x*w + offset_y# (b, c, h*w*N)index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)return x_offset@staticmethoddef _reshape_x_offset(x_offset, ks):b, c, h, w, N = x_offset.size()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)return x_offsetif __name__ == '__main__':model = DeformConv2D(inc=3, outc=32, kernel_size=3, padding=1, bias=None)x = torch.zeros(16, 3, 224, 224)outs = model(x)print(outs.shape)
5. 总结
DeformConv2D 类通过引入偏移量和动态调整卷积核的位置,成功实现了可变形卷积。与传统卷积相比,可变形卷积能更灵活地处理图像中的几何变换,从而提升了网络对复杂图像的适应性和表达能力。这一创新使得网络能够捕捉更复杂的空间特征,为计算机视觉中的任务提供了强大的支持。
可变形卷积不仅在学术研究中具有重要意义,也在实际应用中表现出强大的能力,特别是在目标检测、图像分割等任务中,展示了其巨大的潜力。
通过学习可变形卷积的工作原理,我们可以更好地理解现代计算机视觉技术的前沿发展,也为我们在深度学习中的应用提供了更多灵活的工具和思路。
相关文章:
源自Deformable Convolutional Networks的一种可变形卷积实现解析
衍生记录:深度学习pytorch之简单方法自定义9类卷积即插即用 文章目录 概述1. 可变形卷积的背景2. DeformConv2D概述2.1 构造函数分析2.2 前向传播函数解析2.2.1 偏移量的计算与应用2.2.2 目标位置的计算2.2.3 四个角的插值2.2.4 双线性插值的权重2.2.5 特征图的采样…...
记一次性能调优-20250320
2月份年后上班,刚过完年,还没从喜悦中解放出来,凌晨3点的时候同事就给我打电话,晚上的批量处理任务卡住了,快帮忙看看,做了几分钟的心里建设之后从被窝爬起来,看着手机上好几电话,赶…...
Postman高级功能深度解析:Mock Server与自动化监控——构建高效API测试与监控体系
引言:Postman在API开发中的核心价值 在数字化时代,API(应用程序编程接口)已成为系统间交互的“神经网络”,其质量直接影响用户体验与业务连续性。然而,传统API测试面临两大挑战: 开发阶段依赖…...
【最后203篇系列】020 rocksdb agent
今天还是挺开心的一天,又在工具箱里加了一个工具。嗯,但是快下班的时候也碰到一些不太顺心的事,让我有点恼火。我还真没想到一个专职的前端,加测试,以及其他一堆人,竟然不知道后端返回的markdown,在前端渲染…...
OpenCV旋转估计(2)用于自动检测波浪校正类型的函数autoDetectWaveCorrectKind()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 cv::detail::autoDetectWaveCorrectKind 是 OpenCV 中用于自动检测波浪校正类型的函数,它根据输入的旋转矩阵集合来决定使用哪种波浪…...
mysql-connector-python 报错(0xC0000005)
报错情况: 原因: mysql-connector-python版本不对,我们的mysql版本为sql8.0需要下载mysql-connector-python8.0....的库 方法: pip install mysql-connector-python8.1.0 即可...
从零开始实现Stable Diffusion本地部署
1. 依赖安装 文件打包下载地址(Stable Diffusion) # git : 用于下载源码 https://git-scm.com/downloads/win # Python 作为基础编译环境 https://www.python.org/downloads/ # Nvidia 驱动,用于编译使用GPU显卡硬件 https://ww…...
RAG各类方法python源码解读与实践:利用Jupyter对RAG技术综合评测【3万字长文】
检索增强生成(RAG )是一种结合信息检索与生成模型的混合方法。它通过引入外部知识来提升语言模型的性能,从而提高回答的准确性和事实正确性。为了简单易学,不使用LangChain框架或FAISS向量数据库,而是利用Jupyter Note…...
transform C++标准库算法用法(对容器中元素进行转换操作:大小写转换、向量的加法乘法运算)
std::transform 是 C 标准库中的一个算法,用于对容器(如数组、向量、字符串等)中的元素进行转换操作,并将结果存储到指定的目标位置。它可以对单个范围或两个范围的元素进行操作,并将结果写入另一个容器。 1. 头文件 …...
Java File 类与文件操作
一、引言 在 Java 编程中,文件操作是一项非常常见且重要的任务。无论是读取配置文件、保存用户数据,还是进行日志记录,都离不开对文件的操作。Java 提供了 File 类来表示文件和目录的抽象路径名,通过该类可以对文件和目录进行创建、删除、重命名等操作。同时,Java 还提供…...
Python 字符串的编码格式
在 Python 中,字符串(str)默认使用 Unicode 编码,具体来说,Python 3 中的字符串是以 UTF-8 编码存储的 Unicode 字符序列。Unicode 是一种国际标准,旨在为世界上所有的字符提供唯一的编码,而 UTF-8 是 Unicode 的一种实现方式,具有兼容性和高效性。 1. Unicode 与 UTF-…...
RPA+AI 技术到底好在哪里?
在自动化领域,RPA与生成式AI都是强大的技术,都可以用来实现自动执行重复耗时的任务。 主要区别是:传统RPA擅长处理结构化与规则明确简单的流程,而在非结构化数据处理、动态上下文适应、智能决策等能力上有欠缺;而基于…...
flowable适配达梦7 (2.1)
经过第一版的问题解决,后端项目可以启动,前端页面也集成进去。 前端在流程设计页面报错 之后发现主要是组件中modelerStore这个值没有 解决方法:在data增加对象 给component/process/designer.vue 中涉及到的每个子组件传入 :modelerStore“modeler…...
基于java的ssm+JSP+MYSQL的母婴用品网站(含LW+PPT+源码+系统演示视频+安装说明)
系统功能 管理员功能模块: 主页 个人中心 用户管理 商品分类管理 商品信息管理 留言板管理 成长交流 系统管理 订单管理 留言管理 用户功能模块: 主页 个人中心 我的收藏管理 订单管理 前台首页功能模块: 首页 商品信息 论…...
面试八股 —— Redis篇
重点:缓存 和 分布式锁 缓存(穿透,击穿,雪崩) 降级可作为系统的保底策略,适用于穿透,击穿,雪崩 1.缓存穿透 2.缓存击穿 3.缓存雪崩 缓存——双写一致性 1.强一致性业务(…...
gradle-8.13
gradle-8.13 稍微看了下,基于Maven改造的 https://gradle.org/install/https://github.com/gradle/gradle-distributions/releaseshttps://github.com/gradle/gradle-distributions/releases/download/v8.13.0/gradle-8.13-all.zip https://github.com/gradle/gra…...
不使用负压电源,ADC如何测量正负压?
电路图来自ZLinear的开源数据采集板卡DL8884_RFN,是一个比较常见的电压偏置采集法 对电路进行分析,所以说可以先看下采集卡的模拟输入部分的参数如下: 通道数量: 8通道单端输入/4通道差分输入 分辨率: 16位逐次逼近型(SAR) ADC 采样速率: 40…...
在 Linux 系统上部署 Deepseek AI 的全面指南
对于所有希望亲身体验 AI 魅力的玩家来说,本文将提供一个详尽的教程,指导你在 Linux 系统上部署 Deepseek AI。无论你是技术小白还是有一定基础的用户,都能轻松跟随本文完成部署。 一、关于 Ollama Ollama 是一款功能强大的开源应用&am…...
SinoSteel生产企业ERP实施建议书final(143页PPT)(文末有下载方式)
资料解读:SinoSteel 生产企业 ERP 实施建议书 final 详细资料请看本解读文章的最后内容。 在当今竞争激烈的商业环境中,企业的信息化建设对于提升竞争力和实现可持续发展至关重要。中钢集团作为一家大型跨国企业集团,在钢铁行业占据重要地位。…...
贴片陶瓷天线和蓝牙天线的layout注意事项
板载天线,也有封装成器件进行使用: 看到这里,细心的人发现,天线接入芯片的时候,旁边也直接接地了: F型天线(Inverted F Antenna, IFA)的一端接地,看起来像是“短路”&am…...
关于波士顿动力2025年3月的人形机器人最新视频
这是完整的视频: 波士顿动力最新逆天表演-机器人Atlas行走、奔跑、爬行、杂技_哔哩哔哩_bilibili 至少从目前来看,综合对比运动的幅度、各关节的协调性、整体的顺遂性、动作的拟人程度,波士顿动力是已知人形机器人中最好的。 尤其需要关注…...
fontTools工具的使用介绍
前言 python工具库fontTools,我是用来压缩前端字体的,优化前端请求速度的;使用的过程中,遇到了不少的坑,把这个过程记录下来,防止再犯。 安装 # fontTools 4.56.0 pip install fontTools提取子字体集 方…...
深入理解计算机网络:OSI 与 TCP/IP 各层结构与功能
目录 1. 引言 2. OSI 模型 2.1 OSI 各层的详细功能 2.1.1 物理层 2.1.2 数据链路层 2.1.3 网络层 2.1.4 传输层 2.1.5 会话层 2.1.6 表示层 2.1.7 应用层 3. TCP/IP 模型 3.1 TCP/IP 各层的详细功能 3.1.1 网络接口层 3.1.2 网络层 3.1.3 传输层 3.1.4 应用层 …...
Wi-Fi NAN 架构(Wi-Fi Aware Specification v4.0,第2章:2.3~2.6)
1. NAN 数据通信架构 1.1 单播支持 要在两个NAN设备之间启动单播数据通信,服务需发起一个NAN数据路径(NDP,NAN Data Path)请求。这对NAN设备之间会建立一个NAN设备链路(NDL,NAN Device Link)&…...
Arduino示例代码讲解:Multiple tone player 多音播放器
Arduino示例代码讲解:Multiple tone player 多音播放器 Multiple tone player 多音播放器代码功能代码逐行解释1. 注释部分2. `setup()` 函数3. `loop()` 函数硬件连接**扬声器连接**:**Arduino板**:运行结果修改建议关于`tone()`和`noTone()`函数视频讲解Multiple tone pla…...
Windows电脑 打开 Docker Desktop 提示的“Virtual Machine Platform not enabled”的问题解决
文章目录 问题来源解决方案 问题来源 因为要在本地推送 docker 镜像 到镜像库,所以安装了 Docker Desktop ,刚开始是好的,后来因为要调试 uniapp 安卓App,又装了雷电模拟器。然后就冲突了,打不开 Docker Desktop了&am…...
【愚公系列】《高效使用DeepSeek》020-专业术语解释
🌟【技术大咖愚公搬代码:全栈专家的成长之路,你关注的宝藏博主在这里!】🌟 📣开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主! 👉 江湖人称"愚公搬代码",用七年如一日的精神深耕技术领域,以"…...
1.环境搭建VUE+Spring boot
一.vue 环境搭建 Spring Boot 和 Vue,可以实现前后端分离的架构,提高开发效率和应用性能。Spring Boot 负责后端逻辑和数据处理,Vue 负责前端展示和用户交互。 1.1安装node.js 这个是是安装在个人电脑上 直接在官网下载然后安装即可&…...
第二十七篇 数据仓库与维度建模指南:从理论到实战的进阶之路
声明:文章内容仅供参考,需仔细甄别。文中技术名称属相关方商标,仅作技术描述;代码示例为交流学习用途,部分参考开源文档(Apache 2.0/GPLv3);案例数据已脱敏,技术推荐保持…...
【Unity3D】Addressables使用流程
Package Manager - 搜索 Addressables 安装 Window -> Asset Management -> Addressables 打开窗口 New -> 新建Packed Assets 资源组 默认资源组Default xxx (Default) 将资源,如预制体直接拖拽进资源组 Build -> New Build -> Default Buil…...
