第 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% 空间)
核心优势:
- 仿射矩阵:内置 4×4 变换矩阵,精确定义体素到世界坐标的映射
- 多维度支持:原生支持 3D(体数据)、4D(fMRI 时间序列)、5D(多通道 4D)
- 扩展字段 :
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
医学影像小贴士:配准的"坑"
- 方向问题 :不同设备、不同协议的图像可能有不同的方向约定(RAS vs LPS)。配准前务必用
sitk.DICOMOrientImageFilter统一方向。- FOV 差异:如果 fixed 和 moving 的视野差异很大(如一个扫全脑、一个只扫局部),需要先裁剪到共同区域。
- 收敛判断:不要只看迭代次数,要监控相似性度量的变化曲线,确认真的收敛了。
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")
医学影像小贴士:去噪与诊断的平衡
去噪不是越强越好!过度去噪会:
- 模糊微小病灶(如 3mm 的肺结节)
- 改变纹理特征(影响影像组学分析)
- 引入伪影(如边缘光晕)
最佳实践:与放射科医生合作,确定"可接受的噪声水平",避免为了"好看"而牺牲诊断价值。
提示一个容易被忽略的陷阱:深度学习模型对输入噪声分布很敏感。训练时用了干净图像,测试时给真实低剂量 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)
医学影像小贴士:融合方法的选择
方法 优点 缺点 适用场景 加权叠加 简单快速 可能模糊细节 快速预览、教学演示 伪彩色叠加 临床标准,直观 不适合定量分析 临床诊断工作站 小波融合 保留细节最好 计算量大,可能引入伪影 科研、精细分析 深度学习融合 端到端学习 需要大量训练数据 研究前沿
本章小结
本章系统讲解了医学影像预处理的四大核心任务:
-
格式处理
- DICOM 的完整解析(元数据提取、HU 转换、序列排序)
- NIfTI、NRRD 等科研格式的特点与转换
- 关键陷阱:维度顺序(xyz vs zyx)、空间信息的保留
-
标准化与归一化
- 强度归一化:Min-Max、Z-Score、百分位数裁剪
- 模态特异策略(CT 用窗宽窗位、MRI 用 Z-Score)
- 解剖学标准化:MNI 空间、基于模板的多分辨率配准
-
去噪
- 高斯滤波的 σ \sigma σ 选择(各向异性体素的处理)
- 中值滤波对椒盐噪声的奇效
- 低剂量 CT 的混合去噪策略
-
增强
- CLAHE 的参数调优与模态适配
- 重要警告:CLAHE 会改变像素分布,不适合定量分析
- 多模态融合:PET-CT 的配准、加权、伪彩色、小波融合
预处理的黄金法则:
- ✅ 先可视化,再处理:每一步都要画图检查
- ✅ 保留原始数据:预处理结果另存,不要覆盖原始文件
- ✅ 记录参数:所有预处理步骤的参数都要记录,保证可复现
- ❌ 不要过度处理:为了"好看"而牺牲诊断价值是本末倒置
思考题
- 为什么不能简单按文件名排序拼 DICOM 切片?按 Image Position 排序解决了什么问题?
- 为什么不能直接用
ds.pixel_array的值做 CT 图像分析?RescaleSlope和RescaleIntercept的物理意义是什么?- 在 Z-Score 标准化时,为什么建议在前景掩码内计算均值和标准差,而不是用整个图像?请从"背景占比"和"统计偏差"两个角度解释。
- 多分辨率配准为什么能缓解非凸优化的局部最优问题?请结合 3.2 节的优化理论说明。
- 一个 3D CT 体数据的 spacing 是 (0.5, 0.5, 2.0) mm,如果要在物理空间做 1mm 的各向同性高斯平滑,体素空间的 sigma 应该是多少?为什么?
- N4ITK 偏置场校正为什么对脑影像组学几乎是必做步骤?不校正会怎样?
- CLAHE 增强后的图像,是否适合用于影像组学特征提取?为什么?如果要用于深度学习训练呢?
参考文献
- Bankman I. Handbook of Medical Image Processing and Analysis (2nd ed.). Academic Press, 2008.
- NEMA. Digital Imaging and Communications in Medicine (DICOM) Standard. https://www.dicomstandard.org
- Cox R W, Ashburner J, Breman H, et al. A (sort of) new image data format standard: NIfTI-1. NeuroImage, 2004, 22: e1440.
- 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.
- Tustison N J, Avants B B, Cook P A, et al. N4ITK: improved N3 bias correction. IEEE TMI, 2010, 29(6): 1310--1320.
- Maintz J B A, Viergever M A. A survey of medical image registration. Medical Image Analysis, 1998, 2(1): 1--36.
- Zuiderveld K. Contrast limited adaptive histogram equalization. Graphics Gems, 1994: 474--485.
- 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.
- Townsend D W. Multimodality imaging of structure and function. Physics in Medicine & Biology, 2008, 53(4): R1.
- Burt P J, Adelson E H. The Laplacian pyramid as a compact image code. IEEE Transactions on Communications, 1983, 31(4): 532--540.
- pydicom 官方文档. https://pydicom.github.io
- SimpleITK 官方文档. https://simpleitk.org
- nibabel 官方文档. https://nipy.org/nibabel