第 4 章 医学影像预处理

第 4 章 医学影像预处理

本章导读

在医学影像项目中,有一句广为流传的话:"数据预处理决定了模型性能的下限,算法决定了上限 "。深度学习研究者经常发现:同样的网络结构,仅因预处理流程的差异,Dice 系数可以相差 10 个百分点以上。预处理不是"锦上添花"的前奏,而是整个 pipeline 中最容易出错、最影响结果的环节。

本章围绕医学影像预处理的四大核心任务展开:格式处理 (4.1)解决"数据怎么读进来"的问题;标准化与归一化 (4.2)解决"不同来源数据怎么对齐"的问题;去噪 (4.3)解决"信号怎么变干净"的问题;增强(4.4)解决"信息怎么更突出"的问题。全章以"工程实现"为主线,每个知识点都配套可直接运行的代码,并强调临床场景中的陷阱与最佳实践。


本章学习目标

  • 能把散乱的 DICOM 切片按空间位置组织成有序体积,正确应用 Rescale Slope/Intercept;
  • 理解 NIfTI、NRRD 格式的设计特点及它们与 DICOM 的适用边界;
  • 掌握 Min-Max、Z-score、百分位截断等强度归一化方法及各自的风险;
  • 理解 CT 窗宽窗位、MRI 偏置场校正(N4ITK)的工程意义;
  • 了解基于 MNI 模板的解剖学标准化配准;
  • 掌握医学影像的边缘保留去噪方法(各向异性扩散、非局部均值);
  • 能用 CLAHE 做对比度增强,理解其参数与适用条件;
  • 理解多模态图像融合(如 CT 与 PET)的前提是精确配准。

4.1 医学图像格式处理

4.1.1 DICOM 文件解析与转换

第 2 章已经介绍了 DICOM 的基础结构,本节聚焦工程实践------如何在大规模数据集中高效、鲁棒地处理 DICOM。

1. DICOM 元数据的完整提取

一个典型的 DICOM 文件包含数百个 Tag,但并非所有字段对影像处理都有价值。核心元数据可分为四类:

类别 关键 Tag 用途
患者信息 PatientID, PatientName, PatientBirthDate 数据关联(注意隐私保护)
扫描参数 Modality, KVP, XRayTubeCurrent, SliceThickness 协议记录、批次分析
几何信息 PixelSpacing, ImagePositionPatient, ImageOrientationPatient 空间定位(最关键)
像素信息 BitsAllocated, PixelRepresentation, RescaleSlope, RescaleIntercept 像素值到物理值的转换
python 复制代码
"""
代码说明:基于pydicom提取DICOM文件的关键元数据。
"""
import pydicom
import numpy as np
from pathlib import Path

def parse_dicom_metadata(dcm_path):
    """提取 DICOM 文件的关键元数据"""
    ds = pydicom.dcmread(dcm_path, force=True)

    meta = {
        # 患者信息(注意:实际应用中需匿名化)
        'patient_id': getattr(ds, 'PatientID', ''),
        'patient_name': str(getattr(ds, 'PatientName', '')),

        # 扫描参数
        'modality': getattr(ds, 'Modality', ''),
        'manufacturer': getattr(ds, 'Manufacturer', ''),
        'kvp': getattr(ds, 'KVP', None),  # CT 管电压
        'slice_thickness': getattr(ds, 'SliceThickness', None),

        # 几何信息(空间定位的关键)
        'pixel_spacing': getattr(ds, 'PixelSpacing', [1.0, 1.0]),
        'image_position': getattr(ds, 'ImagePositionPatient', [0, 0, 0]),
        'image_orientation': getattr(ds, 'ImageOrientationPatient',
                                     [1, 0, 0, 0, 1, 0]),

        # 像素信息
        'rows': ds.Rows,
        'columns': ds.Columns,
        'bits_allocated': ds.BitsAllocated,
        'rescale_slope': getattr(ds, 'RescaleSlope', 1),
        'rescale_intercept': getattr(ds, 'RescaleIntercept', 0),
    }
    return meta

# 使用示例
dcm_file = Path('data/CT_001.dcm')
meta = parse_dicom_metadata(dcm_file)
print(f"模态: {meta['modality']}, "
      f"体素间距: {meta['pixel_spacing']}, "
      f"层厚: {meta['slice_thickness']}")

注意

一个三维扫描通常不是单个 .dcm 文件,而是几十到几百个独立切片,每个文件只保存一层。直接按文件名排序拼起来往往是错的------文件序号可能不连续、可能夹杂 scout 定位片或重复序列,必须依靠元数据里的空间信息重排。核心思路是读取每张切片的图像位置(Image Position Patient, 标签 0020,0032),即该层左上角的三维物理坐标,再按切片方向(Image Orientation Patient)计算层间间距,按位置对切片排序。这样拼出的体积在物理空间上才是正确的,后续测量才有意义。

2. 像素数据的正确提取与 HU 转换

不要直接用 pixel_array ------这是很多新手踩的另一个坑。CT 图像需要应用 RescaleSlope 和 RescaleIntercept 才能得到有物理意义的 HU 值:

复制代码
HU值 = Rescale Slope × 存储值 + Rescale Intercept 

对 CT,这条公式就是把原始整数还原成 HU(Hounsfield 单位)。

python 复制代码
def load_dicom_pixel(dcm_path):
    """加载 DICOM 像素数据并转换为 HU(仅 CT)"""
    ds = pydicom.dcmread(dcm_path)

    # 原始像素值
    pixel_array = ds.pixel_array.astype(np.float32)

    # 应用 Rescale 得到 HU 值
    if hasattr(ds, 'RescaleSlope') and hasattr(ds, 'RescaleIntercept'):
        hu = pixel_array * ds.RescaleSlope + ds.RescaleIntercept
    else:
        hu = pixel_array  # MRI 等可能没有 Rescale 字段

    return hu, ds

# 示例
hu_image, ds = load_dicom_pixel('CT_chest.dcm')
print(f"HU 范围: [{hu_image.min():.0f}, {hu_image.max():.0f}]")
# 应该看到类似 [-1024, 3071] 的范围,而非 [0, 4095]

医学影像小贴士:HU 值的物理意义

  • 空气:约 −1000 HU
  • 脂肪:约 −100 到 −50 HU
  • 水:0 HU
  • 软组织:+20 到 +70 HU
  • 骨松质:+100 到 +300 HU
  • 骨密质:+700 到 +3000 HU

如果你的"CT 图像"显示的范围是 0--4095,说明还没做 HU 转换!

不做这步换算,阈值分割和HU区间统计都会出错。

3. 序列的排序与三维重建

单个 DICOM 文件是一张切片,实际临床数据是**一个 Series(序列)**包含几十到几百张切片。要构建三维体数据,需要正确排序:

python 复制代码
import SimpleITK as sitk
import numpy as np

