☰
600张超声图训练甲状腺结节分割:U-Net实战与避坑指南
2026/9/28 8:02:16 网站建设 项目流程

简介:这是一份医学图像分割数据集,面向从事超声影像分析、甲状腺结节检测的研究人员与算法开发者,用于训练和评估分割模型。数据集内含超过600例超声图像及对应分割标签,经对比度拉伸、resize等图像增广后形成1277个文件,其中以PNG图像为主(1275张),另附classes.txt类别说明与Python脚本,整体压缩包约25.13MB。数据已预先划分训练集与验证集,可直接用于模型训练和指标验证;测试集则由使用者自行划分,以保证评估过程的公正性。目前已有700人学习下载,适合作为甲状腺结节超声分割研究的入门或基准数据集。借助这份资源,研究者可省去数据收集与预处理环节,专注于分割算法设计与实验对比,快速开展相关课题。

1. 600张超声图做甲状腺结节分割:这个数据集到底处在什么位置

如果你手里刚好有超过600张超声图像和对应的结节分割标签,那你已经站在医学图像分割最舒服的一条起跑线上。医学图像分割里最难的不是模型,而是带标注的数据;甲状腺结节分割这类任务,数据规模不需要像自然影像那样动辄几万张,600张带标签的超声图足够把U-Net一类的分割流程完整跑通,并看到可用的结果。这套数据解决的正是“想做甲状腺超声AI但缺标注数据”的起步问题:图像是B超,任务是给结节画出边界,标签已经对齐到像素级。适合两类读者:一是刚入门分割、想找一个“不算太小也不至于复杂到跑不动”的数据练手;二是已经在做超声影像处理、想用这个小样本验证增强策略和后处理流程的人。先说清楚,这个规模下不会存在“下载即SOTA”,但它是验证工程流程和踩坑经验的好样本。

2. 拿到数据集先读标签:超声结节分割的标注形态与格式坑

很多人在600多张图上翻车,不是模型问题,而是第一步就读错了标签。超声图像分割数据集的标签存储方式不统一,直接决定你的 DataLoader 怎么写。先别急着训练,把格式摸清,能省掉后面一大半排障时间。

2.1 常见的两种存储方式:掩码图与JSON多边形

超声甲状腺结节分割数据集最常见的标签组织方式是两种。

第一种是“原图 + 单通道掩码图”。掩码图和原图分辨率一致,背景是0,结节区域是255或1,通常就是images/和labels/两个目录。这种格式最省事,PIL.Image.open()直接读,不需要额外解析。

第二种是“原图 + JSON 多边形坐标”。每个图像文件对应一个.json文件,里面记录一组或多组轮廓坐标点,训练前需要把多边形栅格化成掩码。这种格式更接近医生的标注习惯,因为超声科医生本来就是用轨迹球在屏幕上勾轮廓,而不是一个像素一个像素地刷。

判断方式很简单:进目录后看文件后缀。如果看到大量同名 PNG,一个灰度、一个全黑但结节区域是白色,就是掩码格式;如果看到.json或.xml,就是轮廓格式。JSON 和 XML 只是外壳不同,核心都是坐标点,解析方法改一下就行。别小看这个环节,我见过有人把 mask 当成原图喂给模型,模型等于在学“把全黑图预测成全零矩阵”,白白跑了几十个 epoch 才发现。

掩码图用起来直接,JSON 反而有一个必须确定的参数:轮廓内是否填满。有的标注文件只存了闭合多边形的顶点,有的在语义上表示“这条线围住的区域都是结节”。用ImageDraw.polygon栅格化时,outline=1, fill=1两个参数必须写清楚,只画线不填充,出来的 mask 就是一条空心环,损失函数会在环上反复震荡。

2.2 用Python把标签读出来并和原图叠一次

无论格式是什么,先写一小段读取代码,把原图和标签叠在一起,用肉眼确认配对正确,再做后续。下面这段代码我每次拿到新分割数据集都会先跑一次。

import numpy as np from PIL import Image, ImageDraw # 方式A:标签是单通道PNG掩码 image = np.array(Image.open("sample.png").convert("L")).astype(np.float32) mask = np.array(Image.open("sample_mask.png").convert("L")) mask = (mask > 127).astype("uint8") # 兼容255/128/1多种取值,统一成0/1 # 方式B:标签是JSON多边形坐标点 import json with open("sample.json", encoding="utf-8") as f: ann = json.load(f) points = ann["points"] # 常见key是points,也可能是regions/shapes/polygon polygon = [(p["x"], p["y"]) if isinstance(p, dict) else tuple(p) for p in points] mask_from_poly = Image.new("L", (image.shape[1], image.shape[0]), 0) ImageDraw.Draw(mask_from_poly).polygon(polygon, outline=1, fill=1) mask_from_poly = np.array(mask_from_poly, dtype="uint8")

