第一部分:库功能介绍
VoxelMorph 是一个专为 医学图像配准(Medical Image Registration)设计的开源深度学习库,由 MIT 和哈佛等机构开发,以 无监督、快速、端到端 的配准能力著称。除了你已了解的 Unet、SpatialTransformer 和位移场预测外,它还包含一系列关键功能和模块,支撑从 2D/3D 刚性到非刚性、仿射到微分同胚(diffeomorphic)等多种配准任务。
以下是 VoxelMorph 库的核心功能分类详解:
🔹 一、核心网络架构
1. vxm.networks.VxmDense
- 最常用的完整配准模型,封装了 U-Net + SpatialTransformer。
- 自动处理输入拼接、位移场生成、图像形变全过程。
- 支持 2D/3D,可选是否启用微分同胚积分(
int_steps)。
model = vxm.networks.VxmDense(
inshape=(160, 192, 224),
nb_unet_features=[[16,32,32,32], [32,32,32,32,32,16]],
int_steps=7, # 启用微分同胚(smooth, invertible)
int_downsize=2 # 积分在低分辨率进行以加速
)
2. vxm.networks.Unet
- 如前所述,用于提取特征并回归形变场的基础编码器-解码器。
3. vxm.networks.Transform
- 仅包含
SpatialTransformer的轻量模型,用于已有位移场的形变应用。
🔹 二、形变场后处理与高级变换
1. 微分同胚配准(Diffeomorphic Registration)
- 通过 Scaling and Squaring 方法将初始速度场积分成平滑、可逆的形变。
- 保证拓扑保持(无折叠、撕裂),对医学分析至关重要。
- 由
vxm.layers.VecInt(向量场积分层)实现:flow = unet(input) # 初始速度场 diff_flow = vxm.layers.VecInt(int_steps=7)(flow) # 微分同胚形变场
2. 多尺度配准(Multi-resolution)
- 支持在不同分辨率下级联配准(coarse-to-fine),提升大形变鲁棒性。
- 可通过构建多级
VxmDense模型实现。
🔹 三、损失函数(Loss Functions)
VoxelMorph 提供多种无监督损失函数,无需 ground truth 形变场:
| 损失类型 | 类名 | 作用 |
|---|---|---|
| 相似性损失 | vxm.losses.NCC | 归一化互相关,衡量两图结构相似性(对 MRI 强度不敏感) |
vxm.losses.MSE | 均方误差(适用于强度一致的图像) | |
vxm.losses.Dice | 用于分割图配准 | |
| 正则化损失 | vxm.losses.Grad('l2') | 惩罚位移场梯度,鼓励平滑形变 |
vxm.losses.BendingEnergy | 更强的平滑约束(二阶梯度) |
✅ 典型损失组合:
loss = NCC(warped, fixed) + λ * Grad(flow)
🔹 四、实用工具与辅助模块
1. 图像预处理/后处理
vxm.utils提供:read_file,save_file:读写 NIfTI 等医学图像格式volshow:3D 体绘制可视化transform:应用已有形变场到新图像
2. 形变场操作
vxm.utils.jacobian_determinant(flow):计算雅可比行列式,评估形变局部体积变化(判断是否折叠)vxm.utils.compose:组合多个形变场
3. 数据增强
- 支持随机仿射变换、噪声注入等,用于训练鲁棒性提升。
🔹 五、支持的任务类型
| 配准类型 | 是否支持 | 说明 |
|---|---|---|
| 2D / 3D | ✅ | 自动适配输入维度 |
| 单模态 / 多模态 | ✅ | 通过 NCC 或 MI(互信息)损失支持 |
| 无监督 | ✅ | 默认模式,无需标注 |
| 弱监督 | ✅ | 可结合少量 landmark 或 segmentation 监督 |
| 对称配准 | ✅ | 同时 warp A→B 和 B→A,提升一致性 |
| 群体模板构建(Template Construction) | ✅ | 迭代优化平均模板 + 形变场 |
🔹 六、部署与集成
- TensorFlow/Keras 原生:模型可直接
.save()为 SavedModel 或 HDF5。 - ONNX 支持(社区扩展):便于部署到生产环境。
- 与 ITK、SimpleITK 互操作:可通过
vxm.utils转换数据格式。
🔹 七、典型应用场景
- 跨时间点脑 MRI 对齐(如阿尔茨海默病研究)
- 多模态融合(T1 + T2 MRI,MRI + PET)
- 术中图像与术前计划配准
- 构建人群平均脑模板(如 MNI space)
- 动态器官跟踪(心脏、肺部呼吸运动建模)
✅ 总结:VoxelMorph 的核心优势
| 特性 | 说明 |
|---|---|
| 无监督 | 无需昂贵的形变标注 |
| 速度快 | GPU 上 3D 配准 < 1 秒(传统方法需分钟级) |
| 端到端可微 | 支持联合优化 |
| 拓扑保持 | 微分同胚选项确保解剖合理性 |
| 开箱即用 | 提供预训练模型、示例脚本、Jupyter Notebook |
| 活跃社区 | GitHub 超 3k stars,持续更新 |
🌐 官方 GitHub:https://github.com/voxelmorph/voxelmorph
📚 论文:VoxelMorph: A Learning Framework for Deformable Medical Image Registration (IEEE TMI 2019)
如果你从事医学图像分析、计算解剖学或 AI for Healthcare,VoxelMorph 几乎是非刚性配准的首选工具库。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第二部分:warped = spatial_transformer([moving, flow])函数功能和作用
warped = spatial_transformer([moving, flow]) 这行代码的功能是根据给定的位移场(displacement field)对源图像进行空间变换(warping),生成形变后的图像。具体来说,它实现了以下步骤:
1. 输入参数
moving: 这是一个张量,表示需要被形变的源图像(moving image)。其形状通常是(batch_size, height, width, channels)或者在3D情况下为(batch_size, depth, height, width, channels)。flow: 这是另一个张量,表示位移场(displacement field),用于指示每个像素点需要移动的方向和距离。其形状与图像的空间维度相匹配,但通道数等于空间维度的数量(2D图像为2,3D图像为3),即(batch_size, height, width, 2)对于2D情况。
2. SpatialTransformer 层的作用
spatial_transformer 是一个基于 Keras 的自定义层,通常由 VoxelMorph 库提供。它的主要职责是:
- 接收两个输入:一个是待变形的图像(
moving),另一个是指定位移信息的位移场(flow)。 - 执行可微分的空间变换:利用位移场中的信息来计算每个像素的新位置,并使用插值方法(如双线性插值)从原图中获取相应的像素值,从而生成新的、经过形变的图像。
3. 输出结果
warped: 这个变量存储了经过形变后的图像。它的形状与原始的moving图像相同,但是内容已经被按照flow中指定的方式重新排列了。
4. 应用场景
在医学图像配准任务中,这个过程通常用来将一张图像(称为“moving”或“浮动”图像)对齐到另一张参考图像(称为“fixed”或“目标”图像)。通过训练神经网络预测出合适的位移场,然后应用这个位移场来调整 moving 图像的位置,使得两张图像尽可能相似。这一步骤对于后续的任务非常重要,比如病变区域的对比分析等。
示例代码片段
# 假设我们已经有了 moving_image 和 displacement_field
moving_image = ... # shape: (batch, height, width, channels)
displacement_field = ... # shape: (batch, height, width, 2)
# 创建 SpatialTransformer 层实例
spatial_transformer = vxm.layers.SpatialTransformer(name='transformer')
# 应用空间变换
warped_image = spatial_transformer([moving_image, displacement_field])
# 此时 warped_image 包含了经过形变后的图像数据
总之,warped = spatial_transformer([moving, flow]) 实现了一个关键步骤,即将一个位移场应用于特定的源图像上,以产生一个新的图像,该图像已经根据位移场进行了适当的空间变换。这是实现图像配准的核心操作之一。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第三部分:为什么spatial_transformer 可微
SpatialTransformer(特别是 VoxelMorph 中的实现)之所以保持可微分(differentiable),根本原因在于:
它所依赖的核心操作——插值(如双线性/三线性插值)——是关于输入坐标和源图像值的光滑、连续、可导函数。
下面从数学原理、计算图构建和深度学习框架支持三个层面详细解释。
🔹 一、核心思想:形变 = 坐标变换 + 插值采样
SpatialTransformer 的任务是:
给定一个位移场 ( u(x) ),将源图像 ( M ) 在每个目标位置 ( x ) 处的值定义为:
[ W(x) = M(x + u(x)) ]
但问题在于:
- ( x + u(x) ) 通常是非整数坐标(如 (10.37, 25.82))
- 图像 ( M ) 只在整数格点上有定义
✅ 解决方案:用插值从邻近像素估计非格点处的值。
而插值公式本身是可导的 → 整个 W(x) 对 M 和 u(x) 都可导。
🔹 二、以 2D 双线性插值为例:显式写出可导公式
设目标采样点为:
令:
则双线性插值结果为:
✅ 为什么可导?
-
对源图像 ( M ) 可导:( W ) 是
的线性组合 → 梯度直接是权重:
-
对位移场 ( u )(即对 (
))可导:
权重 () 等是 (
) 的多项式函数 → 光滑可导
而
,在非整数点处 (
)(几乎处处成立)。
📌 关键:虽然
floor函数在整数点不可导,但这些点测度为零,在随机梯度下降中几乎不会遇到,实践中视为“处处可导”。
🔹 三、TensorFlow / PyTorch 如何实现自动微分?
SpatialTransformer 在底层通常通过以下方式实现(以 TensorFlow 为例):
# 伪代码示意
def call(self, inputs):
source, flow = inputs
# 1. 构建目标网格 (i, j)
grid = meshgrid_like(source)
# 2. 计算采样坐标: sample_pos = grid + flow
sample_pos = grid + flow
# 3. 使用 tf.image.resize 或自定义 CUDA kernel 进行双线性采样
warped = bilinear_sampler(source, sample_pos)
return warped
其中 bilinear_sampler 是一个由基本可导操作构成的函数:
- 加法(
grid + flow) - 乘法、减法(计算权重)
- 张量索引与加权求和
✅ TensorFlow 的 Autodiff(自动微分)引擎会自动追踪这些操作,构建完整的计算图,并在反向传播时正确计算:
🔹 四、对比:为什么传统图像重采样不可微?
| 方法 | 是否可微 | 原因 |
|---|---|---|
| 最近邻插值(Nearest Neighbor) | ❌ 否 | 使用 argmin 或 round,梯度为 0 或未定义 |
OpenCV remap | ❌ 否 | C++ 实现,无梯度注册到 TF/PyTorch |
| 双线性插值(TF/PyTorch 实现) | ✅ 是 | 由基本可导运算组成,支持 Autodiff |
✅
SpatialTransformer的核心价值:用可微操作替代了传统不可微的图像 warping。
🔹 五、可视化理解:梯度如何流动?
假设损失函数为: [ \mathcal{L} = | W - F |_2^2 ] 其中 ( F ) 是固定图像。
反向传播路径:
Loss L
↑ (∂L/∂W)
Warped Image W = ST(moving, flow)
↑ (∂W/∂flow, ∂W/∂moving)
flow ← U-Net ← input
moving ← input
- 梯度 ( \frac{\partial \mathcal{L}}{\partial \text{flow}} ) 会告诉 U-Net:“你的位移场哪里预测错了”
- 梯度 ( \frac{\partial \mathcal{L}}{\partial \text{moving}} ) 在训练生成模型时也有用(如 CycleMorph)
✅ 总结:为什么 SpatialTransformer 可微?
| 原因 | 说明 |
|---|---|
| 1. 数学基础 | 双线性/三线性插值是光滑的解析函数,几乎处处可导 |
| 2. 操作原子性 | 实现仅使用加、乘、索引、求和等可导基本操作 |
| 3. 框架支持 | TensorFlow/PyTorch 的 Autodiff 能自动处理这些操作 |
| 4. 工程实现 | VoxelMorph 的 SpatialTransformer 显式构建在可微 ops 之上 |
💡 正是这种可微分性,使得 VoxelMorph 能在完全无监督(无需真实形变标签)的情况下,仅通过
warped ≈ fixed的相似性损失,端到端地训练出高质量的配准模型。
这不仅是工程技巧,更是将几何变换融入深度学习范式的理论突破。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
第四部分:解释公式W(x)=M(x+u(x)),为何插值公式本身可导
我们来深入解释公式:[ W(x) = M(x + u(x)) ]
以及为什么 插值公式本身是可导的,从而使得 ( W(x) ) 对源图像 ( M ) 和位移场 ( u(x) ) 都可导。这是理解可微分图像配准(如 VoxelMorph)的核心。
🔹 一、公式含义:( W(x) = M(x + u(x)) )
📌 符号说明:

🎯 直观理解:
“要把目标图像在 ( x ) 处的像素值设为:源图像在 ( x + u(x) ) 处的值。”
这相当于反向映射(inverse warping):
- 不是“把源图像的点推到新位置”,
- 而是“在目标位置 ( x ),回溯到源图像中找应该取哪个值”。
✅ 这种方式避免了空洞(holes)问题,是标准做法。
🔹 二、核心挑战:( M ) 只在整数格点有定义!
问题在于:
- ( x ) 是整数(如 ( x = (10, 20) ))
- 但 ( x + u(x) ) 通常是非整数(如 ( (10.37, 19.82) ))
- 而 ( M ) 只在整数坐标(如 ( (10,19), (11,20) ) 等)有值
→ 不能直接计算 ( M(x + u(x)) )!
✅ 解决方案:插值(Interpolation)
用邻近的已知像素值,估计非格点处的函数值。
最常用的是:
- 2D:双线性插值(bilinear interpolation)
- 3D:三线性插值(trilinear interpolation)
🔹 三、插值公式为何可导?以 2D 双线性为例
设采样点为:
令:
则双线性插值给出: (1)
✅ 为什么这个公式是可导的?
1. 对 ( M ) 可导(即对源图像像素值)
- 公式 (1) 是
的线性组合
- 所以偏导数就是对应的权重:
其他类似。
- 结论:( W ) 对 ( M ) 是线性且可导的。
2. 对 ( u(x) ) 可导(即对位移场)
- 注意:
- 在
时(几乎总是成立),有:
- 而权重如
是 α,β 的多项式函数 → 光滑可导
- 因此,通过链式法则:
而
可从公式 (1) 直接求出(见下文)。
📐 计算示例:
这是一个确定的、连续的表达式,只要 M 有定义就存在。
⚠️ 唯一不可导的点是当 ( x_s ) 或 ( y_s ) 恰好为整数(此时
floor函数不连续)。
但在连续分布下,这种点的概率为 0,在随机梯度下降中可忽略,实践中视为“处处可导”。
🔹 四、推广到一般情况
- 3D 三线性插值:类似,只是有 8 个邻点,权重是 (
) 等形式 → 仍是多项式 → 可导。
- 高阶插值(如 bicubic):虽然更复杂,但只要使用光滑基函数(如 cubic spline),依然可导。
- 深度学习框架(TF/PyTorch):将插值实现为一系列基本可导操作(加、乘、索引、sum),Autodiff 自动处理梯度。
🔹 五、为什么“整个 ( W(x) ) 对 ( M ) 和 ( u(x) ) 都可导”很重要?
因为这使得我们可以构建端到端可训练的配准模型:
# 伪代码
flow = unet(fixed, moving) # flow = u(x),可训练
warped = spatial_transformer([moving, flow]) # W(x) = M(x + u(x))
loss = similarity_loss(warped, fixed) # 比如 NCC 或 MSE
反向传播时:
- 梯度
→ 更新 U-Net
- 梯度
→ (如果需要)更新输入或生成器
✅ 没有可微分的 ( W(x) ),就无法训练!
✅ 总结
| 关键点 | 说明 |
|---|---|
| 公式 ( W(x) = M(x + u(x)) ) | 定义了形变图像:在 ( x ) 处取源图像在 ( x + u(x) ) 处的值 |
| 插值的必要性 | 因为 ( x + u(x) ) 非整数,需用邻近像素估计 |
| 插值可导的原因 | 权重是坐标的多项式函数,输出是像素值的线性组合 → 整体光滑可导 |
| 对 ( M ) 可导 | 因为是线性加权和 |
| 对 ( u(x) ) 可导 | 因为权重关于位移连续可微(几乎处处成立) |
| 工程意义 | 使无监督图像配准成为可能,支持端到端训练 |
💡 正是这种数学上的光滑性 + 框架的自动微分支持,让
SpatialTransformer成为现代可微分图像配准的基石。
---------------------------------------------------------------------------------------------------------------------------------
第五部分:何为反向映射(inverse warping),与源图像的点推到新位置的方式的差别
这是一个非常深刻且关键的问题!“反向映射”(inverse warping) 与 “前向映射”(forward warping) 在图像形变中看似等价,但在数值实现、结果质量和可微分性上存在根本差异。下面我们从原理、问题和实践三个层面详细解释。
🔹 一、两种映射方式的定义
✅ 1. 前向映射(Forward Warping)
- 思想:把源图像中的每个像素 “推”到目标位置。
- 公式:
- 即:源点 (x) 移动到 (y),目标图像在 (y) 处的值设为 (M(x))。
✅ 2. 反向映射(Inverse Warping)← VoxelMorph 使用的方式
-
思想:对目标图像中的每个位置,回溯到源图像中找应该取哪个值。
-
公式:
其中 (v(y)) 是从目标到源的位移(通常令 (v(y) = -u(x)),在小形变下近似)。
更常见的是直接写成: [ W(x) = M(x + u(x)) ] 这里的 (x) 是目标网格上的整数坐标,(x + u(x)) 是源图像中的采样位置。
📌 注意:虽然符号略有不同,但核心是——遍历目标网格,从源图像采样。
🔹 二、为什么前向映射(“推像素”)在数值上不等价?三大致命问题
❌ 问题 1:空洞(Holes / Gaps)
- 源图像的像素被“推”到目标位置后,某些目标位置可能没有被任何源像素覆盖。
- 尤其当形变非均匀时(如局部压缩/拉伸),会出现大片未定义区域。
✅ 反向映射天然避免此问题:每个目标像素都强制赋值(通过插值)。
❌ 问题 2:重叠(Overwrites / Collisions)
- 多个源像素可能被“推”到同一个目标位置。
- 那么该位置该取谁的值?取平均?最大值?这会引入人为伪影,且不可微。
✅ 反向映射无此问题:每个目标位置独立采样,互不影响。
❌ 问题 3:不可微分 & 非结构化输出
- 前向映射的结果是一个稀疏、非规则的点集,难以表示为标准张量。
- 要填满空洞还需额外后处理(如泊松重建),而这些操作往往不可导。
- 无法直接接入深度学习训练流程。
✅ 反向映射输出是规则网格上的稠密张量,天然兼容 CNN 和自动微分。
🔹 三、直观示例(2D 图像)
假设有一个 3×3 的源图像:
M = [[A, B, C],
[D, E, F],
[G, H, I]]
➤ 前向映射(错误方式):
- 把 E 推到 (1.7, 1.8),把 F 推到 (1.7, 1.8) → 冲突!
- 把 A 推到 (0.2, 0.3),但 (0,0), (0,1), (1,0) 等位置没人覆盖 → 空洞!
最终目标图像可能是:
W = [[?, ?, ?],
[?, X, ?], ← 只有少数位置有值
[?, ?, ?]]
➤ 反向映射(正确方式):
- 对目标位置 (0,0):采样源图像 (0 + u₀₀) ≈ (0.1, 0.2) → 插值得到值
- 对 (0,1):采样 (0.9, 1.1) → 插值得到值
- ……每个位置都有定义!
结果:
W = [[w00, w01, w02],
[w10, w11, w12],
[w20, w21, w22]] ← 完整、稠密、平滑
🔹 四、数学视角:函数 vs 映射
- 图像本质是一个函数:(
)
- 形变的目标是定义一个新函数 ( W ),使得 (
),其中 (
) 是空间变换。
- 反向映射直接构造了这个函数在规则网格上的采样。
- 前向映射只是给出了函数的一些离散样本点,无法完整定义 (W)。
📌 在连续域中两者等价,但在离散数值实现中,反向映射是唯一稳定、完整、可微的方式。
🔹 五、计算机视觉中的通用实践
- 所有现代可微分渲染/形变库都使用反向映射:
- VoxelMorph (
SpatialTransformer) - PyTorch:
torch.nn.functional.grid_sample - TensorFlow:
tf.contrib.resampler或自定义 bilinear sampler - OpenCV 的
remap函数也要求提供目标到源的映射(即反向)
- VoxelMorph (
✅ 这不是偶然,而是经过几十年实践验证的最佳方案。
✅ 总结:为什么必须用反向映射?
| 特性 | 前向映射(推像素) | 反向映射(采样) |
|---|---|---|
| 空洞 | ❌ 严重 | ✅ 无 |
| 重叠 | ❌ 冲突 | ✅ 无 |
| 输出结构 | 稀疏、不规则 | 稠密、规则张量 |
| 可微分性 | ❌ 难以实现 | ✅ 天然支持 |
| 插值支持 | ❌ 不适用 | ✅ 双线性/三线性 |
| 深度学习友好 | ❌ 否 | ✅ 是 |
💡 结论:
虽然在理想连续世界中“推”和“拉”是等价的,但在离散数字图像的世界里,只有反向映射(inverse warping)能产生完整、平滑、可微、无伪影的形变结果。
这就是为什么SpatialTransformer和所有现代配准/光流方法都采用这种方式。
所以,数值上并不一样——反向映射是唯一可行的工程方案。
383

被折叠的 条评论
为什么被折叠?



