第 7 章 医学影像特征提取与分析

第 7 章 医学影像特征提取与分析

本章导读

分割回答了"病灶在哪里",但临床医生更关心的是"它有多大、是什么形状、内部纹理如何、恶性可能性多高"。本章完成从"图像"到"数字"的跨越:形态学分析(7.1)把分割掩码转化为体积、形状等几何量;纹理与影像组学分析(7.2)从像素灰度的空间排列中挖掘人眼无法感知的定量信息;计算机辅助诊断(7.3)则把这些特征组织成完整的决策系统。

本章是连接核心技术篇与应用实践篇的枢纽:前面章节的分割结果是本章的输入,本章提取的特征又将直接服务于第 9 章的项目实战与第 12 章的影像组学临床应用。特别地,影像组学(Radiomics) 已成为近十年医学影像研究增长最快的方向之一------它的核心命题是"医学图像不只是用来看的,更是用来算的"。


本章学习目标

  • 能从二值掩模计算面积、周长、质心、等效直径等形态学特征;

  • 理解形状描述子(如圆形度、长宽比、傅里叶描述子)在良恶性鉴别中的作用;

  • 理解 Marching Cubes 算法从体素数据重建三维表面的原理;

  • 掌握放射组学的基本流程:分割 → 特征提取 → 特征筛选 → 建模 → 临床关联;

  • 理解 GLCM、LBP 两类纹理特征的原理与计算方式;

  • 了解 CAD 系统的两类范式:基于规则与基于分类模型;

  • 知道特征降维(PCA、LASSO)在放射组学建模中的必要性。


7.1 形态学分析

第 5 章分割的输出通常是一个二值掩模:1 表示目标、0 表示背景。形态学分析就是从这个分割掩码中提取几何特征。这些特征看似简单,却是临床报告的直接组成部分:肿瘤体积决定 T 分期,结节倍增时间决定随访策略,心室容积与射血分数决定心功能分级。

7.1.1 区域特征提取

对二维区域 R R R,最基础的特征包括:

1. 面积与周长

面积 = ∣ R ∣ ⋅ s x ⋅ s y ( 像素数 × 物理间距 ) \text{面积} = |R| \cdot s_x \cdot s_y \quad (\text{像素数} \times \text{物理间距}) 面积=∣R∣⋅sx⋅sy(像素数×物理间距)

周长的计算必须小心:直接数边界像素会系统性高估(锯齿状边界比对角线长),工程上常用 Freeman 链码加权或 Crofton 公式校正。

2. 质心与方向

质心(零阶矩归一化的一阶矩):

x ˉ = 1 ∣ R ∣ ∑ ( x , y ) ∈ R x , y ˉ = 1 ∣ R ∣ ∑ ( x , y ) ∈ R y \bar{x} = \frac{1}{|R|}\sum_{(x,y) \in R} x, \qquad \bar{y} = \frac{1}{|R|}\sum_{(x,y) \in R} y xˉ=∣R∣1(x,y)∈R∑x,yˉ=∣R∣1(x,y)∈R∑y

二阶中心矩构成协方差矩阵,其特征向量给出区域的主轴方向(呼应 3.1 节的特征值分解),特征值之比刻画区域的"细长程度":

μ p q = ∑ ( x , y ) ∈ R ( x − x ˉ ) p ( y − y ˉ ) q \mu_{pq} = \sum_{(x,y) \in R} (x - \bar{x})^p (y - \bar{y})^q μpq=(x,y)∈R∑(x−xˉ)p(y−yˉ)q

3. 形状描述子
描述子 公式 临床含义示例
圆形度(Circularity) 4 π ⋅ 面积 周长 2 \frac{4\pi \cdot \text{面积}}{\text{周长}^2} 周长24π⋅面积 良性结节圆度高,恶性常有毛刺
紧致度(Compactness) 周长 2 面积 \frac{\text{周长}^2}{\text{面积}} 面积周长2 浸润性生长使紧致度升高
长宽比 主轴长度 / 次轴长度 肿大淋巴结由长圆形变圆
实心度(Solidity) 面积 / 凸包面积 凹陷程度(如分叶征)
离心率(Eccentricity) 椭圆拟合的离心率 腺体导管的形态评估
分叶度 边界曲率的符号变化次数 肿瘤边界规则性
python 复制代码
"""
基于 scikit-image计算形态学特征
"""
import numpy as np
from skimage import measure, morphology

# 假设 tumor_mask 是分割得到的二值掩码(第 5 章的输出)
tumor_mask = np.zeros((256, 256), dtype=bool)
yy, xx = np.ogrid[:256, :256]
tumor_mask[(yy - 128)**2 / 40**2 + (xx - 120)**2 / 60**2 <= 1] = True  # 椭圆病灶