代码逻辑分两块:掩码图读取时用convert("L")强制转到灰度,避免原图是 RGB 三通道、mask 是四通道 PNG 时读进来 shape 对不上;阈值判断用> 127,是为了兼容标签值为 255 和 1 两种写法,统一成 0/1 之后,后面计算 Dice 才不会出现“一个 mask 最大值是255,另一个最大值是1,损失函数算出个怪异值”的尴尬。

JSON 分支里,isinstance(p, dict)是为了兼容两种坐标存储风格:一种存[x, y]列表,一种存{"x": .., "y": ..}字典。outline=1, fill=1是把闭合多边形内部全部填充成 1。如果 JSON 里有多个结节,必须循环画出所有 polygon,再合并到同一张 mask 上,漏掉一个就少一个正样本区域。

之后做叠加图:

import matplotlib.pyplot as plt plt.figure(figsize=(8, 4)) plt.subplot(121); plt.imshow(image, cmap="gray"); plt.title("B-mode") plt.subplot(122); plt.imshow(image, cmap="gray") plt.imshow(mask, cmap="jet", alpha=0.4); plt.title("overlay") plt.savefig("check_overlay.png", dpi=150)

叠加图里如果看到 mask 边界和结节轮廓明显错位,先查是不是原图和 mask 分辨率不一致。有些数据集在发布前把原图缩放过,但 mask 还是原始尺寸,np.array(Image.open(...))的 shape 对不上时,模型训练第一轮就会报广播错误。

2.3 标注质量的初次体检:边界模糊与漏标

600 张图里,最影响后期 Dice 的不是网络结构,是标签本身。第一次全量叠图做完后,我会做三件事。

第一,看 mask 是否刚好盖住结节。超声图像里甲状腺结节的边界本身模糊,医生标注时通常按“低回声区边界”勾,但不同标注者掌握的尺度不一样:有人标得紧,只包含实性部分;有人标得松,把周边低回声晕圈也带进去。这种差异直接决定你能达到的 Dice 上限。如果标签之间本身相差 3-5 个像素,模型不可能在测试集上做到 Dice 0.9 以上。

第二,找漏标。有些图像上结节非常明显,但对应 mask 是全黑。常见原因是数据整理时某张掩码文件丢失或配对错位。处理方式是先把这些样本筛出来看,而不是让模型强行学“有结节但标签为空”的矛盾样本。漏标样本放进训练集,相当于给模型发错误信号;放进验证集,则会把你的评估指标拉低。

第三,看尺寸分布。600 多张里如果大多数是微小结节,少数是大结节,损失函数会被大结节主导,小结节的边界永远学不好。先把每张图的 mask 面积记录下来,生成一个metadata.csv,后面按尺寸分组评估时直接用。

超声原图还有两个常见坑顺便说掉。一是分辨率不统一:同一个数据集里可能有 512×512、800×600 甚至带设备厂商水印的大图,标签如果没跟着一起 resize,后面就要在 DataLoader 里做大量对齐。二是灰度深度:有的图保存成 16bit,直接读出来数值范围是 0-65535,不归一化直接训练,模型一开始就会因为梯度爆炸学不动。统一转成 8bit:image = image / image.max() * 255,或者用skimage.exposure.rescale_intensity做百分位截断。

下面是一份我整理标签时习惯用的落地目录,你不需要照抄,但保持这个文件结构能让后面所有脚本少写很多路径判断。

路径内容作用
images/超声原图 PNG训练输入
labels/对应分割掩码 PNG监督信号
metadata.csv文件名、mask面积、结节尺寸、设备来源分层评估与筛选
split/train.txt训练集文件名划分集合
split/val.txt验证集文件名调参与早停
split/test.txt测试集文件名最终评估

3. 处理数据集用于分割训练:从600张超声图到可训练样本

标签读进来只是第一步。超声图像的特点是噪声重、灰度分布漂移、结节占比小,如果不做针对性处理,直接把 600 张图塞进网络,大概率训练出来的模型只能在原图上“背答案”。

