05 / 2026 / 实验原型
DDefConv:面向不规则视觉目标的实验性可变形特征卷积
基于 PyTorch、注意力机制和可变形采样的实验性视觉算子,尝试接入 Ultralytics YOLO11,用于不规则目标特征增强。
系统结构图暂不可用
问题
如何针对边缘形状不规则、方向变化明显的视觉目标探索可变形特征增强,并保持实现边界清晰可验证?
角色
独立研究原型开发;负责模块实现、YOLO11 接入实验与基础验证。
方法
输入特征分为注意力分支和变形分支,利用学习到的采样偏移执行可变形卷积,再通过空间注意力加权并与旁路特征融合。
结果
README 记录了最小测试、YOLO11 配置接入与已知边界。当前是 Experimental / Research Prototype,不表述为完整 DCNv4、严格的 depthwise deformable convolution 或已全面优于标准卷积。
DDefConv:深度可分离可变形卷积
基于 PyTorch 的实验性视觉算子,以及接入 Ultralytics YOLO11 的完整说明。
DDefConv 面向边缘形状不规则、方向变化明显的视觉目标。模块先将输入特征拆成注意力分支和变形分支,再利用学习到的采样偏移执行可变形卷积,最后通过空间注意力对变形特征进行加权并与旁路特征融合。
项目最初用于水稻病斑特征提取,也可以作为通用特征增强模块放入检测、分类或分割网络。核心代码位于 DDefConv0/ddef_conv.py。
项目亮点
- 将输入特征拆分为注意力分支与可变形采样分支,增强对不规则目标区域的特征表达。
- 提供可独立运行的 PyTorch 模块与
DDefConvBlock封装。 - 给出接入 Ultralytics YOLO11、自定义 YAML、训练和验证的完整流程。
- 明确记录当前实现边界、适用位置与后续改进方向,便于继续实验和消融对比。
快速体验:
cd DDefConv0
python -m pip install torch torchvision
python ddef_conv.py
目录
当前实现说明
在使用代码前,请先了解下面几个边界。
- 本模块不是 DCNv4。
DDefConv调用的是torchvision.ops.deform_conv2d,并且没有传入 modulation mask,因此当前使用的是 Torchvision 文档所述的 DCNv1 式 offset-only 算子;运行DDefConv本身不需要编译仓库中的DCNv4_op。 - 当前代码不是严格的 depthwise deformable convolution。
deform_weight的形状是[C/2, C/2, k, k],因此变形分支仍会混合通道。严格逐通道卷积的权重第二维应为1,通常还要再接一个1 × 1pointwise convolution。当前版本更准确的描述是“通道拆分、注意力引导的可变形特征卷积”。 - 推荐配置为
kernel_size=3, stride=1, padding=1, dilation=1。 偏移分支始终保持输入空间尺寸;若直接设置stride=2或不匹配的 padding/dilation,偏移、注意力和变形卷积输出的空间尺寸会不一致。 - 输入通道数必须为偶数。 模块会沿通道维将输入均分为两个分支。
- 代码并非设计文档公式的逐行复现。 设计文档描述了逐通道、离散取整并带边界 mask 的采样过程;当前实现把采样交给 Torchvision 的连续偏移可变形卷积,二者不能视为完全相同的算子。
- 该文件只实现特征提取算子。 它没有实现显式边缘算子、旋转框检测头、角度预测、旋转损失或旋转 NMS。“边缘感知”和“角度自适应”是原始设计动机,当前代码只是其中的实验性近似实现。需要旋转框检测时,应将它接入 YOLO OBB 模型,而不是把 DDefConv 本身当作角度检测头。
DDefConvBlock不是 UltralyticsConv的完全等价替换。 为了方便 YAML 调用,它保留了g和act参数,但当前代码没有使用这两个参数;它也不能替换输入为 RGB 的第一层,因为第一层输入通道数为3,无法均分。
因此,不建议把 YOLO 中所有 Conv 都直接替换成 DDefConvBlock,尤其不要替换 stride=2 的下采样卷积。最稳妥的做法是先替换一个 stride=1 的特征块并完成消融实验。
模块原理
设输入特征为:
$$ X \in \mathbb{R}^{B \times C \times H \times W}, \qquad C \bmod 2 = 0 $$
模块的计算流程如下。
flowchart LR
X[输入 X] --> O[3×3 Conv + BN<br/>生成偏移 O]
X --> S{沿通道均分}
S --> A[注意力分支 A]
S --> P[变形分支 P]
A --> ABN[BN + 空间统计]
ABN --> W[注意力权重 ω]
P --> DC[DeformConv]
O --> DC
DC --> M[逐元素相乘]
W --> M
X --> U[1×1 Conv 旁路 U]
M --> F[相加 + BN + Sigmoid]
U --> F
F --> Pool[3×3 MaxPool, stride=1]
Pool --> Proj[1×1 输出投影]
Proj --> Y[输出 Y]
1. 生成采样偏移
对输入特征执行 3 × 3 Conv + BatchNorm,为每个卷积位置生成二维偏移:
$$ O = f_{offset}(X), \qquad O \in \mathbb{R}^{B \times 2k^2 \times H \times W} $$
当 k=3 时,偏移通道数为 2 × 3 × 3 = 18。
2. 拆分特征
输入沿通道维均分:
$$ A, P = \mathrm{Split}(X), \qquad A,P \in \mathbb{R}^{B \times C/2 \times H \times W} $$
其中,A 用于计算空间注意力,P 用于可变形采样。
3. 可变形卷积
变形分支按照偏移 O 改变规则卷积网格的采样位置:
$$ P' = \mathrm{DeformConv}(P, O; W_d) $$
与普通卷积的固定采样点相比,可变形卷积可以让感受野随目标形状发生变化,更适合描述弯曲边缘、细长结构和不规则区域。
4. 空间注意力
注意力分支先做批归一化,再按每个样本、每个通道统计空间均值和方差:
$$ \omega = \mathrm{Sigmoid}\left( \rho \cdot \frac{|A' - \mu|}{\sigma^2 + \xi} + \gamma \right) $$
其中 ρ 和 γ 是可学习参数,ξ 用于避免除零。偏离通道空间均值较明显的位置会得到不同的响应权重。
5. 融合与输出
注意力加权后的变形特征与输入的 1 × 1 卷积旁路相加:
$$ Y = \mathrm{Proj}\left( \mathrm{Pool}\left( \mathrm{Sigmoid}\left( \mathrm{BN}(\omega \odot P' + U) \right) \right) \right), \qquad U = \mathrm{Conv}_{1\times1}(X) $$
当前 MaxPool2d 的步长为 1,所以不会改变特征图尺寸;最后的 1 × 1 投影负责得到指定输出通道数。
张量形状
以下以 B=2, C=64, H=W=128, k=3, out_channels=128 为例。
| 张量 | 形状 | 含义 |
|---|---|---|
x |
[2, 64, 128, 128] |
输入特征 |
offset |
[2, 18, 128, 128] |
9 个采样点的二维偏移 |
at_feat |
[2, 32, 128, 128] |
注意力分支 |
p_deep |
[2, 32, 128, 128] |
变形分支 |
omega |
[2, 32, 128, 128] |
空间注意力权重 |
p_deep_prime |
[2, 32, 128, 128] |
可变形卷积输出 |
out |
[2, 128, 128, 128] |
最终输出 |
项目结构
深度可分离可变形卷积/
├── README.md # 本说明
├── DDefConv_TODO.md # 原始设计/需求说明
├── DDefConv0/
│ ├── ddef_conv.py # DDefConv 与 YOLO 封装,核心文件
│ ├── benchmark_conv.py # 探索性 benchmark 脚手架
│ ├── DDefConv_TODO.md # 设计说明副本
│ ├── DCNv4_op/ # DCNv4 CUDA 扩展,上游代码
│ ├── classification/ # DCNv4 上游分类工程
│ ├── detection/ # DCNv4 上游检测工程
│ └── segmentation/ # DCNv4 上游分割工程
└── DCNv4-main/DCNv4-main/ # 原始 DCNv4 工程副本
如果只使用本文介绍的 DDefConv,重点关注下面两个文件即可:
DDefConv0/ddef_conv.py:模块实现和最小测试。DDefConv0/benchmark_conv.py:待完善的参数量及耗时对比脚手架。
DDefConv0/README.md 和 DDefConv0/README_CN.md 主要介绍上游 DCNv4;本项目的 DDefConv 说明以当前顶层 README 为准。
环境与快速测试
依赖
- Python 3.9+
- PyTorch
- Torchvision
- 可选:CUDA,用于 GPU 训练和测速
- 接入 YOLO 时:Ultralytics 源码仓库
PyTorch 与 Torchvision 必须是相互兼容的构建版本。建议根据自己的 CUDA 版本,从 PyTorch 官方安装页面 复制安装命令。
python -m pip install torch torchvision
运行最小测试
cd DDefConv0
python ddef_conv.py
预期输出形状为:
输入特征形状: torch.Size([2, 64, 128, 128])
DDefConv 输出形状: torch.Size([2, 128, 128, 128])
运行基准脚本
cd DDefConv0
python benchmark_conv.py
脚本会尝试比较普通 Conv2d、DDefConv 和可选的 DCNv4,但它目前只是探索性脚手架:反向计时会重复使用同一计算图,DCNv4 分支的输入布局也没有与其接口对齐。不要据此发布速度结论;正式比较前应先修正计时过程、完成预热与同步,并为每个算子使用正确输入格式。
独立使用
import torch
from ddef_conv import DDefConv, DDefConvBlock
x = torch.randn(2, 64, 128, 128)
# 原始模块接口
layer = DDefConv(
in_channels=64,
out_channels=128,
kernel_size=3,
stride=1,
padding=1,
dilation=1,
)
y = layer(x)
print(y.shape) # torch.Size([2, 128, 128, 128])
# 与 Ultralytics 常用 c1/c2/k/s 参数风格一致的封装
block = DDefConvBlock(c1=64, c2=128, k=3, s=1)
y = block(x)
接入 Ultralytics
下面采用修改 Ultralytics 源码的方式。官方也推荐在需要从模型 YAML 解析自定义模块时,将模块放入源码、导出类名,并在 parse_model() 中处理通道参数。具体目录可能随版本变化,请同时参考 Ultralytics 自定义模块说明。
第 1 步:以可编辑方式安装 Ultralytics
git clone https://github.com/ultralytics/ultralytics.git
cd ultralytics
python -m pip install -e .
确认当前 Python 确实使用这份源码:
python -c "import ultralytics; print(ultralytics.__file__)"
输出路径应指向刚刚克隆的 ultralytics 目录,而不是另一份虚拟环境中的普通安装。
第 2 步:复制模块文件
将本仓库的:
DDefConv0/ddef_conv.py
复制到 Ultralytics 源码的:
ultralytics/nn/modules/ddef_conv.py
复制后无需编译 DCNv4_op,因为这个文件使用的是 torchvision.ops.deform_conv2d。
第 3 步:从 modules 包导出模块
编辑:
ultralytics/nn/modules/__init__.py
加入导入:
from .ddef_conv import DDefConv, DDefConvBlock
并在该文件的 __all__ 中加入:
"DDefConv",
"DDefConvBlock",
第 4 步:在 tasks.py 注册
编辑:
ultralytics/nn/tasks.py
先在 from ultralytics.nn.modules import (...) 中加入:
DDefConvBlock,
再找到 parse_model() 内的 base_modules 集合,加入:
DDefConvBlock,
示意如下:
base_modules = frozenset(
{
# ... Ultralytics 原有模块
Conv,
DWConv,
DDefConvBlock,
# ...
}
)
这样 YAML 中的第一个参数会被当作输出通道 c2,parse_model() 会自动补入输入通道 c1,并继续应用 YOLO 的宽度缩放:
YAML: DDefConvBlock, [c2, k, s]
实际构造参数: DDefConvBlock(c1, c2, k, s)
DDefConvBlock 不接收内部重复次数,因此不要把它加入 repeat_modules。
较旧的 Ultralytics 版本可能没有单独命名的
base_modules。这种情况下,在parse_model()中找到对Conv、C2f等基础模块补入c1/c2的判断集合,把DDefConvBlock加入同一集合即可。
第 5 步:修改模型 YAML
先复制官方 YOLO11 检测配置:
ultralytics/cfg/models/11/yolo11.yaml
例如保存为:
ultralytics/cfg/models/11/yolo11-ddef.yaml
建议在配置中显式指定模型尺寸,便于后续加载对应预训练权重:
scale: n
当前 DDefConv 不支持 YOLO 下采样层常用的 stride=2,因此请选择一个 stride=1 的特征块进行替换。最简单的做法是保持层数和输出通道不变,原位替换 backbone 最后的 C2PSA。该位置分辨率较低,适合作为首次接入测试。
原始配置中的第 10 层:
- [-1, 2, C2PSA, [1024]]
替换为:
- [-1, 1, DDefConvBlock, [1024, 3, 1]]
替换后的骨干示例:
nc: 80
scale: n
scales:
n: [0.50, 0.25, 1024]
s: [0.50, 0.50, 1024]
m: [0.50, 1.00, 512]
l: [1.00, 1.00, 512]
x: [1.00, 1.50, 512]
backbone:
- [-1, 1, Conv, [64, 3, 2]]
- [-1, 1, Conv, [128, 3, 2]]
- [-1, 2, C3k2, [256, False, 0.25]]
- [-1, 1, Conv, [256, 3, 2]]
- [-1, 2, C3k2, [512, False, 0.25]]
- [-1, 1, Conv, [512, 3, 2]]
- [-1, 2, C3k2, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]]
- [-1, 2, C3k2, [1024, True]]
- [-1, 1, SPPF, [1024, 5]]
- [-1, 1, DDefConvBlock, [1024, 3, 1]]
# head 与官方 yolo11.yaml 保持不变
这里是“替换”而不是“插入”,因此后面的 from 层索引无需整体调整,未被替换层的预训练权重键名也能尽量保持不变。若更关注浅层小目标特征,也可以另做实验,将前面的某个 C3k2 原位替换为同输出通道、stride=1 的 DDefConvBlock,但要重新测量显存和延迟。
不要做下面这种替换:
# 错误示例:当前实现的 offset 分支与 stride=2 输出尺寸不匹配
- [-1, 1, DDefConvBlock, [256, 3, 2]]
第 6 步:构建并检查模型
from ultralytics import YOLO
from ultralytics.nn.modules import DDefConvBlock
model = YOLO("ultralytics/cfg/models/11/yolo11-ddef.yaml")
model.info()
assert any(
isinstance(module, DDefConvBlock)
for module in model.model.modules()
), "模型中没有解析出 DDefConvBlock"
如果模型可以成功构建、model.info() 能完成一次计算,并且断言通过,说明 Python 导入、YAML 解析和基础前向链路已经接通。
训练与验证
从头训练
yolo detect train \
model=ultralytics/cfg/models/11/yolo11-ddef.yaml \
data=/path/to/data.yaml \
epochs=100 \
imgsz=640 \
batch=16
Windows PowerShell 可以写成一行:
yolo detect train model=ultralytics/cfg/models/11/yolo11-ddef.yaml data=D:\data\data.yaml epochs=100 imgsz=640 batch=16
加载 YOLO11 预训练权重后微调
from ultralytics import YOLO
model = YOLO("ultralytics/cfg/models/11/yolo11-ddef.yaml")
# 只会迁移名称和形状匹配的参数;新加入的 DDefConvBlock 保持随机初始化
model.load("yolo11n.pt")
model.train(
data="/path/to/data.yaml",
epochs=100,
imgsz=640,
batch=16,
device=0,
)
scale: n 应与 yolo11n.pt 对应;使用 s/m/l/x 尺寸时,请同步更改 scale 和预训练权重名称。
验证与推理
from ultralytics import YOLO
model = YOLO("runs/detect/train/weights/best.pt")
metrics = model.val(data="/path/to/data.yaml")
results = model.predict(source="/path/to/image.jpg", save=True)
加载 best.pt 的环境仍需包含注册后的 DDefConvBlock 源码,否则 Python 无法还原自定义模型结构。
用于旋转框检测
如果任务需要预测病斑方向或旋转框,请以官方 yolo11-obb.yaml 为基础,按同样方式替换一个 stride=1 特征块,并使用 OBB 数据格式和训练任务:
yolo obb train model=yolo11-ddef-obb.yaml data=/path/to/obb-data.yaml epochs=100 imgsz=640
DDefConv 负责增强特征;角度回归、OBB 损失和旋转框后处理由 YOLO OBB 检测头负责。 数据标签格式和完整训练参数请参考 Ultralytics OBB 官方文档。
推荐实验方式
为了判断模块是否真正有效,建议至少保留以下对照组,并确保数据划分、随机种子、输入尺寸、训练轮数和增强策略一致。
| 实验 | 改动 | 关注指标 |
|---|---|---|
| Baseline | 官方 YOLO 配置 | mAP、参数量、FLOPs、延迟、显存 |
| DDefConv-P2 | 替换较浅层 stride=1 特征块 |
小目标召回率、边缘细节、延迟 |
| DDefConv-P3/P4 | 替换中后层特征块 | mAP、显存、速度 |
| 多层 DDefConv | 替换两个或更多特征块 | 精度收益是否抵消计算开销 |
不要只比较训练集损失。至少报告验证集 mAP50、mAP50-95、参数量、单张延迟和峰值显存,并在相同硬件上进行测速。
常见问题
KeyError: 'DDefConvBlock'
通常是 tasks.py 的全局命名空间中没有该类。检查:
ddef_conv.py是否放在当前实际导入的 Ultralytics 源码中;ultralytics/nn/modules/__init__.py是否导入并导出了该类;ultralytics/nn/tasks.py是否导入了DDefConvBlock;- 是否使用
python -m pip install -e .安装了当前源码。
AssertionError: in_channels 必须为偶数
输入通道必须能均分为两个分支。YOLO 中尽量使用 8 的倍数作为通道数,并确认宽度缩放后的实际输入通道仍为偶数。
offset、omega 或输出尺寸不一致
先确认 YAML 中使用的是:
DDefConvBlock, [c2, 3, 1]
即 k=3, s=1,并保持默认 p=1, d=1。当前版本不能直接承担 stride=2 下采样。
Couldn't load custom C++ ops 或找不到 torchvision::deform_conv2d
这通常表示 PyTorch 与 Torchvision 版本、CUDA 构建或运行环境不匹配。使用 PyTorch 官方给出的同一组安装命令重新安装二者,并先单独验证:
from torchvision.ops import deform_conv2d
加载 best.pt 时提示找不到自定义模块
请使用训练时同一份修改后的 Ultralytics 源码。自定义类的模块路径必须保持可导入;仅复制权重文件到一个未注册 DDefConv 的环境中通常无法直接加载。
ONNX、TensorRT、OpenVINO 导出失败
torchvision.ops.deform_conv2d 并非所有导出器和推理后端都原生支持。训练前如果最终目标是边缘部署,应尽早测试目标格式:
model.export(format="onnx")
必要时需要自定义 ONNX symbolic、TensorRT plugin,或改用目标后端已经支持的可变形卷积实现。
为什么参数量可能并不“轻量”
当前变形权重是 [C/2, C/2, k, k],参数量随通道数近似二次增长,因此不能仅凭“拆分了一半通道”就认定它是轻量算子。请以实际参数量、FLOPs、延迟和显存结果为准。
后续改进建议
- 将变形权重改为
[C/2, 1, k, k],完成真正的逐通道可变形卷积,再用1 × 1pointwise convolution 混合通道。 - 让偏移分支使用与主分支一致的 stride、padding 和 dilation,从而支持下采样。
- 将 offset 卷积初始化为零或增加可控的 offset 初始化策略,评估训练稳定性。
- 为调制 mask 增加预测分支,评估 DCNv2 风格调制是否有效。
- 增加自动化测试:输出形状、前向/反向、混合精度、CPU/GPU 一致性和梯度检查。
- 增加标准消融结果,而不是只报告单次基准耗时。
- 若用于 OBB,独立验证 DDefConv 对角度误差和旋转框 mAP 的影响。
参考资料
- Torchvision
deform_conv2dAPI - Deformable Convolutional Networks
- Deformable ConvNets v2
- DCNv4 论文
- OpenGVLab/DCNv4
- Ultralytics 模型 YAML 配置与自定义模块
- Ultralytics
parse_model()当前源码 - Ultralytics YOLO11 官方模型配置
License
仓库内的 DCNv4 上游代码及许可见 DDefConv0/LICENSE。将自定义模块复制进 Ultralytics 源码或发布衍生项目时,还应分别确认并遵守 DCNv4、Torchvision、Ultralytics 及其他依赖的许可证要求。