# 连通域标记 + 区域属性提取
labeled = measure.label(tumor_mask)
regions = measure.regionprops(labeled, intensity_image=None)

for r in regions:
    print(f"面积:       {r.area} 像素")
    print(f"质心:       {r.centroid}")
    print(f"周长:       {r.perimeter:.1f} 像素")
    print(f"圆形度:     {4*np.pi*r.area / r.perimeter**2:.3f}")
    print(f"长宽比:     {r.major_axis_length / r.minor_axis_length:.2f}")
    print(f"离心率:     {r.eccentricity:.3f}")
    print(f"实心度:     {r.solidity:.3f}")
    print(f"主轴方向:   {np.degrees(r.orientation):.1f}°")
4. Hu 不变矩

Hu 矩(7 个由二阶/三阶中心矩组合的量)具有平移、旋转、缩放不变性,适合病灶形态的模板匹配与检索(如"在病例库中查找形态相似的结节")。但高阶矩对噪声敏感,实际临床量化中低阶形状描述子更常用。

7.1.2 3D 体素分析

三维形态分析的原理与二维一致,但有两个必须注意的工程细节:

1. 体积计算要乘体素物理体积
python 复制代码
"""
读取影像,并计算肿瘤体积。
"""
import SimpleITK as sitk
import numpy as np

mask_img = sitk.ReadImage('tumor_mask.nii.gz')
mask = sitk.GetArrayFromImage(mask_img)          # (Z, Y, X)
spacing = mask_img.GetSpacing()                  # (sx, sy, sz),单位 mm

voxel_volume = spacing[0] * spacing[1] * spacing[2]   # mm³
tumor_volume_mm3 = mask.sum() * voxel_volume
tumor_volume_ml = tumor_volume_mm3 / 1000

print(f"肿瘤体积: {tumor_volume_ml:.2f} mL")

医学影像小贴士:体积测量是疗效评估的硬指标

RECIST 标准用单径测量,但越来越多证据表明体积测量对疗效变化的敏感性是单径的 3--5 倍(球形病灶直径增大 26% 对应体积翻倍)。肿瘤负荷、脑萎缩速率、心室容积等关键指标全部依赖准确的 3D 体积分析------前提是各向同性重采样(第 4 章)与准确分割(第 5 章)都已正确完成。

2. 表面积需要表面重建

直接数边界体素会严重高估表面积(阶梯效应),正确做法是先用 Marching Cubes 重建光滑三角网格,再求网格面积。

7.1.3 Marching Cubes 表面重建

Marching Cubes(Lorensen & Cline, 1987)是把体数据转换为三角网格的经典算法,也是 3D 可视化、3D 打印、手术规划的基础。

图 7-1 Marching Cubes:从体数据到三角网格

Marching Cubes 表面重建算法核心思想(以 2D 版本 Marching Squares 说明):

  1. 逐个遍历 2 × 2 × 2 2\times2\times2 2×2×2 的体素立方体(cube)
  2. 立方体 8 个顶点各有"在等值面内/外"两种状态 → 共 2 8 = 256 2^8 = 256 28=256 种组合
  3. 查表确定等值面穿过该立方体的三角形构型(利用对称性可归并为 15 种基本情形)
  4. 棱边上的交点用线性插值精确定位
  5. 拼接所有立方体的三角形 → 完整表面网格
python 复制代码
"""
表面重建
"""
from skimage import measure
import numpy as np

# 从体数据提取等值面(例如 CT 中骨骼的 HU 阈值 300)
volume = ...                                  # 3D numpy array (Z, Y, X)
verts, faces, normals, values = measure.marching_cubes(
    volume,
    level=300,                                # 等值面阈值(HU)
    spacing=(2.0, 0.5, 0.5),                  # 体素间距 → 输出物理坐标 mm
    step_size=1,                              # 采样步长(>1 可加速但损失精度)
)

print(f"顶点数: {len(verts)}, 三角面片数: {len(faces)}")

# 网格表面积(物理单位 mm²)
mesh_surface_area = measure.mesh_surface_area(verts, faces)
print(f"表面积: {mesh_surface_area:.1f} mm²")

# 导出 STL 用于 3D 打印 / 手术规划软件
import struct
def save_stl(verts, faces, path):
    with open(path, 'wb') as f:
        f.write(b'\0' * 80)                   # STL 头部
        f.write(struct.pack('<I', len(faces)))
        for face in faces:
            v0, v1, v2 = verts[face]
            normal = np.cross(v1 - v0, v2 - v0)
            norm = np.linalg.norm(normal)
            normal = normal / norm if norm > 0 else normal
            f.write(struct.pack('<12fH', *normal, *v0, *v1, *v2, 0))