3.1 超声图像预处理要点:归一化、去噪、增强

超声与自然图像、CT 的灰度语义完全不同。CT 有标准的亨氏单位,不同设备扫描同一组织灰度基本一致;超声的灰度取决于探头频率、增益、深度和操作者的扫查角度,同一结节在不同机器上可能一个显示为低回声、一个显示为等回声。所以归一化不要用自然图像里那套“整个数据集统一减均值除标准差”,超声图更适合每张单独做百分位截断。

我常用的做法是:把每张原图先 clip 到 1% 和 99.5% 分位数之间,再做线性拉伸到 0-1,最后按单张图减均值除标准差。注意这里的均值和标准差是按图算,不要跨图统计。如果某张图整体偏暗,跨图归一化会把它的对比度压没,结节直接消失在背景里。

去噪方面,超声斑点噪声是乘性噪声,中值滤波是性价比最高的选择。核大小选 3 或 5,不要选 7 以上——中值滤波核越大,结节边缘被磨得越圆,和标签轮廓的偏差越大。双边滤波理论上能保边,但在 600 张的数据规模下,它对最终 Dice 的提升通常不超过 0.01,反而让预处理时间翻倍。我一般把核为 3 的中值滤波放在训练样本读取之后、数据增强之前,当作固定预处理,而不是离线把整批图全部滤一遍存成新图。

增强环节有一个甲状腺特有的取舍:水平翻转要慎用。甲状腺的左叶和右叶在超声图像中是镜像结构,如果做二值分割,翻转后标签也跟着翻转,模型本身能学;但如果你打算在测试阶段用 TTA,翻转就要慎用。测试时翻转原图得到预测,再翻转回来做平均,这个操作对非对称器官有时会引入错误的边界定位。我的习惯是训练时只用旋转、缩放、亮度和弹性形变,测试时不用翻转类 TTA。

增强强度也要匹配小数据集。旋转角度 ±15°,缩放 0.9-1.1,亮度扰动 ±20%,弹性形变 sigma 取 3。强度再大,超声图像里的解剖结构会失真,模型学到的是“扭曲的纹理”,而不是结节边界。

3.2 ROI裁剪与patch采样:小数据集下提高训练效率

600 张原图如果直接缩放到 512×512 输入,GPU 显存占用高,而且一旦结节在图中占比小,下采样会把结节细节直接抹平。常见做法是先用标签的 bbox 裁剪一个以结节为中心的 ROI,再统一 padding 到固定尺寸。这样相当于在计算资源和显存都不变的情况下,把有效分辨率放大了。

import numpy as np def crop_to_nodule(image, mask, crop_size=192, margin=16): """按标签裁剪ROI,并padding到crop_size,返回(image, mask)均为(H=W=crop_size)""" ys, xs = np.where(mask > 0) if len(ys) == 0: return None, None # 全黑标签先跳过,单独人工复核 y_min, y_max = ys.min(), ys.max() x_min, x_max = xs.min(), xs.max() # 在bbox外扩margin,并限制在图像范围内 y_min = max(y_min - margin, 0) y_max = min(y_max + margin, image.shape[0]) x_min = max(x_min - margin, 0) x_max = min(x_max + margin, image.shape[1]) crop = image[y_min:y_max, x_min:x_max] crop_mask = mask[y_min:y_max, x_min:x_max] h, w = crop.shape[:2] padded = np.zeros((crop_size, crop_size), dtype=np.float32) padded_mask = np.zeros((crop_size, crop_size), dtype=np.uint8) # 居中padding,不足部分补零 y_off = (crop_size - h) // 2 x_off = (crop_size - w) // 2 padded[y_off:y_off + h, x_off:x_off + w] = crop padded_mask[y_off:y_off + h, x_off:x_off + w] = crop_mask return padded, padded_mask

这个函数的逻辑很直接:np.where(mask > 0)拿到所有前景像素坐标,求出最小外接矩形;外扩margin=16是为了让模型看到结节周围的甲状腺组织,而不只是“贴着边界的局部图”。crop_size=192是显存和分辨率之间的折中,如果结节特别小,可以设 256,但要相应减小 batch size。

补零 padding 不是最优方案,因为超声图像的背景噪声区域和纯黑区域差别很大,模型可能学会“凡是黑色区域都不是结节”。更稳的办法是在 padding 时用图像边缘像素值填充,而不是 0。实现上可以在 crop 外圈先做镜像填充,或者把padded初始化为image[0, 0]这类背景灰度值。