def load_dicom_series(series_dir):
    """加载整个 DICOM 序列并构建 3D 体数据"""
    reader = sitk.ImageSeriesReader()

    # 自动识别并排序 DICOM 文件
    dicom_names = reader.GetGDCMSeriesFileNames(str(series_dir))
    reader.SetFileNames(dicom_names)

    # 读取并构建 3D 图像
    image_3d = reader.Execute()

    # 转换为 numpy 数组 (Z, Y, X 顺序)
    volume = sitk.GetArrayFromImage(image_3d)

    # 获取空间信息
    spacing = image_3d.GetSpacing()        # (x, y, z) 体素间距,单位 mm
    origin = image_3d.GetOrigin()          # 原点坐标
    direction = image_3d.GetDirection()    # 方向余弦矩阵(9 个元素)

    return volume, spacing, origin, direction

# 使用示例
volume, spacing, origin, direction = load_dicom_series('data/CT_series/')
print(f"体数据形状: {volume.shape}")       # 例如 (512, 512, 300)
print(f"体素间距: {spacing}")               # 例如 (0.7, 0.7, 1.0) mm
print(f"体素间距 (Z, Y, X): {spacing[::-1]}")  # 与 volume.shape 对应

重要:维度顺序陷阱

  • SimpleITK 的 GetSpacing() 返回 (x, y, z) 顺序
  • 但 GetArrayFromImage() 返回的 numpy 数组是 (z, y, x) 顺序

这是医学影像处理最常见的 bug 来源之一!处理时必须对应好维度。
提示

这段代码隐含了一个临床细节:同一检查可能包含多个序列(增强前、增强后、薄层、厚层),按 (0020,0011) Series Number 或 (0020,000E) Series Instance UID 区分不同序列,避免把两个序列的切片混进同一个体积。工程上处理真实临床数据时, " 先分序列、再按位置排序、最后 Rescale " 是固定的三步。

4. 批量转换 DICOM → NIfTI

临床数据通常是 DICOM,但科研与深度学习更常用 NIfTI。批量转换的标准流程:

python 复制代码
import SimpleITK as sitk
from pathlib import Path
import nibabel as nib
import numpy as np

def dicom_series_to_nifti(dicom_dir, output_path):
    """将 DICOM 序列转换为 NIfTI 格式"""
    # 读取 DICOM 序列
    reader = sitk.ImageSeriesReader()
    dicom_names = reader.GetGDCMSeriesFileNames(str(dicom_dir))
    reader.SetFileNames(dicom_names)
    image_3d = reader.Execute()

    # 方法1:直接用 SimpleITK 保存为 NIfTI(推荐,保留空间信息)
    sitk.WriteImage(image_3d, str(output_path))

    # 方法2:通过 nibabel(更灵活,可以自定义)
    # volume = sitk.GetArrayFromImage(image_3d)
    # affine = np.eye(4)
    # affine[0, 0] = image_3d.GetSpacing()[0]
    # affine[1, 1] = image_3d.GetSpacing()[1]
    # affine[2, 2] = image_3d.GetSpacing()[2]
    # affine[:3, 3] = image_3d.GetOrigin()
    # nii_img = nib.Nifti1Image(volume, affine)
    # nib.save(nii_img, output_path)

    print(f"已保存: {output_path}")

# 批量处理
dicom_root = Path('data/raw_dicom/')
nifti_root = Path('data/nifti/')
nifti_root.mkdir(parents=True, exist_ok=True)

for patient_dir in dicom_root.iterdir():
    if patient_dir.is_dir():
        output_file = nifti_root / f"{patient_dir.name}.nii.gz"
        try:
            dicom_series_to_nifti(patient_dir, output_file)
        except Exception as e:
            print(f"转换失败 {patient_dir.name}: {e}")

4.1.2 NIfTI、NRRD 等格式规范与应用场景

医学影像有多种格式,各有适用场景:

格式 全称 特点 典型应用
DICOM Digital Imaging and Communications in Medicine 临床标准,包含完整元数据 医院 PACS、临床数据交换
NIfTI Neuroimaging Informatics Technology Initiative 神经影像标准,单文件存储 3D/4D fMRI、DTI、脑影像研究
NRRD Nearly Raw Raster Data 灵活的头文件格式,支持任意维度 放疗计划、图像引导手术
MetaImage .mhd + .raw 头文件与原始数据分离 ITK 原生格式、算法测试
Analyze .img + .hdr 老式格式,逐渐被 NIfTI 取代 遗留数据集
1. NIfTI 格式详解

NIfTI 是神经影像领域的事实标准,有两种存储方式:

  • .nii:单文件(头 + 数据)
  • .nii.gz :gzip 压缩的 .nii(更常用,节省 70% 空间)

核心优势:

  1. 仿射矩阵:内置 4×4 变换矩阵,精确定义体素到世界坐标的映射
  2. 多维度支持:原生支持 3D(体数据)、4D(fMRI 时间序列)、5D(多通道 4D)
  3. 扩展字段 :qform 和 sform 两套坐标系,适应不同配准需求

NIfTI的最大优点是几何信息与像素数据绑定:加载后直接得到一个数组加一个仿射变换矩阵,像素坐标到物理坐标的换算一步到位。它在脑部 MRI 研究中几乎是标准格式,BRATS 等公开数据集都以 NIfTI 发布。

python 复制代码
"""
描述:NIfTI文件的加载与提取
"""
import nibabel as nib
import numpy as np

# 加载 NIfTI
nii = nib.load('brain_t1.nii.gz')

# 提取数据与元数据
data = nii.get_fdata()           # numpy 数组,自动转为 float
affine = nii.affine              # 4×4 仿射矩阵
header = nii.header              # 完整头信息

print(f"数据形状: {data.shape}")
print(f"数据类型: {data.dtype}")
print(f"体素间距: {header.get_zooms()[:3]}")  # (x, y, z) mm

# 仿射矩阵的含义
# data[i, j, k] 的世界坐标 = affine @ [i, j, k, 1]
voxel_ijk = np.array([128, 128, 64, 1])
world_xyz = affine @ voxel_ijk
print(f"体素 {voxel_ijk[:3]} 的世界坐标: {world_xyz[:3]}")

# 保存新的 NIfTI
new_nii = nib.Nifti1Image(data, affine)
nib.save(new_nii, 'output.nii.gz')
2. NRRD 格式

NRRD(Nearly Raw Raster Data)更轻量,是一个纯文本头描述原始栅格数据,常用于算法中间结果和 DWI/DTI 数据(DIPY 库原生读写 NRRD)。它的优点是简单、易扩展、支持多分量数据(如弥散成像的多梯度方向图像堆叠),缺点是在医学成像社区的普及度不如 NIfTI。

NRRD 的优势是头文件是纯文本,可以直接用文本编辑器查看:

复制代码
NRRD0004
# Complete NRRD file format specification at:
# http://teem.sourceforge.net/nrrd/format.html
type: short
dimension: 3
space: left-posterior-superior
sizes: 512 512 300
space directions: (0.7,0,0) (0,0.7,0) (0,0,1.0)
kinds: domain domain domain
encoding: gzip
data file: CT_volume.raw.gz