# save_stl(verts, faces, 'bone_model.stl')

应用链:CT 分割(第 5 章)→ Marching Cubes 重建 → 3D 打印手术导板 / 虚拟现实术前演练。骨科、颌面外科已常规使用这一流程。

注意:平滑是重建前的重要步骤

分割掩码直接做 Marching Cubes 会得到"阶梯状"表面。标准做法是先对掩码做高斯平滑 ( σ ≈ 0.5 \sigma \approx 0.5 σ≈0.5-- 1.0 1.0 1.0 体素)再提取等值面,或重建后用 Taubin/Laplacian 平滑网格。但平滑会轻微缩小体积------定量测量时应以体素计数为准,网格仅用于可视化与几何分析。


7.2 功能与纹理分析

形态学特征描述"外形",而组织内部的灰度空间排列------纹理------包含肉眼不可见的微观结构信息。这是影像组学的核心洞察。

7.2.1 放射组学(Radiomics)

1. 影像组学的定义与流程

影像组学:从医学影像中高通量提取大量定量特征,通过数据挖掘建立与临床结局(病理分级、基因表型、预后、治疗响应)的关联。

图 7-2 影像组学标准五步流程

2. IBSI 标准与特征类别

IBSI(Image Biomarker Standardisation Initiative) 是影像组学特征的国际计算标准,统一了不同软件的特征定义------此前同名特征(如"熵")在不同软件中计算结果可相差数倍,严重阻碍多中心研究。

放射组学有几个关键工程细节必须强调:

  • 可重复性:不同扫描仪、不同重建核、不同窗宽窗位下,同一肿瘤的纹理特征可能漂移。因此临床级放射组学必须做多中心数据归一化(第 4 章)、特征 ICC(组内相关系数)筛选 ------ 只有跨采集条件仍稳定的特征才保留。
  • 特征标准化:GLCM、LBP 这些特征的量级差异巨大(面积可能几百、纹理熵可能 5),建模前必须做 Z-score 或 Min-Max 归一化(4.2.1)。
  • 小样本陷阱 :上百维特征、几十例病人是放射组学论文的常态,极易过拟合。LASSO 正则化(3.2.2 的 L1 稀疏)、交叉验证、独立外部验证是必备防线。Python 里常用 PyRadiomics 库一键提取上述全套特征,是事实标准工具。

标准特征分为四族:

特征族 数量级 代表特征 刻画的信息
一阶统计(First-order) ~18 均值、方差、偏度、峰度、熵 灰度分布(不考虑空间)
形状(Shape) ~16 体积、表面积、球形度、最大径 几何形态
纹理(Texture) ~75 GLCM、GLRLM、GLSZM、NGTDM、GLDM 灰度空间排列
滤波后特征(Filtered) 数百 小波/LoG 变换后的上述特征 多频率成分的细节

一个 ROI 通常提取 100--1500 个特征------"高通量"由此而来。

3. PyRadiomics 实战
python 复制代码
"""
PyRadiomics库
"""
import SimpleITK as sitk
from radiomics import featureextractor

# 1. 配置提取器
settings = {
    'binWidth': 25,                    # 灰度离散化 bin 宽度(IBSI 推荐固定 binWidth)
    'resampledPixelSpacing': [1, 1, 1],# 重采样到各向同性(第 4 章!)
    'interpolator': 'sitkBSpline',
    'normalize': False,                # CT 有 HU 物理意义,不 normalize
}
extractor = featureextractor.RadiomicsFeatureExtractor(**settings)
extractor.enableAllFeatures()

# 2. 提取(image 与 mask 必须空间对齐------第 6 章配准的意义)
image = sitk.ReadImage('lung_ct.nii.gz')
mask  = sitk.ReadImage('lung_nodule_mask.nii.gz')

features = extractor.execute(image, mask, label=1)

# 3. 整理输出
for name, value in features.items():
    if not name.startswith('diagnostics'):   # diagnostics 是元信息
        print(f"{name}: {value}")

医学影像小贴士:影像组学的三大工程陷阱

  1. 离散化(discretization):纹理矩阵必须先做灰度量化,binWidth 的选择直接影响纹理特征值------CT 推荐固定 binWidth=25 HU,MRI 因无标准单位需先归一化再用固定 bin 数
  2. 重采样:体素间距不同会改变纹理的空间尺度,必须先重采样到统一 spacing
  3. 特征可重复性:同一病灶两次勾画的特征差异(观察者内/间变异)应通过 ICC(组内相关系数)筛选,只保留 ICC > 0.8 的稳健特征进入建模