还要注意一个工程细节:训练时不能只从 mask 非零区域裁剪。如果所有训练样本都是以结节为中心的近景,测试时模型面对整张图的上下文会不知所措。我会保留约 15% 的样本不做 ROI 裁剪,直接 resize 成 192×192 输入,让模型同时见过“结节占满画面”和“结节只占一小块”两种分布。

3.3 划分训练/验证/测试集:按病例还是按图

600 张图看起来不少,但如果同一个病人的多张图同时出现在训练集和验证集,验证指标的参考价值会大打折扣。第一步是看目录或元数据里有没有patient_id之类的字段。有的话,一定要按病人 ID 分组划分:同一个病人的所有图只能进同一个集合。否则模型可能不是在学习“什么是结节”,而是在记忆“这位病人的皮肤纹理和背景噪声”,换到新病人身上立刻失效。

如果数据集没有提供病例 ID,只能用文件名随机划分,但我建议心里有数:这种划分下的验证集 Dice 会偏乐观,最终报告里要写清楚“未按病人分组的局限性”。

划分比例我习惯用 8:1:1,也就是约 480 张训练、60 张验证、60 张测试。60 张验证图在分割任务里只够看趋势,单次验证集的 Dice 波动可能达到 0.03-0.05。应对办法是两个:一是每个 epoch 都在验证集上算 Dice,但只保留最高点对应的权重;二是训练结束后用 5 个不同随机种子跑五折交叉验证,把平均 Dice 和标准差写进结论。测试集要完全锁起来,不做任何早停决策,只允许在最终评估时碰一次。

划分还有一个容易被忽略的顺序问题:数据增强必须发生在划分之后。如果先做增强再划分,同一张原图的不同增强版本可能同时进入训练集和验证集,相当于验证集被“污染”了,测试指标会被高估。标准做法是先按文件名写三个 txt,DataLoader 读取时再按集合名选择增强管线。

4. 用U-Net在600张图上训练甲状腺结节分割:最小可跑通流程与关键参数

处理完数据,接下来就是训练。这个章节我给出一套最小可跑通的 U-Net 训练流程,以及每个关键参数的选择依据。照着这个流程走,即使数据只有 600 张,也能在一个小时左右看到明确的分割效果。

4.1 U-Net为什么是超声结节分割的默认选择

在 600 张规模下,U-Net 几乎是第一选择。原因不是它分数最高,而是它的归纳偏置最适合小数据:编码器逐层下采样提取语义,解码器通过跳跃连接恢复细节,参数规模比 Swin-UNet、TransUNet 这类 Transformer 分割模型小一个量级,在几十万像素的超声图上不容易过拟合。

相比自然图像分割里常用的 DeepLabV3,U-Net 在医学边界任务上的优势是它保留了多尺度的细节信息,浅层特征可以直接传到解码器,所以对结节这种边界模糊的目标更友好。结构上我用 4 层编码器,每层两个 Conv3x3 + BN + ReLU,通道数从 32 开始逐层加倍,解码器用双线性上采样而不是转置卷积。后者参数多而且容易在少量数据下产生棋盘格伪影。

如果你的环境里有现成分割库,直接调用基础模块时注意一个最容易错的地方:预训练模型第一个卷积层的in_channels=3,超声图是单通道,要改成 1。三通道图复制三次喂进去也可以,但显存翻倍,收益不大。

4.2 最小PyTorch训练脚本(DiceLoss + AdamW)

训练目标我用 Dice Loss 和 BCE 的组合:Dice Loss + 0.5 * BCE。纯 BCE 在结节前景占比小的情况下会偏向背景,模型学到最后输出一张全黑图,因为全黑也能把 BCE 压到很低;纯 Dice Loss 对极小结节的梯度不稳定,训练容易震荡。两者组合是医学分割里最稳的做法。

import torch import torch.nn.functional as F def dice_loss(pred, target, smooth=1.0): """pred是未经过sigmoid的logits,target取值0/1""" pred = torch.sigmoid(pred) pred = pred.contiguous().view(pred.size(0), -1) target = target.contiguous().view(target.size(0), -1).float() intersection = (pred * target).sum(dim=1) dice = (2.0 * intersection + smooth) / ( pred.sum(dim=1) + target.sum(dim=1) + smooth ) return 1.0 - dice.mean() for epoch in range(epochs): model.train() for images, masks in train_loader: images = images.cuda() masks = masks.cuda().float() optimizer.zero_grad() logits = model(images) loss = dice_loss(logits, masks) + 0.5 * F.binary_cross_entropy_with_logits( logits, masks ) loss.backward() optimizer.step() # epoch结束存一次验证指标,保留最高Dice权重 val_dice = evaluate(model, val_loader) if val_dice > best_dice: best_dice = val_dice torch.save(model.state_dict(), "best_unet.pt")