NRRD 在放疗计划系统 (如 Eclipse、RayStation)和图像引导手术(如 3D Slicer)中广泛使用。

python 复制代码
import SimpleITK as sitk

# SimpleITK 可以无缝读写 NRRD
image = sitk.ReadImage('volume.nrrd')
sitk.WriteImage(image, 'volume.nii.gz')  # 转为 NIfTI
3. 格式选择建议

三类格式的分工可以这样概括:DICOM 是临床交换 的标准,NIfTI 是神经影像研究 的标准,NRRD 是算法工程中间表示。真实工作流通常是"从 PACS 导出 DICOM → 用 SimpleITK 或 dcm2niix 工具转换为 NIfTI → 加载做算法"。

场景 推荐格式 理由
从医院 PACS 导出 DICOM 保留完整元数据
深度学习训练 NIfTI (.nii.gz) 读写快、空间信息完整、社区标准
放疗计划 NRRD 放疗软件原生支持
算法调试 MetaImage (.mhd) 头文件可读、易检查
跨平台传输 NIfTI (.nii.gz) 压缩率高、通用性好

4.2 图像标准化与归一化

标准化(Standardization) 是预处理中最容易被忽视、却最影响模型泛化的环节。不同设备、不同协议、不同患者的图像,其灰度分布可能天差地别。如果不做标准化,模型会学到"设备特征"而非"病灶特征"。强度归一化就是把这些差异压缩到一个统一范围。

4.2.1 强度归一化

1. Min-Max 归一化

最简单的线性归一化,将像素值缩放到 0, 1 或 -1, 1:

I n o r m = I − I m i n I m a x − I m i n I_{norm} = \frac{I - I_{min}}{I_{max} - I_{min}} Inorm=Imax−IminI−Imin

python 复制代码
def min_max_normalize(image, out_range=(0, 1)):
    """Min-Max 归一化"""
    min_val, max_val = image.min(), image.max()
    normalized = (image - min_val) / (max_val - min_val + 1e-8)

    # 映射到目标范围
    a, b = out_range
    return normalized * (b - a) + a

# 使用示例
import numpy as np
ct_slice = np.random.randint(-1000, 3000, (512, 512)).astype(np.float32)
ct_norm = min_max_normalize(ct_slice, out_range=(0, 1))
print(f"归一化后范围: [{ct_norm.min():.3f}, {ct_norm.max():.3f}]")

优点:简单、直观、快速、可逆。

缺点 :对异常值极其敏感------如果图像中有金属植入物(HU > 3000),整个图像会被压缩到很窄的范围。

提示

为了减少Min-Max归一化对极值的极其敏感度,因此工程上常配合百分位截断:不直接用 min/max,而是用第 1 和第 99 百分位作为上下界,把头尾各 1% 的极端值截掉,再做 Min-Max。

2. Z-Score 标准化

将像素值变换为均值为 0、标准差为 1 的分布:

I n o r m = I − μ σ I_{norm} = \frac{I - \mu}{\sigma} Inorm=σI−μ

其中 μ \mu μ 是均值, σ \sigma σ 是标准差。

Z-score 对离群值比 Min-Max 稳健,但 σ 的估计仍受极端值影响,所以实际中常先用百分位截断再算 Z-score,或用中位数与四分位距做鲁棒估计。

python 复制代码
def z_score_normalize(image, mask=None):
    """
    Z-Score 标准化
    mask: 可选的前景掩码,只在前景内计算统计量(推荐)
    """
    if mask is not None:
        # 只在前景区域内计算均值和标准差
        foreground = image[mask > 0]
        mean, std = foreground.mean(), foreground.std()
    else:
        mean, std = image.mean(), image.std()

    return (image - mean) / (std + 1e-8)

# 使用示例:脑部 MRI 标准化
brain_mri = np.random.randn(256, 256, 128).astype(np.float32) * 100 + 500
brain_mask = brain_mri > 200  # 简单的前景掩码

brain_norm = z_score_normalize(brain_mri, mask=brain_mask)
print(f"标准化后: mean={brain_norm[brain_mask].mean():.3f}, "
      f"std={brain_norm[brain_mask].std():.3f}")

医学影像小贴士:为什么要在前景内计算 Z-Score?

医学影像中,背景(空气、扫描床)往往占据 50% 以上的体素。如果用全体素计算均值和标准差,会被大量背景值"带偏"。最佳实践:只在感兴趣区域(ROI)或前景掩码内计算统计量,再应用到整个图像。

3. 百分位数裁剪 + Min-Max

结合鲁棒性与线性映射,先裁剪极端值,再做 Min-Max:

python 复制代码
def percentile_normalize(image, lower=0.5, upper=99.5, out_range=(0, 1)):
    """
    百分位数归一化:先按百分位数裁剪,再 Min-Max
    对异常值(金属伪影、极端噪声)更鲁棒
    """
    p_low, p_high = np.percentile(image, [lower, upper])

    # 裁剪到百分位数范围
    clipped = np.clip(image, p_low, p_high)

    # Min-Max 归一化
    normalized = (clipped - p_low) / (p_high - p_low + 1e-8)

    a, b = out_range
    return normalized * (b - a) + a

# CT 图像:裁剪掉金属伪影的极端 HU 值
ct_with_metal = np.random.randint(-1000, 3000, (512, 512)).astype(np.float32)
ct_with_metal[100:150, 100:150] = 30000  # 模拟金属伪影

ct_robust = percentile_normalize(ct_with_metal, lower=1, upper=99)
print(f"鲁棒归一化后范围: [{ct_robust.min():.3f}, {ct_robust.max():.3f}]")

重要

要强调一点:CT 与 MRI 的归一化哲学完全不同 。CT 有固定的物理基准(HU 单位,水恒为 0、空气恒为 −1000),因此 CT 图像不需要全局强度归一化,而是用窗宽窗位把特定组织的 HU 范围映射到显示灰度------肺窗(WL −600, WW 1500)看肺结节,纵隔窗(WL 40, WW 400)看纵隔结构。MRI 没有这种固定物理基准,T1、T2、FLAIR 各有自己的强度刻度,跨扫描仪差异大,因此 MRI 才真正依赖 Z-score 和解剖学归一化。

4. 模态特异的标准化策略

不同模态的物理意义不同,标准化策略也应有所区别:

模态 像素值物理意义 推荐标准化方法
CT HU 值(有标准定义) 窗宽窗位 + Min-Max 到 0, 1
MRI 无标准单位(相对信号强度) Z-Score(前景内)或直方图匹配
PET SUV 值(半定量) 除以肝脏平均 SUV(参考器官归一化)
超声 无标准单位 直方图匹配或 Z-Score
python 复制代码
"""
说明:根据模态选择合适的归一化方法
"""
def modality_specific_normalize(image, modality, **kwargs):
    if modality == 'CT':
        # CT:使用预设窗宽窗位(例如肺窗 [-1000, 400])
        wl, ww = kwargs.get('window_level', -600), kwargs.get('window_width', 1500)
        lower, upper = wl - ww / 2, wl + ww / 2
        clipped = np.clip(image, lower, upper)
        return (clipped - lower) / (upper - lower)

    elif modality == 'MRI':
        # MRI:前景 Z-Score
        mask = kwargs.get('mask', image > np.percentile(image, 10))
        return z_score_normalize(image, mask=mask)

    elif modality == 'PET':
        # PET:SUV 归一化(假设已转换为 SUV)
        liver_suv = kwargs.get('liver_suv', 2.0)  # 肝脏参考 SUV
        return image / liver_suv

    else:
        # 默认:Min-Max
        return min_max_normalize(image)
5. 窗宽窗位与MRI偏置场校正

CT 的"归一化"更准确的叫法是窗宽窗位(Window Width/Level)显示变换。它不是把图像改了,而是按临床需要选择 HU 的一段范围映射到 8 位显示灰度:

显示值 = (HU − WL + WW/2) / WW × 255,再截断到 0, 255。

WL 是窗位(中心 HU),WW 是窗宽(显示 HU 的跨度)。选不同窗宽窗位等于在同一幅 CT 上"聚焦"不同组织。算法预处理时也会利用这一点------比如肺结节检测模型,输入常常不是原始 HU 全图,而是切出肺窗对应的 HU 区间,再归一化到 0, 1,让模型专注于肺实质和结节。

MRI 面临另一个问题:偏置场(Bias Field) 。由于射频场不均匀、患者体位等原因,同一组织在 MRI 图像里的灰度会出现缓慢变化的明暗渐变------脑部图像一边亮一边暗。这对强度归一化和基于灰度的分割是灾难性的:同一个白质,左边可能比右边亮 30%,GMM 会把它当成两种组织。N4ITK 是目前最常用的偏置场校正算法,它估计一个平滑的乘性场并除掉,让全脑灰度一致化。

python 复制代码
"""
说明:基于SimpleITK实现N4ITK偏置场校正。
"""
import SimpleITK as sitk

img = sitk.ReadImage("brain_mri.nii.gz")
img = sitk.Cast(img, sitk.sitkFloat32)
# 通常先做一个粗略的脑掩模再校正,避免颅外组织干扰
mask = sitk.OtsuThreshold(img, 0, 1, 200)
corr = sitk.N4BiasFieldCorrection(img, mask)
sitk.WriteImage(corr, "brain_mri_n4.nii.gz")

提示

偏置场校正是 MRI 预处理管线里几乎不可跳过的一步,尤其在跨中心多中心研究中,不做 N4 校正几乎不可能得到可重复的组学特征或分割结果。

4.2.2 解剖学标准化

解剖学标准化(Anatomical Standardization) 是将不同患者的影像配准到同一个标准空间(如 MNI 空间),使得"同一个体素位置对应同一个解剖结构"。这是群体分析、脑影像研究的必要步骤。

1. 标准模板:MNI 空间

MNI(Montreal Neurological Institute)空间 是脑影像研究的标准空间,基于 152 名健康成年人的 MRI 平均而成。所有个体脑部 MRI 都配准到 MNI 模板后,才能进行统计比较。

常用的 MNI 模板:

  • MNI152:最经典,1mm 分辨率
  • MNI152NLin2009cAsym:非对称版本,保留左右差异
  • MNI152NLin6Asym:FSL 常用版本
2. 基于模板的配准流程
python 复制代码
import SimpleITK as sitk
import numpy as np

def register_to_template(moving_image_path, template_path, output_path):
    """
    将个体 MRI 配准到 MNI 模板
    moving_image: 个体 MRI
    template: MNI 模板
    """
    # 读取图像
    fixed = sitk.ReadImage(template_path, sitk.sitkFloat32)
    moving = sitk.ReadImage(moving_image_path, sitk.sitkFloat32)

    # 1. 初始对齐:几何中心对齐
    initial_transform = sitk.CenteredTransformInitializer(
        fixed, moving,
        sitk.Euler3DTransform(),
        sitk.CenteredTransformInitializerFilter.GEOMETRY
    )

    # 2. 刚体配准(旋转 + 平移)
    registration = sitk.ImageRegistrationMethod()
    registration.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
    registration.SetMetricSamplingStrategy(registration.RANDOM)
    registration.SetMetricSamplingPercentage(0.1)
    registration.SetInterpolator(sitk.sitkLinear)
    registration.SetOptimizerAsRegularStepGradientDescent(
        learningRate=2.0, minStep=1e-4, numberOfIterations=200)
    registration.SetInitialTransform(initial_transform)

    rigid_transform = registration.Execute(fixed, moving)
    print(f"刚体配准完成,最终互信息: {registration.GetMetricValue():.4f}")

    # 3. 仿射配准(在刚体基础上,允许缩放和剪切)
    affine_transform = sitk.AffineTransform(fixed.GetDimension())
    affine_transform.SetCenter(rigid_transform.GetCenter())
    affine_transform.SetMatrix(rigid_transform.GetMatrix())
    affine_transform.SetTranslation(rigid_transform.GetTranslation())

    registration.SetInitialTransform(affine_transform)
    registration.SetOptimizerAsRegularStepGradientDescent(
        learningRate=1.0, minStep=1e-5, numberOfIterations=200)

    final_transform = registration.Execute(fixed, moving)
    print(f"仿射配准完成,最终互信息: {registration.GetMetricValue():.4f}")

    # 4. 应用变换
    resampled = sitk.Resample(moving, fixed, final_transform,
                              sitk.sitkLinear, 0.0, moving.GetPixelID())

    sitk.WriteImage(resampled, output_path)
    print(f"已保存配准结果: {output_path}")

    return final_transform

# 使用示例(需要 MNI 模板文件)
# transform = register_to_template(
#     'patient_brain.nii.gz',
#     'mni152_template.nii.gz',
#     'patient_brain_mni.nii.gz'
# )

配准完成后,一个体素坐标 (x, y, z) 在所有人图上都指向同一个脑区------这是脑影像组学、连接组学、群体统计分析的基础。配准本身是第 6 章的主题,这里只需知道:强度归一化和解剖学标准化在预处理管线里常常串联使用,前者处理灰度,后者处理位置。

在分割、病灶检测这类个体内任务中,解剖学标准化并非必需------模型只需要识别"这个肺里的结节",不关心它对应哪个 MNI 坐标。但一旦做组间统计、脑图谱分析、跨中心研究,模板配准就必不可少。

3. 多分辨率配准策略

医学影像配准是一个非凸优化问题 (见 3.2 节),容易陷入局部最优。多分辨率(Multi-Resolution) 策略是解决这一问题的标准方法:先在低分辨率下粗配准,再逐步提高到高分辨率精配准。

