558. Vesuvius Challenge - Ink Detection | vesuvius-challenge-ink-detection
大家好!这里是我在本次比赛中最佳私有排行榜解决方案的总结(前13名,得分0.641814,公共排行榜得分0.723345)。
我非常感谢组织者和参赛者们,这场比赛非常有趣且富有挑战性。我学到了很多关于图像分割的知识,也获得了很多乐趣!我解决方案中的一些想法来自于Kaggle的代码和讨论区,希望我已经全部注明了出处。
如果您有任何问题,请随时在评论区留言。
受此分析启发,我绘制了每个碎片每层的平均强度曲线,包括完整图像、仅墨水和仅背景。

从图中可以观察到,碎片1和3在强度值以及最小和最大位置方面较为相似,而碎片2则不同。
我背后的物理直觉是:纸莎草在3D体积内似乎没有按深度对齐,而且纸莎草的厚度在碎片1、3和碎片2之间似乎有所不同。
因此,我决定通过位移和缩放操作在Z轴方向对齐并拉伸/压缩纸莎草:
z_shift参数以最小化以下损失(y_target为3号碎片的完整强度曲线)z_shifted = z + z_shift
f = interpolate.interp1d(z_shifted, y, bounds_error=False, fill_value='extrapolate')
return f(z_target) - y_target
z_scale(假设同一碎片的纸莎草厚度相同)z_shift和z_scale图线性上采样至完整体积大小并保存到磁盘scipy.ndimage.geometric_transform应用z_shift和z_scale到完整体积,变换函数用C模块编写以提高速度得到的z_shift图和z_scale值如下:
| 碎片1 | 碎片2 | 碎片3 |
|---|---|---|
![]() |
![]() |
![]() |
Z缩放值:碎片1和3约为1.0,碎片2约为0.6,这表明碎片2的厚度约为其他碎片的两倍。
这种操作对2D方法应该是有益的。它似乎能将CV分数提高约0.03-0.05 F05,但由于时间限制,我没有进行完整的对比实验。
此外,我尝试分别拟合/计算每个碎片的归一化以完全匹配强度曲线,但效果不佳。
预训练的maxvit编码器 + SMP 2D unet与聚合在CV分数上表现最佳,因此最佳解决方案使用的是maxvit_rmlp_base_rw_384.sw_in12k_ft_in1k。
2D unet与聚合的工作方式如下:unet模型应用于输入的每个切片(沿Z维度大小=步长=3,共6个切片),然后6个输出的logits通过线性层聚合。
使用预训练模型比随机初始化产生的效果好得多。不幸的是,我没有想出更好的架构来处理3D输入和2D输出,同时允许使用预训练模型。
该模型的最佳CV分数及相应预测如下:
| 碎片 | F05分数 | 预测结果 |
|---|---|---|
| 1 | 0.6544 | ![]() |
| 2a | 0.6643 | ![]() |
| 2b | 0.7489 | ![]() |
| 2c | 0.6583 | ![]() |
| 3 | 0.7027 | ![]() |
尝试过但效果不佳的方法
多种骨干网络模型:
以及多种完整模型方法:最后一层聚合的全3D模型、2.5D、带聚合的2D、从2D转换为3D的自定义3D模型并使用预训练权重。
训练时使用以下增强:
train_transform = A.Compose(
[
RandomCropVolumeInside2dMask(
base_size=self.hparams.img_size,
base_depth=self.hparams.img_size_z,
scale=(0.5, 2.0),
ratio=(0.9, 1.1),
scale_z=(1 / self.hparams.z_scale_limit, self.hparams.z_scale_limit),
always_apply=True,
crop_mask_index=0,
),
A.Rotate(
p=0.5,
limit=rotate_limit_degrees_xy,
crop_border=False,
),
ResizeVolume(
height=self.hparams.img_size,
width=self.hparams.img_size,
depth=self.hparams.img_size_z,
always_apply=True,
),
A.HorizontalFlip(p=0.5),
A.VerticalFlip(p=0.5),
A.RandomRotate90(p=0.5),
A.RandomBrightnessContrast(p=0.5, brightness_limit=0.1, contrast_limit=0.1),
A.OneOf(
[
A.GaussNoise(var_limit=[10, 50]),
A.GaussianBlur(),
A.MotionBlur(),
],
p=0.4
),
A.GridDistortion(num_steps=5, distort_limit=0.3, p=0.5),
A.CoarseDropout(
max_holes=1,
max_width=int(self.hparams.img_size * 0.3),
max_height=int(self.hparams.img_size * 0.3),
mask_fill_value=0, p=0.5
),
A.Normalize(
max_pixel_value=MAX_PIXEL_VALUE,
mean=self.train_volume_mean,
std=self.train_volume_std,
always_apply=True,
),
ToTensorV2(),
ToCHWD(always_apply=True),
],
)
其中,RandomCropVolumeInside2dMask从体积中裁剪随机3D补丁,使其中心点(x, y)位于遮罩内;ResizeVolume是三次线性3D缩放;ToCHWD仅置换维度。参数设置为:img_size = 384,img_size_z = 18,z_scale_limit = 1.33,rotate_limit_degrees_xy = 45,train_volume_mean和train_volume_std为ImageNet统计量的平均值。
推理时使用以下变换:
test_transform = A.Compose(
[
CenterCropVolume(
height=None,
width=None,
depth=math.ceil(self.hparams.img_size_z * self.hparams.z_scale_limit),
strict=True,
always_apply=True,
p=1.0,
),
ResizeVolume(
height=self.hparams.img_size,
width=self.hparams.img_size,
depth=self.hparams.img_size_z,
always_apply=True,
),
A.Normalize(
max_pixel_value=MAX_PIXEL_VALUE,
mean=self.train_volume_mean,
std=self.train_volume_std,
always_apply=True,
),
ToTensorV2(),
ToCHWD(always_apply=True),
],
)
其中,CenterCropVolume是3D体积的中心裁剪。
还尝试了混合方法(cutmix、mixup、复制粘贴正类体素等自定义方法),但结果相互矛盾,因此未包含在最终方案中。
TTA使用无翻转/H翻转/V翻转与无旋转/90度/180度/270度旋转的笛卡尔积,共产生12次预测。
更新:训练项目的源代码可在此处找到。