这段脚本的关键参数有三个。第一,smooth=1.0是平滑系数,作用是避免标签和预测全零时除零;在 600 张数据规模下不要设太大,否则 Dice 的梯度被压平,训练变慢。第二,优化器用 AdamW,初始学习率 3e-4,weight_decay=1e-4。小数据集上 AdamW 的权重衰减能明显抑制过拟合,SGD 反而不好收敛。第三,epochs 设 200,但配 early stopping,patience 30。早停看验证集 Dice,连续 30 个 epoch 不上升就停,不是为了精确,是为了防止最后几轮过拟合导致权重变差。

推理阶段要注意:模型输出的是 logits,不是概率。保存预测图时先torch.sigmoid()再转 numpy,阈值取 0.5。如果验证集上预测边界明显比标签大一圈,阈值可以抬到 0.6-0.7,这也是后处理的一部分。

4.3 从头训练和迁移学习怎么选

600 张属于典型小样本,理论上迁移学习应该有效,但超声图像和 ImageNet 的视觉特征差异很大,预训练权重不一定带来收益。

我一般会同时跑两组对比。第一组是 U-Net encoder 用 ImageNet 预训练的 ResNet34,输入改单通道,第一层卷积权重在通道维取平均。第二组是完全随机初始化,从头训练。两组用完全相同的预处理和数据增强。经验结果是:如果结节边界在图像里比较清晰、标注也一致,从头训练和迁移学习的最终 Dice 差距通常在 0.02 以内;如果数据里有较多低质量图或标注噪声,迁移学习会更稳一些。

迁移学习有一种用法更容易踩坑:把 encoder 冻结,只训练 decoder。这在超声分割上通常效果不好,因为超声图像的低层特征和自然图像差别太大,冻结 encoder 等于让模型用一个“看不懂超声纹理”的编码器。正确做法是全部参数一起微调,学习率可以比从头训练设低一点,比如 1e-4。

5. 甲状腺结节分割数据集训练中的避坑记录:5个值得写进排障手册的问题

这一章写我自己在这类数据集上踩过的坑。每一条都是“现象 → 原因 → 解决”的格式,照着排查比反复调参有效。

5.1 斑点噪声把模型带偏

现象:训练 loss 正常下降,验证集 Dice 也不难看,但把所有预测图叠出来看,结节边界外面散布着大量小碎块,像撒了一把盐。原因:超声斑点噪声在图像上表现为黑白相间的细颗粒,模型把很多噪声点当成了结节边缘。解决:训练预处理加 3×3 中值滤波;预测阶段对概率图做阈值后,用scipy.ndimage.binary_opening去掉面积小于 20 像素的连通域。如果碎块出现在结节内部,改用闭运算把空洞补上。不要为了去噪把滤波核加到 7×7,边界会被磨掉一圈,Dice 一样上不去。

5.2 标签边界不一致导致Dice卡在0.6

现象:训练集 Dice 能到 0.92,验证集始终在 0.6-0.7 之间波动,而且预测的轮廓整体比标签小一圈或者大一圈。原因:不同标注者对“结节边界”的尺度掌握不一样,有的把低回声晕圈算进去,有的只标实性部分,模型只能学一个折中边界。解决:不要在硬标签上硬扛,可以给标签边界做 soft label 平滑。具体做法是:对二值 mask 做距离变换,边界附近 3-5 个像素范围的标签值从 1 渐变到 0,让模型对边界的不确定性有感知。损失函数不用改,但梯度更稳。另一个做法是统一做一次形态学腐蚀或膨胀,让所有标注边界对齐到同一尺度。这样处理后,验证集 Dice 通常能回升到 0.75 以上。

5.3 小数据集过拟合和验证集波动