python 复制代码
def multi_resolution_registration(fixed_path, moving_path, output_path):
    """多分辨率配准:从粗到细"""
    fixed = sitk.ReadImage(fixed_path, sitk.sitkFloat32)
    moving = sitk.ReadImage(moving_path, sitk.sitkFloat32)

    registration = sitk.ImageRegistrationMethod()

    # 设置多分辨率金字塔(3 层)
    registration.SetShrinkFactorsPerLevel(shrinkFactors=[4, 2, 1])
    registration.SetSmoothingSigmasPerLevel(smoothingSigmas=[2, 1, 0])
    registration.SmoothingSigmasAreSpecifiedInPhysicalUnitsOn()

    # 相似性度量与优化器
    registration.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
    registration.SetMetricSamplingStrategy(registration.RANDOM)
    registration.SetMetricSamplingPercentage(0.1)
    registration.SetInterpolator(sitk.sitkLinear)
    registration.SetOptimizerAsRegularStepGradientDescent(
        learningRate=2.0, minStep=1e-4, numberOfIterations=200,
        estimateLearningRate=registration.EachIteration)

    # 初始变换
    initial_transform = sitk.CenteredTransformInitializer(
        fixed, moving, sitk.AffineTransform(fixed.GetDimension()),
        sitk.CenteredTransformInitializerFilter.GEOMETRY)
    registration.SetInitialTransform(initial_transform, inPlace=False)

    # 执行配准
    final_transform = registration.Execute(fixed, moving)

    # 应用变换
    resampled = sitk.Resample(moving, fixed, final_transform,
                              sitk.sitkLinear, 0.0, moving.GetPixelID())
    sitk.WriteImage(resampled, output_path)

    print(f"多分辨率配准完成")
    return final_transform

医学影像小贴士:配准的"坑"

  1. 方向问题 :不同设备、不同协议的图像可能有不同的方向约定(RAS vs LPS)。配准前务必用 sitk.DICOMOrientImageFilter 统一方向。
  2. FOV 差异:如果 fixed 和 moving 的视野差异很大(如一个扫全脑、一个只扫局部),需要先裁剪到共同区域。
  3. 收敛判断:不要只看迭代次数,要监控相似性度量的变化曲线,确认真的收敛了。

4.3 医学图像去噪

第 2 章已经介绍了噪声类型与基本滤波器,本节聚焦医学影像的实战去噪。

4.3.1 高斯滤波:平滑与保留细节的权衡

高斯滤波是最常用的线性滤波器,其核函数为:

G ( x , y ) = 1 2 π σ 2 exp ⁡ ( − x 2 + y 2 2 σ 2 ) G(x, y) = \frac{1}{2\pi\sigma^2} \exp\left(-\frac{x^2 + y^2}{2\sigma^2}\right) G(x,y)=2πσ21exp(−2σ2x2+y2)

σ \sigma σ 的选择 是关键: σ \sigma σ 越大,平滑越强,但细节丢失越多。

python 复制代码
"""
高斯滤波
"""
from scipy import ndimage
import numpy as np
import matplotlib.pyplot as plt

# 模拟带噪声的 MRI 图像
clean_mri = np.random.rand(256, 256) * 100 + 200
noisy_mri = clean_mri + np.random.randn(256, 256) * 15  # 加性高斯噪声

# 不同 sigma 的高斯滤波
sigma_values = [0.5, 1.0, 2.0, 4.0]
filtered_images = [ndimage.gaussian_filter(noisy_mri, sigma=s)
                   for s in sigma_values]

# 计算 PSNR(峰值信噪比)
def calculate_psnr(clean, noisy):
    mse = np.mean((clean - noisy) ** 2)
    max_val = clean.max()
    return 20 * np.log10(max_val / np.sqrt(mse))

print(f"原始噪声图像 PSNR: {calculate_psnr(clean_mri, noisy_mri):.2f} dB")
for s, img in zip(sigma_values, filtered_images):
    psnr = calculate_psnr(clean_mri, img)
    print(f"高斯滤波 (σ={s}) PSNR: {psnr:.2f} dB")

经验法则:

  • CT 去噪 : σ = 0.5 \sigma = 0.5 σ=0.5-- 1.0 1.0 1.0(保持边缘)
  • MRI 去噪 : σ = 1.0 \sigma = 1.0 σ=1.0-- 2.0 2.0 2.0(允许更多平滑)
  • PET 去噪 : σ = 2.0 \sigma = 2.0 σ=2.0-- 4.0 4.0 4.0(PET 本身分辨率低,可以强平滑)
3D 体数据的各向异性高斯滤波

医学影像常有各向异性 体素(如 CT 层内 0.5mm、层间 2mm)。直接在体素空间做各向同性高斯滤波,会导致不同方向的平滑程度不一致。各向异性扩散滤波(Anisotropic Diffusion) 的思想是让扩散像热扩散一样进行,但在梯度大的位置(边缘)抑制扩散、在梯度小的位置(均匀区域)正常平滑。

python 复制代码
def anisotropic_gaussian_filter(volume, spacing, sigma_mm=1.0):
    """
    物理空间各向同性高斯滤波
    volume: 3D numpy array (Z, Y, X)
    spacing: (z, y, x) 体素间距,单位 mm
    sigma_mm: 目标高斯核标准差,单位 mm
    """
    # 将物理空间的 sigma 转换为体素空间的 sigma
    sigma_voxel = [sigma_mm / sp for sp in spacing]

    # 应用高斯滤波
    filtered = ndimage.gaussian_filter(volume, sigma=sigma_voxel)
    return filtered

# 示例:CT 体数据,层内 0.5mm,层间 2mm
ct_volume = np.random.rand(300, 512, 512).astype(np.float32)
ct_spacing = (2.0, 0.5, 0.5)  # (z, y, x)

# 希望在物理空间做 1mm 的高斯平滑
ct_filtered = anisotropic_gaussian_filter(ct_volume, ct_spacing, sigma_mm=1.0)

4.3.2 中值滤波:椒盐噪声的克星

中值滤波 用邻域中位数替代中心像素,对椒盐噪声(随机黑白点)有奇效,同时能较好保留边缘。

python 复制代码
from scipy import ndimage

# 模拟带椒盐噪声的 X 线图像
xray_clean = np.random.rand(512, 512) * 200 + 500
xray_noisy = xray_clean.copy()

# 添加椒盐噪声
salt_pepper = np.random.rand(512, 512)
xray_noisy[salt_pepper < 0.01] = 0      # 1% 黑点
xray_noisy[salt_pepper > 0.99] = 1000   # 1% 白点

# 中值滤波(3x3 窗口)
xray_median = ndimage.median_filter(xray_noisy, size=3)

# 对比高斯滤波
xray_gauss = ndimage.gaussian_filter(xray_noisy, sigma=1.0)

print(f"噪声图像 PSNR: {calculate_psnr(xray_clean, xray_noisy):.2f} dB")
print(f"中值滤波 PSNR: {calculate_psnr(xray_clean, xray_median):.2f} dB")
print(f"高斯滤波 PSNR: {calculate_psnr(xray_clean, xray_gauss):.2f} dB")

