CZII语义分割与物体检测竞赛方案总结
汇总了五个值得参考的TOP方案,希望对你的CV学习有帮助
前言
以下是一份将五份CZII - CryoET Object Identification TOP方案的关键思路融合而成的综合解决方案, 对于3D物体分割具有很好的参考价值。该方案在数据准备、模型设计、训练方法、推理、后处理等方面都有涉及,可作为完整思路参考。
1. 数据准备与探索
- 体数据归一化
- 使用类似 NormalizeIntensityd(MONAI)或 PyTorch 中的 z-score 归一化方式,为扫描数据带来一致性。
- 如果不同样本的强度分布差异很大,可考虑实例级或全局的 min-max 归一化。
- 分割半径 / 热力图生成
- 多个方案都对每种粒子类型(如 apo-ferritin, beta-galactosidase, ribosome 等)采用不同的自定义半径(或高斯 sigma)。
- Gaussian Function 对于 Mask的处理(实现代码在最后,仅供参考,未经实测)
- 使用高斯函数来创建掩码(或称热图)是一种将粒子位置信息以平滑、连续的方式表达的方法,而不是简单地将某个像素点标记为1,其余全部为0。
- 高斯函数在粒子中心处取值最高(例如1.0),随着距离增加,值逐渐降低。平滑数据能为模型提供更多的梯度信息,更精确地学习到粒子的中心位置。
- 真实图像中的粒子中心可能由于噪声或成像问题而存在不确定性。使用高斯“斑点”可以反映这种不确定性,使得模型在训练过程中不必严格拘泥于一个精确的点,而是可以在一定范围内进行判断。
- 平滑的高斯分布能够提供连续的误差信号,这使得模型在反向传播时得到的梯度更加平滑,有助于稳定训练过程,加速收敛。
- 在某些模型中(如tattaka的模型),会根据粒子的实际大小采用不同的sigma值。较大的粒子使用较大的sigma值,生成的高斯分布更宽;较小的粒子则使用较小的sigma值,生成的分布更窄。
- 如果使用热力图方法,可以为小粒子使用 较小半径,为大粒子使用 稍大半径,以平衡精确定位与多粒子重叠的矛盾。
- 据第四名方案中提到,在Disscussion贴中标定的中心点计算有偏差。
半径缩放:

位置偏移:

- 数据增强
- 旋转与翻转:
- 沿 Z 轴随机 ±90° 旋转,并做小角度(±10°)的 X/Y 轴旋转。
- 沿 X、Y、Z 轴随机翻转。
- 强度扰动 (intensity shifts):
- 对体数据加少量高斯噪声或亮度平移,帮助模型泛化。
- 裁剪 / 采样策略:
- 使用 RandCropByLabelClassesd 或类似的方法平衡正负样本(包含或不包含粒子的区域)。
- (可选)粘贴 / Mixup:
- 若数据量稀少,可尝试从其他体数据中复制小块含粒子区域并粘贴到当前样本空白区。但需通过交叉验证来验证增益。
2. 模型结构
下面介绍一种结合多种高分方案灵感的 2.5D + 3D 混合式 网络,也可替换成纯 3D UNet 或 SegResNet 结构,核心思路大同小异。
- Backbone(主干)
- 使用 2.5D UNet,在 2D 骨干网络(如 ResNet101、ConvNeXt、EfficientNet B3 等)基础上:
- 通过平均池化或最小步幅卷积在深度维度聚合信息,减少显存使用。
- 在保留 2D 特征提取能力的同时,保证一定的 3D 语义上下文(如在每个 stage 后适度进行 3D 卷积或 pooling)。