现象:训练集 Dice 一路涨到 0.95,验证集到了第 80 个 epoch 开始掉头向下;或者验证集 Dice 在两个 epoch 之间从 0.78 跌到 0.65 再弹回。原因:600 张的规模下,模型很容易把训练图的纹理、设备水印、背景固定图案一起记住。解决:把数据增强强度调大,亮度扰动 ±20%、缩放 0.9-1.1、弹性形变 sigma=3;做早停时只看验证集 Dice,不看了训练 loss;最后用五折交叉验证的平均指标代替单次留出集指标。千万不要用“最后一个 epoch 的权重”做最终评估,那是小数据集上最便宜也最贵的教训。

5.4 超声设备差异导致的地域偏差

现象:在自己的留出集上效果不错,换一批来源不同的 B 超图就明显变差,边界出现整体偏移。原因:超声没有标准灰度单位,不同设备、探头频率、增益、深度设置会改变结节显示效果。600 张如果都来自同一台机器,模型的泛化边界就很窄。解决:预处理阶段不要用固定灰度窗位,每张图按百分位归一化;如果知道设备型号,在验证时按设备分组算 Dice,看看是哪一类图在拉后腿。最直接的做法是从新设备补标 30-50 张,用它们做一次微调。这比在模型里加域自适应模块更省事,也更容易落地。

5.5 结节尺寸差异大,单一Dice不敏感

现象:总体 Dice 有 0.80,但按结节长径分组后,长径小于 5mm 的微小结节 Dice 只有 0.2,几乎全漏;大于 20mm 的结节都分割得很好。原因:Dice 是面积比,大结节在分母中权重大,漏掉一个小结节对整体 loss 影响很小,模型优化时自然优先保住容易的大目标。解决:损失函数换成 Tversky Loss 或 Focal Dice,让假阴性得到更高惩罚;评估时按结节面积分组算分层 DICE,不要只看总体一个数;推理时如果原图分辨率足够,用滑窗 patch 预测代替整图 resize。对甲状腺结节随访来说,微小结节的变化往往才是临床关心的重点,这个分层指标比总 Dice 更重要。

6. 最后一步:用效果图和分层指标判断模型是否真的能用

6.1 画一组预测叠加图:肉眼先于指标

指标可以骗人,图像不会。每次训练结束,我会把验证集全部预测图存成三列并排图:原图、标签、预测叠加。用 sigmoid 输出得到概率,再做二值化,代码很短但必须规范:

import matplotlib.pyplot as plt import numpy as np def dump_prediction(image, gt, prob, idx, save_dir): """prob是sigmoid输出,值域0-1""" pred = (prob > 0.5).astype("uint8") fig, axes = plt.subplots(1, 3, figsize=(12, 4)) axes[0].imshow(image, cmap="gray") axes[0].set_title("B-mode") axes[1].imshow(gt, cmap="gray") axes[1].set_title("Ground Truth") axes[2].imshow(image, cmap="gray") axes[2].imshow(pred, cmap="Reds", alpha=0.5) axes[2].set_title("Prediction") fig.savefig(f"{save_dir}/{idx:04d}.png", dpi=150) plt.close(fig)

这段代码没有做任何后处理,就是为了看模型的原始输出。如果看到预测比标签大一圈,优先怀疑标注尺度不一致而不是模型架构;如果看到预测边界整齐但位置偏移,优先怀疑配准和裁剪问题。保存成 150 dpi 的 PNG,放到一个单独的目录里按 epoch 命名,就能直观看到训练过程是怎么演化的。

6.2 按结节尺寸分组的Dice与体积误差表

小数据集的评估不能只给一个平均 Dice。我最终会生成一张按结节尺寸分组的表,格式大概是这样的:

结节尺寸分组样本数Dice(阈值0.5)HD95(mm)体积误差(%)
<5mm120.443.2+18
5-10mm280.721.8-6
10-20mm150.811.2+4
>20mm50.871.0+2

HD95 可以用medpy.metric.binary.hd95计算,体积误差直接用预测 mask 的像素数量除以标签像素数量再减 1。Dice 高只能说明两个区域重叠面积大,边界外扩一圈时 Dice 可能依然好看,但体积误差会暴露问题。对甲状腺结节的随访场景,体积变化是医生做判断的重要参考,所以这个表比总 Dice 更有临床意义。

我的习惯是,任何一个分割模型上线前,总 Dice 和分组表必须放在一起看。如果某个分组样本数太少、标签又边界模糊,我宁可在结论里写“该组不确定”,也不要让一个漂亮的平均 DICE 把问题盖过去。这个小数据集方案做到最后,真正值钱的不是模型权重,而是你对自己数据边界的那份清楚认知。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询