中值滤波的优势场景:

  • 椒盐噪声(传感器坏点、传输错误)
  • 需要保留锐利边缘(骨骼边界、器官轮廓)

劣势:

  • 对高斯噪声效果不如高斯滤波
  • 大窗口会产生"块状"伪影

4.3.3 非局部均值(NLM)

非局部均值(Non-Local Means, NLM) 的思路是:医学图像内部有大量相似的局部块(同一个器官里反复出现的纹理单元),因此去噪时不只看邻域,而是在整幅图里搜相似块加权平均。它在 MRI 上效果很好,尤其对 Rician 噪声。代价是计算量较大,实践中常用优化实现或 GPU 加速。

不同模态不同方法

低剂量 CT 的主要噪声是泊松型(光子数少),去噪方法除空间滤波外,第 3 章讲的全变分正则、迭代重建(第 6 章)都是这个问题的求解。MRI 幅度图 的噪声是 Rician 分布,均值滤波会引入偏差,NLM 或专门的 Rician 去噪方法更合适。超声的斑点是乘性噪声,各向异性扩散和 speckle-reducing 各向异性扩散(SRAD)比普通高斯滤波合理。工程上没有"一种滤波走天下",方法选择跟着噪声模型走。

4.3.4 实战:CT 低剂量去噪

低剂量 CT(LDCT)能显著降低辐射剂量,但噪声会大幅增加。这是一个典型的噪声 vs 剂量权衡问题。

python 复制代码
def simulate_low_dose_ct(clean_ct, dose_factor=0.25):
    """
    模拟低剂量 CT
    dose_factor: 剂量因子(0.25 = 25% 剂量)
    噪声标准差与剂量的平方根成反比
    """
    noise_std = 20 / np.sqrt(dose_factor)
    noisy_ct = clean_ct + np.random.randn(*clean_ct.shape) * noise_std
    return noisy_ct

# 模拟全剂量与 1/4 剂量 CT
full_dose_ct = np.random.rand(512, 512) * 1000 - 500  # HU 范围
quarter_dose_ct = simulate_low_dose_ct(full_dose_ct, dose_factor=0.25)

# 组合去噪策略:高斯 + 中值
def hybrid_denoise(noisy_image):
    """先高斯抑制整体噪声,再中值保留边缘"""
    step1 = ndimage.gaussian_filter(noisy_image, sigma=0.8)
    step2 = ndimage.median_filter(step1, size=3)
    return step2

denoised = hybrid_denoise(quarter_dose_ct)

print(f"1/4 剂量 CT PSNR: {calculate_psnr(full_dose_ct, quarter_dose_ct):.2f} dB")
print(f"混合去噪后 PSNR: {calculate_psnr(full_dose_ct, denoised):.2f} dB")

医学影像小贴士:去噪与诊断的平衡

去噪不是越强越好!过度去噪会:

  1. 模糊微小病灶(如 3mm 的肺结节)
  2. 改变纹理特征(影响影像组学分析)
  3. 引入伪影(如边缘光晕)

最佳实践:与放射科医生合作,确定"可接受的噪声水平",避免为了"好看"而牺牲诊断价值。
提示

一个容易被忽略的陷阱:深度学习模型对输入噪声分布很敏感。训练时用了干净图像,测试时给真实低剂量 CT,模型性能会显著下降。因此现代医学影像 AI 管线常常把去噪作为网络的一部分(端到端学习),或至少在训练时做对应的噪声增强,使训练分布与真实数据分布一致。这与第 9 章的数据增强理念一脉相承。


4.4 医学图像增强

图像增强 的目标是突出诊断相关信息,而非简单的"变好看"。

4.4.1 对比度增强:CLAHE 的医学应用

2.2.2 节讲了

第 2 章介绍了全局直方图均衡化(HE),并指出它的问题:当图像存在大片亮背景时,均衡化会把大部分灰度级让给背景,目标结构反而对比不足。医学图像里这种情况非常普遍------X 光片亮背景占大头,CT 软组织对比度低。CLAHE(Contrast Limited Adaptive Histogram Equalization,限制对比度的自适应直方图均衡化) 是 HE 的改进,也是医学影像最常用的局部增强方法,本节聚焦其在医学影像中的参数调优。

CLAHE 做了三件事:把图像分成网格块(tile) ,每块独立做直方图均衡;对每块均衡的放大系数设上限(clip limit) ,避免把噪声过度放大;块与块之间用双线性插值拼接,避免块边界。结果是局部对比度增强,同时不损失全局结构、不把噪声吹成斑点。它在胸片、CT 肺窗、眼底图、病理切片上都有广泛应用。

python 复制代码
"""
基于skimage的CLAHE实现
"""
from skimage import exposure
import numpy as np
import matplotlib.pyplot as plt

def clahe_enhance(image, kernel_size=None, clip_limit=0.01, nbins=256):
    """
    CLAHE 增强
    kernel_size: 分块大小(None = 自动,通常为图像尺寸的 1/8)
    clip_limit: 对比度限制(0.01-0.03 适合医学影像)
    nbins: 直方图 bin 数
    """
    if kernel_size is None:
        kernel_size = min(image.shape) // 8

    # 归一化到 [0, 1]
    img_norm = (image - image.min()) / (image.max() - image.min() + 1e-8)

    # 应用 CLAHE
    enhanced = exposure.equalize_adapthist(
        img_norm,
        kernel_size=kernel_size,
        clip_limit=clip_limit,
        nbins=nbins
    )
    return enhanced

# 示例:胸部 X 线增强
chest_xray = np.random.rand(512, 512) * 100 + 500

# 不同参数的 CLAHE
params = [
    (64, 0.01),  # 保守:小分块,低对比度限制
    (128, 0.03), # 激进:大分块,高对比度限制
]

fig, axes = plt.subplots(1, 3, figsize=(15, 5))
axes[0].imshow(chest_xray, cmap='gray')
axes[0].set_title('原始 X 线')
axes[0].axis('off')

for ax, (ks, cl) in zip(axes[1:], params):
    enhanced = clahe_enhance(chest_xray, kernel_size=ks, clip_limit=cl)
    ax.imshow(enhanced, cmap='gray')
    ax.set_title(f'CLAHE (kernel={ks}, clip={cl})')
    ax.axis('off')

plt.tight_layout()
plt.savefig('clahe_comparison.png', dpi=150)
plt.show()

参数选择指南:

参数 小值(保守) 大值(激进) 推荐
kernel_size 细节保留好,但可能产生棋盘伪影 平滑,但可能过度增强 图像尺寸的 1/8 到 1/4
clip_limit 噪声不放大,但增强有限 增强明显,但噪声也被放大 0.01--0.03
nbins 平滑 细节丰富 256
模态特异的 CLAHE 应用
python 复制代码
def modality_clahe(image, modality):
    """根据模态选择 CLAHE 参数"""

    if modality == 'X-ray':
        # X 线:中等增强
        return clahe_enhance(image, kernel_size=64, clip_limit=0.02)

    elif modality == 'CT':
        # CT:保守增强(避免改变 HU 值分布)
        return clahe_enhance(image, kernel_size=128, clip_limit=0.01)

    elif modality == 'MRI':
        # MRI:可以增强(无标准 HU 值约束)
        return clahe_enhance(image, kernel_size=64, clip_limit=0.03)

    elif modality == 'ultrasound':
        # 超声:强增强(本身对比度低)
        return clahe_enhance(image, kernel_size=32, clip_limit=0.04)

重要:CLAHE 的副作用

CLAHE 会改变像素值的分布 ,这对依赖绝对灰度值的任务(如 CT 的 HU 阈值分割、影像组学特征提取)是致命的。

使用场景:

  • ✅ 深度学习输入(模型学习相对特征)
  • ✅ 可视化展示(增强可读性)
  • ❌ 阈值分割前的预处理
  • ❌ 影像组学特征提取前

4.4.2 多模态图像融合:CT 与 PET 的信息整合

多模态融合 将不同模态的互补信息整合到一张图像中。最经典的例子是 PET-CT 融合:CT 提供解剖结构,PET 提供代谢信息。

1. 配准是融合的前提

PET 和 CT 通常是同一台设备先后扫描的,必须用配准方法把两者对齐到同一物理空间,才能做像素级叠加。融合本身是一个色彩映射操作:CT 作为灰度底图,PET 的代谢强度叠加成彩色(高代谢红 / 黄、低代谢蓝),用透明度混合。

python 复制代码
import SimpleITK as sitk

def register_pet_to_ct(ct_path, pet_path, output_pet_path):
    """将 PET 配准到 CT 空间"""
    ct = sitk.ReadImage(ct_path, sitk.sitkFloat32)
    pet = sitk.ReadImage(pet_path, sitk.sitkFloat32)

    # 刚体配准(PET-CT 通常只有刚体位移)
    registration = sitk.ImageRegistrationMethod()
    registration.SetMetricAsMattesMutualInformation(numberOfHistogramBins=50)
    registration.SetMetricSamplingStrategy(registration.RANDOM)
    registration.SetMetricSamplingPercentage(0.1)
    registration.SetInterpolator(sitk.sitkLinear)
    registration.SetOptimizerAsRegularStepGradientDescent(
        learningRate=2.0, minStep=1e-4, numberOfIterations=100)

    # 初始变换
    initial_transform = sitk.CenteredTransformInitializer(
        ct, pet, sitk.Euler3DTransform(),
        sitk.CenteredTransformInitializerFilter.GEOMETRY)
    registration.SetInitialTransform(initial_transform)

    # 执行配准
    final_transform = registration.Execute(ct, pet)

    # 重采样 PET 到 CT 空间
    pet_resampled = sitk.Resample(pet, ct, final_transform,
                                  sitk.sitkLinear, 0.0, pet.GetPixelID())
    sitk.WriteImage(pet_resampled, output_pet_path)

    return final_transform

注意

任何跨模态叠加,先确认两幅图在物理空间已对齐,否则融合出来的是错位的伪图。

2. 像素级融合:加权叠加

最简单的融合方法是对配准后的图像做加权叠加:

python 复制代码
def simple_fusion(ct_image, pet_image, alpha=0.6):
    """
    简单加权融合
    alpha: CT 的权重(0.6 = 60% CT + 40% PET)
    """
    # 归一化到 [0, 1]
    ct_norm = (ct_image - ct_image.min()) / (ct_image.max() - ct_image.min())
    pet_norm = (pet_image - pet_image.min()) / (pet_image.max() - pet_image.min())

    # 加权融合
    fused = alpha * ct_norm + (1 - alpha) * pet_norm
    return fused

# 示例
ct_slice = np.random.rand(512, 512) * 1000 - 500  # HU
pet_slice = np.random.rand(512, 512) * 10         # SUV

fused_60 = simple_fusion(ct_slice, pet_slice, alpha=0.6)
fused_40 = simple_fusion(ct_slice, pet_slice, alpha=0.4)
3. 伪彩色融合:临床常用方案

临床工作站通常用伪彩色显示融合图像:CT 用灰度,PET 用热铁色阶(hot iron),叠加显示。

python 复制代码
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap

def create_pet_ct_fusion_display(ct_slice, pet_slice, suv_threshold=2.5):
    """
    创建 PET-CT 融合的临床显示效果
    CT: 灰度背景
    PET: 热铁色阶叠加(只显示 SUV > threshold 的区域)
    """
    # CT 归一化到 [0, 1]
    ct_norm = np.clip((ct_slice + 1000) / 2000, 0, 1)  # 窗宽窗位 [-1000, 1000]

    # PET 归一化
    pet_norm = np.clip(pet_slice / 10, 0, 1)  # 假设 SUV 上限 10

    # 创建 RGB 图像
    fusion_rgb = np.zeros((*ct_slice.shape, 3))

    # CT 作为灰度背景
    fusion_rgb[..., 0] = ct_norm
    fusion_rgb[..., 1] = ct_norm
    fusion_rgb[..., 2] = ct_norm

    # PET 高摄取区域用热铁色阶叠加
    hot_mask = pet_slice > suv_threshold
    fusion_rgb[hot_mask, 0] = pet_norm[hot_mask]      # 红通道
    fusion_rgb[hot_mask, 1] = pet_norm[hot_mask] * 0.5  # 绿通道(半强度)
    fusion_rgb[hot_mask, 2] = 0                         # 蓝通道(无)

    return fusion_rgb

# 示例
ct_example = np.random.rand(512, 512) * 1000 - 500
pet_example = np.random.rand(512, 512) * 8
pet_example[200:300, 200:300] = 6  # 模拟高摄取病灶

fusion_display = create_pet_ct_fusion_display(ct_example, pet_example)

plt.figure(figsize=(8, 8))
plt.imshow(fusion_display)
plt.title('PET-CT Fusion (SUV > 2.5 highlighted)')
plt.axis('off')
plt.savefig('pet_ct_fusion.png', dpi=150)
plt.show()
4. 高级融合:小波变换融合

对于需要保留更多细节的场景,可以用小波变换做多尺度融合:

python 复制代码
import pywt