4. 特征选择与建模

上千个特征对几十到几百个样本------典型的"小 n 大 p"问题,直接建模必然过拟合。标准应对:

  1. 可重复性筛选:ICC > 0.8
  2. 单变量筛选:Mann-Whitney U 检验 / 相关性初筛
  3. 降维:PCA 或相关性聚类(去除 |r| > 0.9 的冗余特征)
  4. 嵌入式选择:LASSO(L1 正则逻辑回归------第 3 章的稀疏先验再次登场!)
  5. 建模 :逻辑回归 / SVM / 随机森林 + 嵌套交叉验证
python 复制代码
"""
特征选择
"""
from sklearn.linear_model import LassoCV, LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import StandardScaler
import numpy as np

# X: (n_patients, n_features) 影像组学特征矩阵
# y: (n_patients,) 临床结局(如 0=良性, 1=恶性)

X_scaled = StandardScaler().fit_transform(X)

# LASSO 特征选择(L1 正则 → 稀疏解,3.1 节的几何直觉在此兑现)
lasso = LassoCV(cv=5, random_state=42, max_iter=5000)
lasso.fit(X_scaled, y)
selected = np.where(np.abs(lasso.coef_) > 1e-5)[0]
print(f"LASSO 从 {X.shape[1]} 个特征中选出 {len(selected)} 个")

# 用选出的特征建模
clf = LogisticRegression(max_iter=1000)
scores = cross_val_score(clf, X_scaled[:, selected], y,
                         cv=5, scoring='roc_auc')
print(f"5 折交叉验证 AUC: {scores.mean():.3f} ± {scores.std():.3f}")

7.2.2 纹理分析:GLCM 与 LBP

图 7-3 GLCM 灰度共生矩阵计算示意

1. GLCM(灰度共生矩阵)

GLCM 统计相距固定方向和距离的像素对的灰度联合分布:

P ( i , j ∣ d , θ ) = # { ( p , q ) : I ( p ) = i ,    I ( q ) = j ,    q = p + ( d cos ⁡ θ , d sin ⁡ θ ) } P(i, j | d, \theta) = \#\{(p, q) : I(p) = i, \; I(q) = j, \; q = p + (d\cos\theta, d\sin\theta)\} P(i,j∣d,θ)=#{(p,q):I(p)=i,I(q)=j,q=p+(dcosθ,dsinθ)}

从矩阵中提取的 Haralick 特征:

特征 公式 物理含义 肿瘤应用
对比度(Contrast) ∑ i , j ( i − j ) 2 P ( i , j ) \sum_{i,j} (i-j)^2 P(i,j) ∑i,j(i−j)2P(i,j) 局部灰度变化 恶性肿瘤异质性高 → 对比度高
能量(Energy/ASM) ∑ i , j P ( i , j ) 2 \sum_{i,j} P(i,j)^2 ∑i,jP(i,j)2 纹理规则性 良性组织纹理均匀 → 能量高
同质性(Homogeneity) ∑ i , j P ( i , j ) 1 + ∣ i − j ∣ \sum_{i,j} \frac{P(i,j)}{1+|i-j|} ∑i,j1+∣i−j∣P(i,j) 灰度相近程度 坏死区破坏同质性
相关性(Correlation) ∑ i , j ( i − μ i ) ( j − μ j ) P ( i , j ) σ i σ j \sum_{i,j} \frac{(i-\mu_i)(j-\mu_j)P(i,j)}{\sigma_i\sigma_j} ∑i,jσiσj(i−μi)(j−μj)P(i,j) 灰度线性依赖 结构方向性
熵(Entropy) − ∑ i , j P ( i , j ) log ⁡ P ( i , j ) -\sum_{i,j} P(i,j)\log P(i,j) −∑i,jP(i,j)logP(i,j) 随机性/复杂度 肿瘤异质性的经典度量
python 复制代码
"""
提取GLCM
"""
from skimage.feature import graycomatrix, graycoprops
import numpy as np

# ROI 灰度量化(纹理矩阵的前提,呼应上面的工程陷阱)
roi = ...                                     # 病灶 ROI,uint8
roi_q = (roi / roi.max() * 63).astype(np.uint8)   # 量化为 64 级

# 4 个方向、距离 1 的共生矩阵
glcm = graycomatrix(roi_q, distances=[1],
                    angles=[0, np.pi/4, np.pi/2, 3*np.pi/4],
                    levels=64, symmetric=True, normed=True)

for prop in ['contrast', 'energy', 'homogeneity', 'correlation']:
    vals = graycoprops(glcm, prop)
    print(f"{prop:12s}: {vals.mean():.3f} (4 方向均值)")

