返回列表

15th place solution

686. PhysioNet - Digitization of ECG Images | physionet-ecg-image-digitization

开始: 2025-10-21 结束: 2026-01-22 医学影像分析 数据算法赛
第 15 名解决方案 - PhysioNet ECG 图像数字化

第 15 名解决方案

作者: Arunodhayan
队友: @fatliuyun
竞赛排名: 15

概述

ECG 图像 → 信号掩码预测 → 后处理波形提取

核心思想是将信号提取与信号重建分离。通过学习在像素级别分割 ECG 轨迹,模型对现实世界的伪影(如网格失真、扫描噪声、纸张折叠、墨水褪色和遮挡)变得显著更 robust(鲁棒)。

为了捕捉全局 ECG 布局和细粒度波形细节,使用了两种互补的训练策略:

  • 全裁剪训练 (Full-crop training):保留完整的 ECG 上下文和长程信号连续性。
  • 半裁剪训练 (Half-crop training):专注于局部波形结构,提高对细微信号变化的敏感度。

这两种策略都采用轻量级的 ResNet 编码器(ResNet-18 和 ResNet-34d)并共享一个共同的解码头。它们的预测在推理过程中结合,以提高鲁棒性和泛化能力。

校正图像与掩码生成

校正图像

来自 @hengck23 的早期校正版本存在一个微妙的边界问题,即预测的网格没有完全跨越图像范围,导致最后 1-2 列像素在 warp( warping)期间映射错误或边框填充。这个问题通过强制固定输出画布、使用 (W−1, H−1) 归一化网格点以及在 upsampling 变形场时启用角点对齐插值 (align_corners=True) 得以修复。结果是,密集网格现在覆盖了完整的图像宽度,消除了边缘像素丢失,并显著改善了校正图像与监督掩码之间的像素级对齐,特别是在波形端点处。

# 修剪不稳定的网格列
gridpoint_xy = gridpoint_xy[:, :56]

# 校正扫描仪边距的经验偏移量
offset_y = 7
offset_x = 34

# 移除右侧噪声区域
image = image[:, :2166]

# 参考输出大小
H, W = 1700 - offset_y, 2200 - offset_x

# 分配填充的输出画布
empty_image = np.zeros((H + offset_y, W, 3), np.uint8)

# 将网格点归一化到 [-1, 1]
H1, W1 = image.shape[:2]
sparse_map = gridpoint_xy / [[[W1 - 1, H1 - 1]]] * 2 - 1
sparse_map = torch.from_numpy(
    np.ascontiguousarray(sparse_map.transpose(2, 0, 1))
).unsqueeze(0).float()

# 将稀疏网格插值为密集变形场
dense_map = F.interpolate(
    sparse_map,
    size=(H, W),
    mode="bilinear",
    align_corners=True
)

# 应用基于网格的 warp
distort = torch.from_numpy(
    np.ascontiguousarray(image.transpose(2, 0, 1))
).unsqueeze(0).float()

rectified = F.grid_sample(
    distort,
    dense_map.permute(0, 2, 3, 1),
    mode="bilinear",
    padding_mode="border",
    align_corners=True
)

# 转换回图像格式
rectified = rectified.data.cpu().numpy()
rectified = rectified[0].transpose(1, 2, 0).astype(np.uint8)

# 恢复垂直偏移以进行对齐
empty_image[offset_y:, :, :] = rectified

return empty_image

掩码生成

系列数量:4

输出掩码形状:(4, 1696, 4352)(高度 × 宽度对应于校正后的 ECG 图像)

系列 零毫伏 (Zero-MV) 毫伏 --> 像素 (mV --> Pixel)
0 707 78.0
1 991 79.5
2 1273 78.5
3 1533 78.0

训练

为了平衡全局 ECG 布局理解与局部波形精度,采用了两种互补的训练策略:全裁剪训练和半裁剪训练。每种策略都解决了早期实验中观察到的不同失败模式。

全裁剪训练 (全局上下文)

在全裁剪训练中,模型在整个校正后的 ECG 图像上进行训练,保留所有导联的完整时间跨度和垂直对齐。

  • 捕捉 ECG 信号的长程时间连续性
  • 保留相对的导联位置和基线一致性
  • 提高信号边界(开始/结束区域)附近的稳定性
  • 帮助模型学习整体 ECG 结构和布局