def wavelet_fusion(ct_image, pet_image, wavelet='db4', level=2):
    """
    基于小波变换的多模态融合
    低频取平均(保留整体结构),高频取最大值(保留细节)
    """
    # 归一化
    ct_norm = (ct_image - ct_image.min()) / (ct_image.max() - ct_image.min())
    pet_norm = (pet_image - pet_image.min()) / (pet_image.max() - pet_image.min())

    # 小波分解
    coeffs_ct = pywt.wavedec2(ct_norm, wavelet, level=level)
    coeffs_pet = pywt.wavedec2(pet_norm, wavelet, level=level)

    # 融合系数
    fused_coeffs = []

    # 低频:取平均
    cA_fused = (coeffs_ct[0] + coeffs_pet[0]) / 2
    fused_coeffs.append(cA_fused)

    # 高频:取最大值(保留边缘和细节)
    for i in range(1, len(coeffs_ct)):
        cH_ct, cV_ct, cD_ct = coeffs_ct[i]
        cH_pet, cV_pet, cD_pet = coeffs_pet[i]

        cH_fused = np.maximum(np.abs(cH_ct), np.abs(cH_pet)) * np.sign(cH_ct + cH_pet)
        cV_fused = np.maximum(np.abs(cV_ct), np.abs(cV_pet)) * np.sign(cV_ct + cV_pet)
        cD_fused = np.maximum(np.abs(cD_ct), np.abs(cD_pet)) * np.sign(cD_ct + cD_pet)

        fused_coeffs.append((cH_fused, cV_fused, cD_fused))

    # 小波重构
    fused_image = pywt.waverec2(fused_coeffs, wavelet)

    # 裁剪到原始尺寸(小波变换可能改变尺寸)
    fused_image = fused_image[:ct_image.shape[0], :ct_image.shape[1]]

    return fused_image

# 示例
ct_test = np.random.rand(512, 512)
pet_test = np.random.rand(512, 512)

fused_wavelet = wavelet_fusion(ct_test, pet_test)

医学影像小贴士:融合方法的选择

方法 优点 缺点 适用场景
加权叠加 简单快速 可能模糊细节 快速预览、教学演示
伪彩色叠加 临床标准,直观 不适合定量分析 临床诊断工作站
小波融合 保留细节最好 计算量大,可能引入伪影 科研、精细分析
深度学习融合 端到端学习 需要大量训练数据 研究前沿

本章小结

本章系统讲解了医学影像预处理的四大核心任务:

  1. 格式处理

    • DICOM 的完整解析(元数据提取、HU 转换、序列排序)
    • NIfTI、NRRD 等科研格式的特点与转换
    • 关键陷阱:维度顺序(xyz vs zyx)、空间信息的保留
  2. 标准化与归一化

    • 强度归一化:Min-Max、Z-Score、百分位数裁剪
    • 模态特异策略(CT 用窗宽窗位、MRI 用 Z-Score)
    • 解剖学标准化:MNI 空间、基于模板的多分辨率配准
  3. 去噪

    • 高斯滤波的 σ \sigma σ 选择(各向异性体素的处理)
    • 中值滤波对椒盐噪声的奇效
    • 低剂量 CT 的混合去噪策略
  4. 增强

    • CLAHE 的参数调优与模态适配
    • 重要警告:CLAHE 会改变像素分布,不适合定量分析
    • 多模态融合:PET-CT 的配准、加权、伪彩色、小波融合

预处理的黄金法则:

  • ✅ 先可视化,再处理:每一步都要画图检查
  • ✅ 保留原始数据:预处理结果另存,不要覆盖原始文件
  • ✅ 记录参数:所有预处理步骤的参数都要记录,保证可复现
  • ❌ 不要过度处理:为了"好看"而牺牲诊断价值是本末倒置

思考题

  1. 为什么不能简单按文件名排序拼 DICOM 切片?按 Image Position 排序解决了什么问题?
  2. 为什么不能直接用 ds.pixel_array 的值做 CT 图像分析?RescaleSlope 和 RescaleIntercept 的物理意义是什么?
  3. 在 Z-Score 标准化时,为什么建议在前景掩码内计算均值和标准差,而不是用整个图像?请从"背景占比"和"统计偏差"两个角度解释。
  4. 多分辨率配准为什么能缓解非凸优化的局部最优问题?请结合 3.2 节的优化理论说明。
  5. 一个 3D CT 体数据的 spacing 是 (0.5, 0.5, 2.0) mm,如果要在物理空间做 1mm 的各向同性高斯平滑,体素空间的 sigma 应该是多少?为什么?
  6. N4ITK 偏置场校正为什么对脑影像组学几乎是必做步骤?不校正会怎样?
  7. CLAHE 增强后的图像,是否适合用于影像组学特征提取?为什么?如果要用于深度学习训练呢?

参考文献

  1. Bankman I. Handbook of Medical Image Processing and Analysis (2nd ed.). Academic Press, 2008.
  2. NEMA. Digital Imaging and Communications in Medicine (DICOM) Standard. https://www.dicomstandard.org
  3. Cox R W, Ashburner J, Breman H, et al. A (sort of) new image data format standard: NIfTI-1. NeuroImage, 2004, 22: e1440.
  4. Jenkinson M, Bannister P, Brady M, et al. Improved optimization for the robust and accurate linear registration and motion correction of brain images. NeuroImage, 2002, 17(2): 825--841.
  5. Tustison N J, Avants B B, Cook P A, et al. N4ITK: improved N3 bias correction. IEEE TMI, 2010, 29(6): 1310--1320.
  6. Maintz J B A, Viergever M A. A survey of medical image registration. Medical Image Analysis, 1998, 2(1): 1--36.
  7. Zuiderveld K. Contrast limited adaptive histogram equalization. Graphics Gems, 1994: 474--485.
  8. Pizer S M, Amburn E P, Austin J D, et al. Adaptive histogram equalization and its variations. Computer Vision, Graphics, and Image Processing, 1987, 39(3): 355--368.
  9. Townsend D W. Multimodality imaging of structure and function. Physics in Medicine & Biology, 2008, 53(4): R1.
  10. Burt P J, Adelson E H. The Laplacian pyramid as a compact image code. IEEE Transactions on Communications, 1983, 31(4): 532--540.
  11. pydicom 官方文档. https://pydicom.github.io
  12. SimpleITK 官方文档. https://simpleitk.org
  13. nibabel 官方文档. https://nipy.org/nibabel
相关推荐
Web3&Basketball1 小时前
实测Gemini4Argon:ERQA 87%登顶长程任务榜
数据库·python·大模型·多模态·gemini·长上下文·推理优化
把我格子衫拿来1 小时前
医学影像重采样:spacing 改了,为什么模型位置也跟着错了?
python·dicom·医学影像·simpleitk·重采样
打工仔折腾 AI1 小时前
用 Dashlet 把极空间 NAS 服务入口整理成统一导航首页的实践
java·开发语言·人工智能·后端·python·rpc·ai agent 实战
野生码农AI实战1 小时前
【无标题】
大数据·人工智能
wuyk5551 小时前
PyQt6-第05讲-QtDesigner工作流与ui文件
python·ui·pyqt
桃西西呀2 小时前
搜「怎么做接口限流」,向量库却把「怎么做缓存」的段落递了上来
人工智能·llm·ai编程
像风一样自由20202 小时前
56.实战PostgreSQL-pgvector-FastAPI构建轻量级RAG
数据库·人工智能·postgresql·大模型·fastapi·智能体
Want5952 小时前
Python跳动的爱心②
开发语言·python·数学建模
2401_832298102 小时前
人工智能时代,我们如何与机器同行
人工智能