肿瘤异质性与预后 :大量研究表明,高熵/高对比度(高异质性)的肿瘤在肺癌、胶质瘤、头颈癌中与更差的预后显著相关------影像组学把"异质性"从医生的主观印象变成了可统计的定量指标。

2. LBP(局部二值模式)

LBP 把每个像素与 8 邻域比较,生成 8 位二进制编码:

L B P P , R ( x c ) = ∑ p = 0 P − 1 s ( g p − g c ) ⋅ 2 p , s ( x ) = { 1 x ≥ 0 0 x < 0 LBP_{P,R}(x_c) = \sum_{p=0}^{P-1} s(g_p - g_c) \cdot 2^p, \quad s(x) = \begin{cases} 1 & x \ge 0 \\ 0 & x < 0 \end{cases} LBPP,R(xc)=p=0∑P−1s(gp−gc)⋅2p,s(x)={10x≥0x<0

整幅图像的 LBP 直方图构成 256 维纹理特征。旋转不变 LBP 与 uniform LBP(把含至多两次 0/1 跳变的模式归为一类)是常用变体。

python 复制代码
"""
scikit-image 一行调用LBP
"""
from skimage.feature import local_binary_pattern

radius, n_points = 3, 24
lbp = local_binary_pattern(roi, n_points, radius, method='uniform')

# uniform LBP 直方图特征
n_bins = n_points + 2
hist, _ = np.histogram(lbp.ravel(), bins=n_bins, range=(0, n_bins),
                       density=True)
print(f"LBP 特征维度: {len(hist)}")

GLCM vs LBP 的选择:

维度 GLCM LBP
捕捉的信息 像素对的联合分布(二阶统计) 局部微结构模式
对单调灰度变换 敏感 不变(只比较大小关系)
计算量 中 小
医学典型场景 肿瘤异质性定量 肝纤维化分级、乳腺钼靶纹理

LBP 的单调不变性在 MRI 上特别有价值 :MRI 无标准灰度单位,不同设备灰度绝对值不同,但组织间的相对明暗关系是稳定的------LBP 恰好只编码这种相对关系,因此跨设备鲁棒性优于 GLCM。


7.3 计算机辅助诊断(CAD)

CAD 是把前面所有技术组装成临床可用决策系统的工程。临床上分为两类:

  • CADe(Computer-Aided Detection,检测):标记可疑位置,"别漏掉"
  • CADx(Computer-Aided Diagnosis,诊断):判断良恶性/分级,"帮我判断"

7.3.1 基于规则的诊断系统

最早的 CAD 是专家规则的程序化------把放射科医生的读片经验写成显式条件:

复制代码
肺结节 CADe 规则链(简化示例):
1. 肺实质分割(HU 阈值 -500,第 5 章)
2. 候选检测:肺野内球形度 > 0.6 且 3mm < 直径 < 30mm 的连通域
3. 假阳性剔除:
   - 若候选与胸膜距离 < 2mm 且扁平 → 胸膜结节,剔除
   - 若候选呈管状连通(血管追踪) → 血管断面,剔除
   - 若 HU 均值 > 200 → 钙化灶,降级提示
4. 按恶性风险特征评分:毛刺 +2、分叶 +1、胸膜牵拉 +1

规则系统的价值与边界:

优势 局限
完全可解释,医生信任 规则覆盖面有限,泛化差
无需训练数据 阈值对设备/协议敏感
失效模式可预测 复杂模式(磨玻璃结节)难以规则化
满足早期监管要求 维护成本高

规则系统至今仍是 CAD 产品中后处理与假阳性剔除的常见组件------深度学习给出候选,规则做临床逻辑过滤,二者互补。

7.3.2 基于机器学习的 CADx

传统 ML 的 CADx = 影像组学特征 + 分类器(正是 7.2 节的组合):

python 复制代码
"""
经典 CADx 流水线:分割 → 特征 → 分类
"""
def cad_pipeline(ct_image, nodule_mask):
    # 1. 形态学特征(7.1)
    region = measure.regionprops(measure.label(nodule_mask))[0]
    morph_feats = [region.area, 4*np.pi*region.area/region.perimeter**2,
                   region.eccentricity, region.solidity]

    # 2. 纹理特征(7.2)
    roi = ct_image[nodule_mask]
    roi_q = (np.clip(roi, -1000, 400) + 1000) // 22  # 64 级量化
    glcm = graycomatrix(roi_q.reshape(-1, 1), ...)   # 示意
    texture_feats = [...]

    # 3. 分类
    X = np.array(morph_feats + texture_feats).reshape(1, -1)
    malignancy_prob = trained_classifier.predict_proba(X)[0, 1]
    return malignancy_prob

7.3.3 基于深度学习的疾病检测:肺炎 CT 筛查

深度学习 CAD 跳过手工特征,端到端学习"图像 → 诊断"。以胸部 CT 肺炎筛查为例(第 9 章将展开为完整项目):

1. 任务设计的关键决策
决策点 选项 肺炎筛查的选择
输入粒度 2D 切片 / 2.5D / 3D 2.5D(相邻 3 层作通道)
输出形式 分类 / 分割 / 检测 切片分类 + Grad-CAM 定位
标签来源 影像报告 NLP / 人工标注 报告结构化提取 + 抽检
网络 ResNet / DenseNet / EfficientNet DenseNet-121(特征复用,小数据友好)
2. MONAI 分类网络实现
python 复制代码
"""
MONAI 分类网络实现
"""
import torch
from monai.networks.nets import DenseNet121
from monai.transforms import (
    Compose, LoadImaged, EnsureChannelFirstd, ScaleIntensityd,
    Resized, RandFlipd, RandRotated, ToTensord
)

# 数据流水线(第 4 章知识的应用)
train_transforms = Compose([
    LoadImaged(keys=['image']),
    EnsureChannelFirstd(keys=['image']),
    ScaleIntensityd(keys=['image']),
    Resized(keys=['image'], spatial_size=(224, 224)),
    RandFlipd(keys=['image'], prob=0.5, spatial_axis=0),
    RandRotated(keys=['image'], prob=0.3, range_x=0.1),
    ToTensord(keys=['image', 'label']),
])

# 模型:3 通道输入(2.5D:相邻 3 层切片)
model = DenseNet121(spatial_dims=2, in_channels=3,
                    out_channels=2, dropout_prob=0.3)

# 类别不平衡处理(第 5 章损失函数思想)
class_weights = torch.tensor([1.0, 3.0])   # 肺炎样本少,加权
criterion = torch.nn.CrossEntropyLoss(weight=class_weights)
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4,
                              weight_decay=1e-5)   # 3.2 节的优化器
3. 可解释性:Grad-CAM 定位依据

临床 CAD 必须回答"模型为什么这样判断"。Grad-CAM 用最后一层卷积特征图的梯度加权生成热图:

w k = 1 Z ∑ i , j ∂ y c ∂ A i j k , L G r a d C A M = R e L U ( ∑ k w k A k ) w_k = \frac{1}{Z}\sum_{i,j} \frac{\partial y^c}{\partial A_{ij}^k}, \qquad L_{GradCAM} = \mathrm{ReLU}\Big(\sum_k w_k A^k\Big) wk=Z1i,j∑∂Aijk∂yc,LGradCAM=ReLU(k∑wkAk)

python 复制代码
"""
Grad-CAM 热图
"""
from pytorch_grad_cam import GradCAM
from pytorch_grad_cam.utils.image import show_cam_on_image

target_layers = [model.features.denseblock4]   # 最后一个特征块
cam = GradCAM(model=model, target_layers=target_layers)

grayscale_cam = cam(input_tensor=img_tensor, targets=None)[0]
# 叠加显示:热图应落在肺野炎症区域------否则模型可能在"看图边框猜答案"
visualization = show_cam_on_image(img_display, grayscale_cam, use_rgb=True)

重要:CAD 的可解释性不是可选项

曾有多篇论文的"肺炎检测模型"被发现实际学习的是图像角落的医院标记、扫描体位差异等捷径特征(shortcut learning)。Grad-CAM 热图检查 + 外部数据集验证是排除此类问题的最低要求。这也是第 10 章伦理与安全将深入讨论的主题。

7.3.4 CAD 系统的性能评估

分类模型的评估与分割(5.4 节)有完全不同的指标体系:

图 7-4 ROC 曲线与临床工作点选择

1. ROC 曲线与 AUC

ROC 曲线 :以假阳性率(1 − 特异性)为横轴、敏感性为纵轴,扫描分类阈值得到的曲线。AUC(曲线下面积)= 随机正样本排在随机负样本之前的概率。

  • AUC = 0.5:无判别力;AUC > 0.9:优秀
  • 临床阈值的选择:筛查任务选高敏感性工作点(宁可误报);确诊任务选高特异性工作点
python 复制代码
"""
绘制ROC曲线
"""
from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt

fpr, tpr, thresholds = roc_curve(y_true, y_score)
roc_auc = auc(fpr, tpr)

# 找 Youden 指数最大的工作点
youden = tpr - fpr
best_idx = np.argmax(youden)
print(f"最佳阈值: {thresholds[best_idx]:.3f}, "
      f"敏感性: {tpr[best_idx]:.3f}, 特异性: {1-fpr[best_idx]:.3f}")

plt.plot(fpr, tpr, label=f'AUC = {roc_auc:.3f}')
plt.plot([0, 1], [0, 1], 'k--', label='随机猜测')
plt.xlabel('1 - 特异性 (FPR)'); plt.ylabel('敏感性 (TPR)')
plt.legend(); plt.title('肺炎筛查 ROC 曲线'); plt.show()
2. FROC:检测任务专用

CADe 系统评估用 FROC(Free-Response ROC) :横轴是每例图像的平均假阳性数,纵轴是敏感性。临床可用性的经验标准是"每图 ≤ 1 个假阳性时敏感性 > 90%"。

3. 置信区间与统计比较

医学 AI 论文的规范:报告 95% 置信区间 (bootstrap 1000 次重采样),模型间比较用 DeLong 检验(比较两条 ROC 的 AUC 差异是否显著)。

python 复制代码
"""
DeLong 检验
"""
def bootstrap_auc(y_true, y_score, n_boot=1000, seed=42):
    rng = np.random.RandomState(seed)
    aucs = []
    n = len(y_true)
    for _ in range(n_boot):
        idx = rng.randint(0, n, n)
        if len(np.unique(y_true[idx])) < 2:
            continue
        fpr_b, tpr_b, _ = roc_curve(y_true[idx], y_score[idx])
        aucs.append(auc(fpr_b, tpr_b))
    return np.percentile(aucs, [2.5, 50, 97.5])

ci_low, median, ci_high = bootstrap_auc(y_true, y_score)
print(f"AUC = {median:.3f} (95% CI: {ci_low:.3f}--{ci_high:.3f})")

医学影像小贴士:CAD 评估的临床转化视角

一个 AUC 0.95 的模型未必有临床价值。评估 CAD 必须回答:

  1. 工作点:在哪个阈值运行?该阈值下的敏感性/特异性/PPV 是多少?
  2. 疾病流行率:PPV 强烈依赖患病率------筛查人群(患病率 1%)中,即使特异性 99%,阳性预测值也只有约 50%
  3. 增量价值 :模型辅助下医生的 AUC 是否显著高于医生单独诊断?(reader study)
  4. 工作流影响:节省了多少读片时间?增加了多少不必要召回?

本章小结

本章完成了从"分割结果"到"临床决策"的三级跨越:

  1. 形态学分析

    • 2D 区域特征(面积、周长、圆形度、离心率)与 3D 体素分析(体积必须乘体素物理体积)
    • Marching Cubes 表面重建:256 种构型查表 + 棱边线性插值,输出三角网格用于 3D 打印与表面积测量
    • 工程要点:重建前高斯平滑消除阶梯效应,但定量测量以体素计数为准
  2. 功能与纹理分析

    • 影像组学五步法:采集 → 分割 → 提取 → 选择 → 建模,IBSI 标准统一特征定义
    • GLCM 捕捉二阶灰度统计(熵 = 肿瘤异质性的定量度量);LBP 编码局部微结构且单调不变(MRI 跨设备友好)
    • 小 n 大 p 问题的标准应对:ICC 筛选 → LASSO(第 3 章 L1 稀疏先验的直接应用)→ 嵌套交叉验证
  3. 计算机辅助诊断

    • CADe/CADx 分工;规则系统的可解释性价值与深度学习互补
    • 深度学习 CAD 的四要素:任务设计、MONAI 分类网络、Grad-CAM 可解释性、shortcut learning 防范
    • 评估体系:ROC/AUC + bootstrap CI + FROC + DeLong 检验,以及临床转化的四问(工作点、流行率、增量价值、工作流)

提示

需要警惕的是贯穿全章的一条暗线:特征数值的可信度取决于前置环节------ 分割不准、归一化不做、采集条件不统一,特征再花哨也是噪声。

至此,第二篇核心技术篇(预处理、分割、重建与配准、特征分析)全部完成。读者已具备完整的技术栈------第三篇将把这些技术组装成各模态的完整实战项目。

思考题

  1. 为什么直接用边界像素数估计周长会系统性高估?Marching Cubes 的线性插值交点定位解决了什么类似问题?
  2. Marching Cubes 算法的 "逐立方体查表" 思想是什么?它为什么能把体素数据变成连续三角表面?
  3. 某研究团队用 GLCM 特征在中心 A 的 MRI 数据上训练了胶质瘤分级模型,在中心 B 上性能大幅下降。请从 MRI 的灰度特性、LBP 的单调不变性、离散化参数三个角度分析原因,并给出改进方案。
  4. 放射组学为什么要做特征 ICC 筛选?不做会有什么后果?
  5. 影像组学中 LASSO 的特征选择对应第 3 章的哪个几何原理?为什么在特征高度相关时(如多个 GLCM 特征)LASSO 的选择结果不稳定,可以如何改进(提示:Elastic Net)?
  6. 一个肺炎筛查模型 AUC 0.96,但在患病率 0.5% 的体检人群中,特异性 95% 意味着每 1000 人会产生多少假阳性?阳性预测值是多少?这说明了什么筛查设计原则?
  7. Grad-CAM 显示肺炎模型的热图集中在图像边缘而非肺野,模型可能学到了什么?你会设计哪些实验来验证和修复?

参考文献

  1. Lorensen W E, Cline H E. Marching cubes: A high resolution 3D surface construction algorithm. ACM SIGGRAPH, 1987, 21(4): 163--169.
  2. Haralick R M, Shanmugam K, Dinstein I. Textural features for image classification. IEEE TSMC, 1973, 3(6): 610--621.(GLCM 的原始文献)
  3. Ojala T, Pietikäinen M, Mäenpää T. Multiresolution gray-scale and rotation invariant texture classification with local binary patterns. IEEE TPAMI, 2002, 24(7): 971--987.(LBP)
  4. Lambin P, Rios-Velazquez E, Leijenaar R, et al. Radiomics: extracting more information from medical images using advanced feature analysis. European Journal of Cancer, 2012, 48(4): 441--446.(影像组学奠基论文)
  5. Gillies R J, Kinahan P E, Hricak H. Radiomics: images are more than pictures, they are data. Radiology, 2016, 278(2): 563--577.
  6. Zwanenburg A, Vallières M, Abdalah M A, et al. The image biomarker standardization initiative: standardized quantitative radiomics for high-throughput image-based phenotyping. Radiology, 2020, 295(2): 328--338.(IBSI 标准)
  7. van Griethuysen J J M, Fedorov A, Parmar C, et al. Computational radiomics system to decode the radiographic phenotype. Cancer Research, 2017, 77(21): e104--e107.(PyRadiomics)
  8. Aerts H J W L, Velazquez E R, Leijenaar R T H, et al. Decoding tumour phenotype by noninvasive imaging using a quantitative radiomics approach. Nature Communications, 2014, 5: 4006.
  9. Selvaraju R R, Cogswell M, Das A, et al. Grad-CAM: Visual explanations from deep networks via gradient-based localization. ICCV, 2017: 618--626.
  10. DeLong E R, DeLong D M, Clarke-Pearson D L. Comparing the areas under two or more correlated receiver operating characteristic curves: a nonparametric approach. Biometrics, 1988, 44(3): 837--845.
  11. Geirhos R, Jacobsen J H, Michaelis C, et al. Shortcut learning in deep neural networks. Nature Machine Intelligence, 2020, 2(11): 665--673.
  12. Rajpurkar P, Irvin J, Zhu K, et al. CheXNet: Radiologist-level pneumonia detection on chest X-rays with deep learning. arXiv:1711.05225, 2017.
  13. Lambin P, Leijenaar R T H, Deist T M, et al. Radiomics: the bridge between medical imaging and personalized medicine. Nature Reviews Clinical Oncology, 2017, 14(12): 749--762.
  14. PyRadiomics 官方文档. https://pyradiomics.readthedocs.io
  15. IBSI 官方文档. https://theibsi.github.io
相关推荐
u0105871451 小时前
医学图像处理教学系统技术架构与实现
图像处理·健康医疗·课程设计
无语的小歪歪1 小时前
ATF 智测 1.0
python·测试工具·集成测试
czq_26867194871 小时前
Python打卡第45天
开发语言·python
java_prinln1 小时前
9. Agent 的长短期记忆系统怎么做的?记忆是怎么存的?粒度是多少?怎么用的?
数据库·python·agent
FOAF-lambda1 小时前
Python实战:基于OpenCV与单调栈,精准定位PDF中最大的空白区域
python·opencv·pdf
terry6001 小时前
企业数据查询接口防爬实战:图形验证码服务端校验接入与限流设计(附Python)
python·信息与通信·图形验证码·反爬虫·人机验证·行为验证码·滑动拼图验证
量化炼金 (CodeAlchemy)1 小时前
【Python】pandas vs polars 简单性能对比测试
开发语言·python·pandas
学习论之费曼学习法1 小时前
无人值守AI内容矩阵实战(7):Playwright 发布器避坑——编辑器竞态、索引延迟与风控
python·线性代数·矩阵·编辑器·浏览器自动化·playwright·踩坑记录
wuyk5552 小时前
PyQt6-第06讲-主窗口框架
python·pyqt