Dice 很高就够了吗?分割评价中的 IoU、表面距离与 HD95

两个分割结果的 Dice 都超过 0.99,它们对后续建模或定位的影响可能很不一样。体积重叠、边界位置、远处孤立组件和细结构完整性,回答的是不同问题。选指标时应先确定任务要避免哪种错误,再解释分数。

**阅读范围:**本文是三维视觉与医学影像软件工程系列的独立技术教程。数据和数值实验均为合成示例,不读取患者数据,不代表实际设备性能或临床精度。文中长度按示例明确的单位解释。资料核对日期:2026 年 10 月 16 日。

图 1:一个高分,不能覆盖所有错误类型(原创教学配图)

一、重叠指标先要求空间对齐

Dice=2|A∩B|/(|A|+|B|),IoU=|A∩B|/|A∪B|。当两个集合非空时,IoU=Dice/(2−Dice)。它们都没有物理长度单位,主要描述集合重叠,而不是边界偏移了多少毫米。

在逐体素比较前,预测与参考必须处于同一物理网格。shape 相同但原点、方向或 spacing 不同,仍不能直接比较。本例输入已经明确处于同一网格;真实影像应先核对空间元数据,必要时采用明确策略重采样。

二、表面距离的定义必须写出来

把预测边界上的点到参考边界的最近距离记为一组,再反向计算另一组,可以构造平均表面距离或分位数。需要说明边界如何定义、是否双向、怎样加权、用哪些物理单位。不同实现的"ASD"或"HD95"可能并不完全相同。

本文用布尔腐蚀提取边界体素,使用考虑 spacing 的欧氏距离变换,合并双向距离后取均值与第 95 百分位。这是不加表面积权重的体素边界约定,不是连续三角面或 surfel 加权指标。跨论文或库比较前应先对齐这些细节。

三、为什么 HD95 也会漏掉小区域?

最大 Hausdorff 距离关注最坏的最近边界距离,对远处噪点敏感。HD95 用分位数削弱极端值影响,但代价是可能忽略占比较小的异常表面。是否应该忽略,取决于任务而不是指标名字。

若大部分表面完全重合,而远处小组件贡献不到合并距离的 5%,则 HD95 可以为零,同时最大 Hausdorff 距离很大。这个现象不违反定义,反而提醒我们不要把"鲁棒分位数"当成"所有局部区域都可靠"。

四、空掩码、多类别与汇总方式

双空掩码的 Dice 有的实现设为 1,有的设为未定义;单空掩码的距离也需要明确约定。本篇直接拒绝空掩码,要求调用方单独制定统计策略,避免悄悄把失败病例从均值里删掉。

多类别任务应区分逐类宏平均与按体素汇总。大类别会掩盖小结构错误;把所有病例合并计算,也可能让大体积病例支配分数。建议保留逐病例、逐类别分布以及失败案例,而不是只报告一个平均数。

五、可运行的完整合成实验

在 64³ 的网格上生成半径 12 体素的球体。方案一沿 x 平移 2 体素;方案二保留球体,在远处加入 3³ 的小立方组件。spacing 按 z,y,x 输入,本例均为 1 mm。代码同时检查相同掩码应得到完美指标,并验证 Dice--IoU 关系。

环境说明:代码在本机 Python 3.14 环境中实际执行。为便于复现,请保存为 demo.py,在支持这些库的独立环境中安装依赖;若目标解释器没有兼容 wheel,应先选择受支持的解释器。环境差异可能影响最后几位数值,不影响本文所检查的几何或统计关系。

复制代码
python -m pip install numpy scipy
python demo.py

import numpy as np
from scipy.ndimage import binary_erosion,distance_transform_edt

def metrics(a,b,spacing=(1.,1.,1.)):
    a,b=np.asarray(a,bool),np.asarray(b,bool)
    s=np.asarray(spacing,float)
    if a.shape!=b.shape or a.ndim!=3 or s.shape!=(3,) or not np.isfinite(s).all() or (s<=0).any():
        raise ValueError('Same 3D grid and positive spacing required')
    if not a.any() or not b.any():raise ValueError('Empty mask: define separate evaluation policy')
    intersection=np.count_nonzero(a&b)
    dice=2*intersection/(a.sum()+b.sum())
    iou=intersection/np.count_nonzero(a|b)
    sa=a^binary_erosion(a);sb=b^binary_erosion(b)
    da=distance_transform_edt(~sb,sampling=s)[sa]
    db=distance_transform_edt(~sa,sampling=s)[sb]
    # This demo uses an unweighted pooled voxel-surface convention.
    distances=np.r_[da,db]
    return dice,iou,distances.mean(),np.percentile(distances,95),distances.max()