- Decoder & Upsampling
- 检测头(Head)可选方案
- 纯热力图(分割方式):输出 3D 热力图(C 通道),再通过局部极大值搜索中心点坐标。
- 无锚点(Anchor-Free)偏移回归:除了输出分类热力图,还预测每个像素到粒子中心的偏移量,可提升精度,尤其适合坐标回归。
3. 损失函数
根据最后的输出类型(纯热力图或热力图 + 偏移),可选择不同损失:
- 热力图分割类损失
- Tversky Loss + Cross Entropy:能很好处理前景/背景不平衡,效果稳定。
- MSE / Varifocal:如果纯热力图回归(第 4 名方案的做法),也可以用 MSE 进行简单直接的监督,但注意平衡正负样本。
- 无锚点检测类损失
- CenterNet-Style Focal 或 Varifocal 用于分类热力图。
- Smooth L1 / IoU-based Loss 用于偏移回归。
- 点与点之间的距离 IoU(如 exp(−∥Δx∥^2/(2r^2))可模拟边框 IoU 的概念。
- 无锚点检测完整代码来自第一名方案。
4. 训练策略
- 交叉验证
- 建议采用 5 折(或 6+1)CV,根据体数据数量做最优切分。
- 为每折保存多个最佳权重(如 Fβ\betaβ 排名前 3~5 个 checkpoint)以进行后续集成。
- Patch(块)级训练
- 常用 patch 大小为 [64–128, 128–256, 128–256],具体视显存决定。
- 验证 / 推理时可采用与训练相同或更大尺寸(如 [128, 256, 256]),以获取更完整上下文。
- 学习率 & 优化器
- 多数方案使用 AdamW + 小基准 LR(如1e-3或5e-4)。
- LR 调度可用 Cosine 或简单 “Step” 策略。
- 若训练不稳定,可调 β1,β2(如 β1=0.7,β2=0.9996)或开启梯度裁剪。
- 模型选择
- 每轮计算在验证集上的 Fβ 或自定义检测指标。
- 若分数长期无提升,可早停(patience=20~30)。
- EMA 或 SWA
- 在训练后期,对模型权重应用 EMA(指数滑动平均) 或 SWA(随机权重平均),常能带来小幅但稳定的提升。
- EMA技术详细信息可参考此篇文章。
5. 推理 & 分块处理(Tiling)
- 滑窗推理(Sliding-Window)
- 使用与训练类似的窗口尺寸或更大(如 [192, 256, 256])。
- 设置合理的重叠(~50%),避免边缘效应。
- 重叠区域使用 加权平均 融合,每块中心的权值较高,边缘较低。
- stride / 分辨率
- 第一名的物体检测方案中,推理采用 stride=2 或 stride=4的Feature Map,能显著降低推理时间(或显存)。
- 当 stride 从 1 调整到 2,性能仅下降约 0.002,但推理速度可提升约 50%。
- 若使用 stride=2/4,后处理要注意坐标缩放。
- 采用Pixel Shuffle的上采样中 stride = 4,也有助于推理加速。
- 多 GPU & 加速
- 如果竞赛环境允许,可在 2 张 T4 上并行推理,加速一倍。
- 使用 TensorRT(或 ONNX/OpenVINO)进行推理,可以获得 2×~3× 的加速。
6. 后处理:粒子中心点
根据是否是 纯热力图 或 热力图 + 偏移 的方法,后处理略有不同:
- 局部极大值 & 阈值过滤
- 先用 3D 最大池化(如 3×3×3 或 7×7×7 kernel)找到局部极大值。
- 针对不同粒子类型,设置不同置信度阈值(或统一阈值),过滤低分检测。
- 偏移修正(若使用无锚点检测 Head)
- 获取 offset 分支输出,为每个峰值做坐标细化。
- 将 voxel 索引映射回原图坐标系,如果训练时中心做了 +0.5 / +1.0 像素偏移,请在推理时反向修正。
修正前 vs 修正后:


- NMS / 分裂合并
- 贪心式 3D NMS:同类粒子点若相距很近(或点 IoU 大),可合并或剔除置信度较低者。
- 连通域(如 cc3d,connected-components)或自定义算法(如第 10 名方案的 KL-based 颗粒分裂),识别并拆分重叠较大的区域。
- 坐标缩放
- 预测坐标最后要乘以 体素-物理单位 比例(如 10.0 /voxel),变换到真实单位坐标。
7. 集成策略
关于集成:一般都会选择集成,并且在本次方案之中有多模型集成,但从实际公布的效果上来看,单模的性能也是很不错的。所以对于显卡紧张的朋友可以专注于CV单模。(仅个人推荐)
- 模型多样性
- 训练多个不同骨干(ResNet101、ConvNeXt、SegResNet、DynUNet 等)。
- 变化超参(patch size、batch size、stride 等),保证模型多样性。
- 甚至可混合 纯分割模型 与 点检测模型,可能获得更高收益。
- 融合方式
- 分割 / 热力图模型:可在输出热力图或 logits 级直接加权平均,再做一次后处理。
- 点检测模型:可对最终点集进行后期融合(如多组检测结果合并,再用 NMS 去重)。
- 平均融合 效果常见且易操作,也可根据验证 Fβ 对不同模型做加权。
- 效率平衡
- 越多模型推理越慢。需在分数提升与 12 小时推理限制之间取得平衡,留意速度瓶颈。
8. 时间与显存优化
- 降低输出步幅(Stride)
- 像第一名的物体检测方案提到,通过将最后层 stride 从 1 调到 2,可极大提升推理效率,性能损失很小。尝试 stride=2 或 4,若 Fβ 下降可接受,则可显著减少推理时间。
- TensorRT / ONNX
- 将 PyTorch 模型先导出为 ONNX,再转成 TensorRT。
- 核查推理正确性后测量实际加速效果。
- 并行化
- 充分利用竞赛环境的多 GPU(如 T4×2)并行处理不同体或不同 sliding window。
- 智能切片
- 减少过多切片带来的重叠计算。
- 适度增大补丁尺寸可降低拼接次数,但需留意显存。
9. 总结
据以上内容来看,众多模型架构涉及物体检测,2.5DUnet,3DUnet等方案,其最终表现非常近似。


多种骨干网络性能也很近似:

实际效果的重点推测应处于数据处理方面,比如高斯平滑,定位点的抵消以及对于热力图输出的后处理。(未实测,仅推测。)
在分割后使用CC3D的定位方案中, 综合来看最通用且效果较好的是 Weighted-Cross-Entropy Loss。
10. 代码
3D物体高斯MASK处理:
import numpy as np
import matplotlib.pyplot as plt
def generate_3d_gaussian_mask(volume_shape, center, sigma):
"""
生成一个3D高斯掩码(热图)
参数:
volume_shape: 3D体积的尺寸,格式为 (深度, 高度, 宽度)
center: 粒子中心的坐标,格式为 (z, y, x)
sigma: 高斯分布的标准差,可以是标量,也可以是 (sigma_z, sigma_y, sigma_x) 的元组
返回:
3D numpy数组,表示高斯热图(中心值归一化为1.0)
"""
# 如果sigma是标量,则转换为元组
if np.isscalar(sigma):
sigma = (sigma, sigma, sigma)
# 创建坐标网格
z = np.arange(0, volume_shape[0])
y = np.arange(0, volume_shape[1])
x = np.arange(0, volume_shape[2])
# 使用 'ij' 索引顺序生成 3D 坐标网格
zz, yy, xx = np.meshgrid(z, y, x, indexing='ij')
# 计算3D高斯分布:exp(-((z-center_z)^2/(2*sigma_z^2) + (y-center_y)^2/(2*sigma_y^2) + (x-center_x)^2/(2*sigma_x^2)))
gaussian = np.exp(-(((zz - center[0]) ** 2) / (2 * sigma[0] ** 2) +
((yy - center[1]) ** 2) / (2 * sigma[1] ** 2) +
((xx - center[2]) ** 2) / (2 * sigma[2] ** 2)))
# 归一化,使得中心的值为1.0
gaussian = gaussian / gaussian.max()
return gaussian
if __name__ == "__main__":
# 定义3D体积尺寸,例如一个64x64x64的体积
volume_shape = (64, 64, 64)
# 定义粒子中心(高斯分布中心)的位置
center = (32, 32, 32)
# 定义sigma,可以使用标量或元组,标量会在三个维度上使用相同值
sigma = 6 # 或者 sigma = (6, 6, 6)
# 生成3D高斯掩码
mask = generate_3d_gaussian_mask(volume_shape, center, sigma)
print("生成的3D高斯掩码形状:", mask.shape)
# 可视化中央切片(例如 z=32 这一层)
plt.imshow(mask[32], cmap='hot')
plt.colorbar()
plt.title("中央切片的3D高斯掩码")
plt.show()
Pytorch 内置 Pixel Shuffle 使用:
import torch
import torch.nn as nn
# 示例:上采样因子为 2(对应 stride=2 的 feature map)
upscale_factor = 2
pixel_shuffle = nn.PixelShuffle(upscale_factor)
# 假设输入特征图尺寸为 (batch_size, C, H, W)
# 为保证 pixel shuffle 正常工作,要求 C 必须是 upscale_factor^2 的倍数
# 例如,如果原本希望输出通道数为 out_channels,则输入的通道数应为 out_channels * (upscale_factor ** 2)
batch_size = 1
out_channels = 3
H, W = 32, 32
C = out_channels * (upscale_factor ** 2) # 比如 upscale_factor=2,则 C=3*4=12
# 创建一个随机 tensor
x = torch.randn(batch_size, C, H, W)
print("输入尺寸:", x.shape) # (1, 12, 32, 32)
# 使用 pixel shuffle 上采样
output = pixel_shuffle(x)
print("输出尺寸:", output.shape) # 输出尺寸:(1, 3, 64, 64)
11. 参考内容
评论
目录