半裁剪训练 (局部细节)

半裁剪训练使用随机水平裁剪,覆盖大约一半的图像宽度,同时保留完整的垂直分辨率。裁剪区域在训练前调整回原始尺寸。

为什么半裁剪很重要:

  • 迫使模型专注于局部波形形态
  • 提高每个信号段的有效分辨率
  • 提高对局部噪声、断裂和遮挡的鲁棒性
  • 作为一种强大的数据增强形式

数据增强策略

几何增强

增强 参数 概率
仿射变换 (Affine transform) 缩放 0.90–1.10, 旋转 ±8°, 平移 ≤4% 0.60
透视变换 (Perspective transform) 缩放 0.02–0.07 0.40
光学失真 (Optical distortion) 失真 ≤0.03, 偏移 ≤0.02 0.15
弹性变换 (Elastic transform) Alpha = 10, Sigma = 3 0.03
网格失真 (Grid distortion) Steps = 3, 失真限制 = 0.03 0.05
仿射 (剪切) 剪切 ±3° 0.15

光度增强

增强 参数 概率
亮度 / 对比度 ±0.15 0.50 (OneOf)
CLAHE Clip = 2.0, Grid = 8×8 0.30 (OneOf)
随机 Gamma 0.8–1.2 0.20 (OneOf)
高斯噪声 方差 5–20 0.50 (OneOf)
ISO 噪声 0.30 (OneOf)
乘法噪声 0.9–1.1 0.20 (OneOf)
色相 / 饱和度 / 值 H±2, S±5, V±5 0.15

模糊与锐化

增强 参数 概率
锐化 (Sharpen) Alpha 0.05–0.15 0.20
高斯模糊 核大小 3–5 0.60 (OneOf)
运动模糊 核大小 3–5 0.40 (OneOf)

ECG 特定遮挡

增强 目的 概率
CoarseDropout (小) 墨水断裂 / 污渍 0.25
CoarseDropout (垂直) 打印机 / 扫描垂直线 0.20
Perlin 噪声 patches 纸张污垢 / 老化伪影 ~0.50 (自定义)

后处理与信号优化 (第二阶段)

在基于分割的信号提取之后,应用额外的时间细化和间隙填充以恢复生理上一致的 ECG 波形。此后处理侧重于处理缺失段、强制周期一致性以及稳定 QRS 形态。

1) 缺失段检测与填充

  • 二进制掩码指示缺失点 (mask = 1)。
  • 检测每个导联中长于最小长度的连续缺失运行。
  • 这些运行填充有同一导联中附近观测值的平均值。
  • 防止长平坦间隙,同时保持基线一致性。

2) 基于心跳相位感知的插值 (关键步骤)

  • ECG 被划分为固定的 1250 样本段。
  • 在所有导联中检测全局 R 波峰。
  • 对于缺失点 t:
    • 在同一段中找到最近的 R 波峰。
    • 计算相位偏移 k = t − R_peak。
    • 从同一段中的其他周期收集对应点 (R_j + k)。
    • 如果存在多个有效值,则平均它们以填充 t。
  • 强制周期一致性并保留波形形态。

3) 鲁棒 R 波峰检测

  • 带通滤波 (5–18 Hz) 强调 QRS 复合波。
  • 应用 Pan-Tompkins 风格的移动窗口积分 (MWI)。
  • 能量包络 across 导联融合。
  • 具有回搜逻辑的自适应阈值恢复 missed beats。
  • 波峰被细化为真正的阳性 QRS 最大值。
  • 自动选择具有最强 QRS 能量的参考导联。

4) 模型级时间细化

  • 多尺度时间卷积 (TCN) 捕捉局部和长程依赖关系。
  • 傅里叶谐波特征建模主导心脏周期性。
  • 门控融合混合时域和频域信息。
  • 残差双向 LSTM 强制时间平滑度。
  • 模型仅学习缺失区域的修正;观测点被保留。

5) 输出保证

  • 观测(非缺失)点从不被修改。
  • 填充值尊重局部连续性和全局心脏节律。
  • 不应用激进的 low-pass 滤波;保留 sharp QRS 形态。
同比赛其他方案