z,y,x=np.indices((64,64,64));a=(x-30)**2+(y-30)**2+(z-30)**2<=12**2
b=np.roll(a,2,axis=2)
c=a.copy();c[55:58,55:58,55:58]=True
same=metrics(a,a)
assert np.allclose(same,[1,1,0,0,0])
for name,mask in [('shift_2_voxels',b),('tiny_remote_component',c)]:
    v=metrics(a,mask)
    assert np.isclose(v[1],v[0]/(2-v[0]))
    print(name,'Dice IoU pooled_ASD HD95 HD',v)
try:metrics(a,np.zeros_like(a))
except ValueError:pass
else:raise AssertionError('Empty mask policy bypassed')
print('PASS: identity, Dice-IoU relation and empty-mask rejection')

六、运行结果与应该怎样解读

平移方案 Dice 约 0.87725,HD95=2 mm,最大 HD=2 mm。远处小组件方案 Dice 约 0.99812,按本文约定 HD95=0,但最大 HD 约 35.22783 mm。说明体积高分与分位数高分都可能漏掉少量远处异常。

图 2:实际实验掩码的最大投影(原创教学配图)

代码中的断言验证了本篇明确描述的性质。配图中标为原理示意的部分用于解释模型,不是实测截图;由公式生成的曲线只在图注与正文列出的假设下成立。不要把合成实验输出当成真实产品的性能报告。

七、常见现象与排查方向

现象 优先检查 处理方向
Dice 极高但局部严重偏移 大体积结构掩盖小区域 增加表面、连通分量与关键结构检查
两个库 HD95 不一样 双向、分位数或表面加权不同 用同一小型案例对齐定义
误差单位不清楚 距离变换未传 spacing 按数组轴顺序传入物理间距
均值漂亮但漏检病例不见了 空掩码被忽略 预先约定失败与空值统计策略

八、独立验证与适用边界

对任务相关的小结构、狭窄连接、远处组件和边界误差分别设计验证案例。参考分割本身也有观察者差异,不能把它不加讨论地视为绝对真值。本文没有实现表面积加权、病例汇总或临床有效性验证;指标反例只证明特定定义的边界,不代表任何模型总体性能。

建议把原始输入、参数、软件版本、输出结果和失败状态一起保留。若把同一份检查数据反复用于选择参数,它就不再是完全独立的最终验证集。将算法演示接入真实系统时,应另行验证输入质量、参考不确定性和完整处理链路。

参考资料

SciPy:距离变换及 sampling 参数

SciPy:binary_erosion

上述资料用于核对相关接口与基本约定;本文的合成数据、推导示例与代码不声称复刻库内全部实现。

写在最后

"知之为知之,不知为不知,是知也。"

------ 孔子,《论语·为政》

一个分数回答一个问题;把问题问清楚,评价才有价值。

相关推荐
曲径海盗1 小时前
【Python 学习第 31 天】复习:变量与数据类型 等 3 题
python·学习笔记·编程入门
Data_Journal1 小时前
如何绕过 Cloudflare:网页抓取的最佳方法
网络·python
爱笑的源码基地2 小时前
云 PACS 源码实践:HL7 对接 HIS / 电子病历
源码·开发·信创·pacs·医学影像
我命由我123452 小时前
智能助手通用问答边界
人工智能·python·学习·ai·学习方法·ai编程·智能体
Angeldream1233 小时前
第 4 章 医学影像预处理
图像处理·人工智能·python·医学影像·医学影像处理
Web3&Basketball3 小时前
实测Gemini4Argon:ERQA 87%登顶长程任务榜
数据库·python·大模型·多模态·gemini·长上下文·推理优化
把我格子衫拿来3 小时前
医学影像重采样:spacing 改了,为什么模型位置也跟着错了?
python·dicom·医学影像·simpleitk·重采样
打工仔折腾 AI3 小时前
用 Dashlet 把极空间 NAS 服务入口整理成统一导航首页的实践
java·开发语言·人工智能·后端·python·rpc·ai agent 实战
wuyk5553 小时前
PyQt6-第05讲-QtDesigner工作流与ui文件
python·ui·pyqt