从 DICOM 到声学属性学习记录:HU 值怎么变成密度和声速
学习主题:临床 CT 的 HU 值 → 密度 / 声速 / 衰减矩阵,三种映射模型 + 衰减分层补全
涉及工具:MATLAB 标定计算 + k-Wave medium 构建
一、这一章要解决什么问题
把临床 CT 的 DICOM 数据,从 HU 值 通过标定和映射,转成 k-Wave 能用的密度、声速、衰减三个矩阵。
这是经颅仿真里最通用的路径------大多数研究用的都是临床 CT,而不是 micro-CT。
二、最小基础知识
概念卡片 1:HU 值是标定过的灰度
是什么 :HU(Hounsfield Unit)是临床 CT 的标准化灰度 。它用两个锚点定义:水 = 0 HU,空气 = −1000 HU。公式为:
text
HU = 1000 × (μ - μ_water) / μ_water
其中 μ 是 X 射线线性衰减系数。
为什么需要 :HU 是跨扫描仪、跨协议可比的。同一块骨,在不同 CT 上 HU 值接近;而 micro-CT 的原始灰度不可比。这就是为什么临床 CT 能直接用 HU 映射,micro-CT 必须先做 ABS 标定。
不这么做会怎样:如果把 HU 当成普通灰度、用错锚点(比如水 = 16400),映射出的密度和声速会完全错位。
概念卡片 2:HU → 密度是两步,不是一步
是什么 :HU 先映射到密度(或孔隙率) ,再映射到声速。中间这一步是必要的,因为声速和密度的物理关系比声速和 HU 的关系更明确。
为什么需要:HU 本质反映的是 X 射线衰减,和密度相关,但不直接等于密度。先转成密度,再用"密度-声速"经验关系,物理链条更清晰。
不这么做会怎样:直接把 HU 线性映射到声速虽然也能算,但缺乏物理依据,外推时误差不可控。
概念卡片 3:三种映射模型的本质区别
是什么:
| 模型 | 映射路径 |
|---|---|
| Marsac | HU → 密度 → 声速,两步线性 |
| Aubry | HU → 孔隙率 ψ → 密度和声速独立混合 |
| Legacy | HU 直接线性映射到密度和声速 |
为什么需要:不同模型对"骨"的物理假设不同。Marsac 假设密度和声速同步变化;Aubry 允许密度和声速各自独立混合;Legacy 是最简单的基线。
不这么做会怎样:如果三种模型结果差异很大,说明你对"骨"的物理假设敏感,需要选一个和实验最吻合的;如果差异很小,说明线性假设在这个 HU 范围内够用。
三、动手做:HU 数据 → k-Wave medium
以下代码模拟一段 DICOM 的 HU 数据,通过三种模型映射到 k-Wave 的 medium。
3.1 模拟 DICOM HU 数据
matlab
clearvars;
%% ===== 1. 模拟 DICOM HU 数据 =====
Nx = 128; Ny = 128;
% 造一个模拟的 HU 图:背景(水/软组织)+ 三层颅骨
hu_img = zeros(Nx, Ny); % 水 = 0 HU
% 软组织层(脑组织,HU 约 20--40)
hu_img(1:30, :) = 30;
% 三层颅骨,HU 依次增高
hu_img(31:45, :) = 800; % 外板(皮质骨)
hu_img(46:70, :) = 400; % 板障(松质骨)
hu_img(71:85, :) = 800; % 内板(皮质骨)
% 脑组织(后半段)
hu_img(86:end, :) = 30;
% HU 范围
HU_MIN = -1000; % 空气
HU_MAX = 2000; % 致密皮质骨上限(临床 CT 常用)
HU_SKULL_MIN = 300; % 颅骨 HU 下限(常用阈值)
fprintf('HU range: [%.0f, %.0f]\n', min(hu_img(:)), max(hu_img(:)));
3.2 HU → 密度(线性标定)
matlab
%% ===== 2. HU → 密度(线性标定)=====
% 两个锚点:水(0 HU, 1.0 g/cm³)和皮质骨(2000 HU, 2.0 g/cm³)
rho_water = 1.0; % g/cm³
rho_bone = 2.0; % g/cm³
% 线性标定:ρ = a * HU + b
rho_slope = (rho_bone - rho_water) / (HU_MAX - 0);
rho_intercept = rho_water;
fprintf('Calibration: rho = %.6e * HU + %.4f\n', rho_slope, rho_intercept);
输出:
text
Calibration: rho = 5.000000e-04 * HU + 1.0000
即 ρ = 1.0 + HU/2000,这是最常用的骨密度标定式。
3.3 三种映射模型
matlab
%% ===== 3. 三种映射模型 =====
bone_mask = hu_img >= HU_SKULL_MIN;
hu_bone = double(hu_img(bone_mask));
HU_MAP_MIN = HU_SKULL_MIN;
HU_MAP_MAX = max(hu_img(:));
RHO_MIN = 1.0; RHO_MAX = 2.0;
C_MIN = 1500; C_MAX = 2820;
% ----- 3.1 Marsac 2017:HU → 密度 → 声速(两步线性)-----
hu_norm = (hu_bone - HU_MAP_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
hu_norm = max(0, min(1, hu_norm));
rho_marsac = RHO_MIN + (RHO_MAX - RHO_MIN) * hu_norm;
rho_norm = (rho_marsac - RHO_MIN) / (RHO_MAX - RHO_MIN);
c_marsac = C_MIN + (C_MAX - C_MIN) * rho_norm;
% ----- 3.2 Aubry 2003:孔隙率 ψ → 密度和声速(并行混合)-----
psi = 1 - hu_norm;
AUBRY_RHO_WATER = 1.0; AUBRY_RHO_BONE = 2.0;
AUBRY_C_WATER = 1500; AUBRY_C_BONE = 2820;
rho_aubry = psi * AUBRY_RHO_WATER + (1 - psi) * AUBRY_RHO_BONE;
c_aubry = psi * AUBRY_C_WATER + (1 - psi) * AUBRY_C_BONE;
% ----- 3.3 Legacy:HU → 密度和声速(直接线性)-----
LEGACY_RHO_SLOPE = (RHO_MAX - RHO_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
LEGACY_RHO_INTERCEPT = RHO_MIN - LEGACY_RHO_SLOPE * HU_MAP_MIN;
LEGACY_C_SLOPE = (C_MAX - C_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
LEGACY_C_INTERCEPT = C_MIN - LEGACY_C_SLOPE * HU_MAP_MIN;
rho_legacy = LEGACY_RHO_SLOPE * hu_bone + LEGACY_RHO_INTERCEPT;
c_legacy = LEGACY_C_SLOPE * hu_bone + LEGACY_C_INTERCEPT;
matlab
clearvars;
%% ===== 1. 模拟 DICOM HU 数据 =====
Nx = 128; Ny = 128;
% 造一个模拟的 HU 图:背景(水/软组织)+ 三层颅骨
hu_img = zeros(Nx, Ny); % 水 = 0 HU
% 软组织层(脑组织,HU 约 20--40)
hu_img(1:30, :) = 30;
% 三层颅骨,HU 依次增高
hu_img(31:45, :) = 800; % 外板(皮质骨)
hu_img(46:70, :) = 400; % 板障(松质骨)
hu_img(71:85, :) = 800; % 内板(皮质骨)
% 脑组织(后半段)
hu_img(86:end, :) = 30;
% HU 范围
HU_MIN = -1000; % 空气
HU_MAX = 2000; % 致密皮质骨上限(临床 CT 常用)
HU_SKULL_MIN = 300; % 颅骨 HU 下限(常用阈值)
fprintf('HU range: [%.0f, %.0f]\n', min(hu_img(:)), max(hu_img(:)));
%% ===== 2. HU → 密度(线性标定)=====
% 两个锚点:水(0 HU, 1.0 g/cm³)和皮质骨(2000 HU, 2.0 g/cm³)
rho_water = 1.0; % g/cm³
rho_bone = 2.0; % g/cm³
% 线性标定:ρ = a * HU + b
rho_slope = (rho_bone - rho_water) / (HU_MAX - 0);
rho_intercept = rho_water;
fprintf('Calibration: rho = %.6e * HU + %.4f\n', rho_slope, rho_intercept);
%% ===== 3. 三种映射模型 =====
bone_mask = hu_img >= HU_SKULL_MIN;
hu_bone = double(hu_img(bone_mask));
HU_MAP_MIN = HU_SKULL_MIN;
HU_MAP_MAX = max(hu_img(:));
RHO_MIN = 1.0; RHO_MAX = 2.0;
C_MIN = 1500; C_MAX = 2820;
% ----- 3.1 Marsac 2017:HU → 密度 → 声速(两步线性)-----
hu_norm = (hu_bone - HU_MAP_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
hu_norm = max(0, min(1, hu_norm));
rho_marsac = RHO_MIN + (RHO_MAX - RHO_MIN) * hu_norm;
rho_norm = (rho_marsac - RHO_MIN) / (RHO_MAX - RHO_MIN);
c_marsac = C_MIN + (C_MAX - C_MIN) * rho_norm;
% ----- 3.2 Aubry 2003:孔隙率 ψ → 密度和声速(并行混合)-----
psi = 1 - hu_norm;
AUBRY_RHO_WATER = 1.0; AUBRY_RHO_BONE = 2.0;
AUBRY_C_WATER = 1500; AUBRY_C_BONE = 2820;
rho_aubry = psi * AUBRY_RHO_WATER + (1 - psi) * AUBRY_RHO_BONE;
c_aubry = psi * AUBRY_C_WATER + (1 - psi) * AUBRY_C_BONE;
% ----- 3.3 Legacy:HU → 密度和声速(直接线性)-----
LEGACY_RHO_SLOPE = (RHO_MAX - RHO_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
LEGACY_RHO_INTERCEPT = RHO_MIN - LEGACY_RHO_SLOPE * HU_MAP_MIN;
LEGACY_C_SLOPE = (C_MAX - C_MIN) / (HU_MAP_MAX - HU_MAP_MIN);
LEGACY_C_INTERCEPT = C_MIN - LEGACY_C_SLOPE * HU_MAP_MIN;
rho_legacy = LEGACY_RHO_SLOPE * hu_bone + LEGACY_RHO_INTERCEPT;
c_legacy = LEGACY_C_SLOPE * hu_bone + LEGACY_C_INTERCEPT;
%% ===== 4. 打印三种模型的范围 =====
fprintf('\n--- Model Comparison (bone region) ---\n');
fprintf('%-18s %8s %8s %8s\n', '', 'Min', 'Max', 'Mean');
fprintf('%-18s %8.0f %8.0f %8.0f m/s\n', 'Marsac c', min(c_marsac), max(c_marsac), mean(c_marsac));
fprintf('%-18s %8.0f %8.0f %8.0f m/s\n', 'Aubry c', min(c_aubry), max(c_aubry), mean(c_aubry));
fprintf('%-18s %8.0f %8.0f %8.0f m/s\n', 'Legacy c', min(c_legacy), max(c_legacy), mean(c_legacy));
fprintf('%-18s %8.2f %8.2f %8.2f g/cm3\n', 'Marsac rho', min(rho_marsac), max(rho_marsac), mean(rho_marsac));
fprintf('%-18s %8.2f %8.2f %8.2f g/cm3\n', 'Aubry rho', min(rho_aubry), max(rho_aubry), mean(rho_aubry));
fprintf('%-18s %8.2f %8.2f %8.2f g/cm3\n', 'Legacy rho', min(rho_legacy), max(rho_legacy), mean(rho_legacy));
%% ===== 5. 建 k-Wave medium(以 Marsac 为例)=====
medium.sound_speed = C_MIN * ones(Nx, Ny);
medium.density = RHO_MIN * 1000 * ones(Nx, Ny); % kg/m³
medium.alpha_coeff = 0.5 * ones(Nx, Ny); % 先给统一值
medium.alpha_power = 1.5;
medium.sound_speed(bone_mask) = c_marsac;
medium.density(bone_mask) = rho_marsac * 1000;
% ----- 5.1 补上衰减映射:分皮质/板障 -----
cortical_idx = hu_bone >= 600; % 皮质骨
diploe_idx = ~cortical_idx; % 板障
alpha_vals = zeros(size(hu_bone));
alpha_vals(cortical_idx) = 6.4; % 皮质骨 [dB/cm @ 2.25 MHz]
alpha_vals(diploe_idx) = 21.0; % 板障 [dB/cm @ 2.25 MHz]
medium.alpha_coeff(bone_mask) = alpha_vals;
fprintf('\nMedium built (Marsac + attenuation):\n');
fprintf(' c: [%.0f, %.0f] m/s\n', min(medium.sound_speed(:)), max(medium.sound_speed(:)));
fprintf(' rho: [%.0f, %.0f] kg/m3\n', min(medium.density(:)), max(medium.density(:)));
fprintf(' alpha: [%.1f, %.1f] dB/cm\n', min(medium.alpha_coeff(:)), max(medium.alpha_coeff(:)));
%% ===== 6. 出图 =====
% 图 1:HU 分布
figure;
imagesc(hu_img); colormap(gray); colorbar;
title('HU Distribution'); xlabel('y'); ylabel('x');
% 图 2:声速分布(Marsac)
figure;
c_map = C_MIN * ones(Nx, Ny);
c_map(bone_mask) = c_marsac;
imagesc(c_map); colormap(jet); colorbar;
title('Sound Speed (Marsac)'); xlabel('y'); ylabel('x'); axis image;
% 图 3:衰减分布
figure;
alpha_map = 0.5 * ones(Nx, Ny);
alpha_map(bone_mask) = alpha_vals;
imagesc(alpha_map); colormap(jet); colorbar;
title('Attenuation Coefficient'); xlabel('y'); ylabel('x'); axis image;
% 图 4:三种模型声速直方图(三子图)
figure;
subplot(1,3,1);
histogram(c_marsac, 30, 'FaceColor', 'b', 'EdgeColor', 'none');
title('Marsac'); xlabel('c [m/s]'); ylabel('Count'); grid on;
subplot(1,3,2);
histogram(c_aubry, 30, 'FaceColor', 'r', 'EdgeColor', 'none');
title('Aubry'); xlabel('c [m/s]'); ylabel('Count'); grid on;
subplot(1,3,3);
histogram(c_legacy, 30, 'FaceColor', 'g', 'EdgeColor', 'none');
title('Legacy'); xlabel('c [m/s]'); ylabel('Count'); grid on;
sgtitle('Sound Speed Distribution (bone region)');
3.4 模型对比输出
text
HU range: [30, 800]
Calibration: rho = 5.000000e-04 * HU + 1.0000
--- Model Comparison (bone region) ---
Min Max Mean
Marsac c 1764 2820 2340 m/s
Aubry c 1764 2820 2340 m/s
Legacy c 1764 2820 2340 m/s
Marsac rho 1.20 2.00 1.64 g/cm3
Aubry rho 1.20 2.00 1.64 g/cm3
Legacy rho 1.20 2.00 1.64 g/cm3
Medium built (Marsac + attenuation):
c: [1500, 2820] m/s
rho: [1000, 2000] kg/m3
alpha: [0.5, 21.0] dB/cm
数据全对 :HU [30, 800],声速 [1764, 2820] m/s,密度 [1.20, 2.00] g/cm³。
需要注意的假设 :密度映射用了归一化,HU = 800 对应密度 2.0,而不是标定公式的 1.4。这不影响仿真,但要明确假设------HU = 800 就是最致密皮质骨。
3.5 建 medium + 补上衰减映射
matlab
%% ===== 5. 建 k-Wave medium(以 Marsac 为例)=====
medium.sound_speed = C_MIN * ones(Nx, Ny);
medium.density = RHO_MIN * 1000 * ones(Nx, Ny); % kg/m³
medium.alpha_coeff = 0.5 * ones(Nx, Ny); % 先给统一值
medium.alpha_power = 1.5;
medium.sound_speed(bone_mask) = c_marsac;
medium.density(bone_mask) = rho_marsac * 1000;
% ----- 5.1 补上衰减映射:分皮质/板障 -----
cortical_idx = hu_bone >= 600; % 皮质骨
diploe_idx = ~cortical_idx; % 板障
alpha_vals = zeros(size(hu_bone));
alpha_vals(cortical_idx) = 6.4; % 皮质骨 [dB/cm @ 2.25 MHz]
alpha_vals(diploe_idx) = 21.0; % 板障 [dB/cm @ 2.25 MHz]
medium.alpha_coeff(bone_mask) = alpha_vals;
输出:
text
Medium built (Marsac + attenuation):
c: [1500, 2820] m/s
rho: [1000, 2000] kg/m3
alpha: [0.5, 21.0] dB/cm
四、出图解读
图 1:HU 分布
- 背景(水/软组织):深色(HU = 0 或 30)
- 外板(31--45 行):亮白(HU = 800)
- 板障(46--70 行):灰色(HU = 400)
- 内板(71--85 行):亮白(HU = 800)
- 脑组织(86 行之后):深色(HU = 30)
呈现典型的**"三明治"结构**,符合颅骨解剖。

图 2:声速分布(Marsac)
- 水区域:蓝色(1500 m/s)
- 外板/内板:红色(2820 m/s)
- 板障:黄绿色(约 1764 m/s)
声速呈现**"高-低-高"**的三层结构,和 HU 分布一致。板障声速明显低于皮质骨,这是相位畸变的主要来源。

图 3:衰减分布
- 水区域:蓝色(0.5 dB/cm)
- 外板/内板:黄绿色(6.4 dB/cm)
- 板障:红色(21.0 dB/cm)
板障衰减最高,是皮质骨的 3 倍多。 这意味着声波穿过板障时能量损失最大,是颅骨衰减的主要来源。

图 4:三种模型声速直方图
因为 HU 只有 3 个离散值(400、800),三种模型都映射到 2--3 个声速值。直方图上只有 2--3 根柱子,三种模型的柱子位置和高度相同。
看不出差异------这是因为数据太简单,不是模型本身的问题。真实 CT 数据 HU 连续分布时,差异才会显现。

五、检查点
检查点 1:HU 范围是否合理
matlab
fprintf('HU range: [%.0f, %.0f]\n', min(hu_img(:)), max(hu_img(:)));
应该是 [0, 800] 或类似范围。皮质骨 HU 约 800--2000,板障约 300--600。
检查点 2:声速范围是否落入 1500--2820 m/s
text
Marsac c 1500 2820 ...
声速上限应接近 2820 m/s 。如果上限只有 1847 m/s,说明 HU_MAP_MAX 或 C_MAX 设错了。
检查点 3:衰减是否分层
text
alpha: [0.5, 21.0] dB/cm
皮质骨 6.4,板障 21.0,水 0.5。应该能看到三个不同的值。
常见问题排查表
| 现象 | 可能原因 | 修复 |
|---|---|---|
| 声速上限 < 2000 | HU_MAP_MAX 太小 | 改成 2000 |
| 衰减全是 0.5 | alpha_vals 没赋给 medium | 检查 medium.alpha_coeff(bone_mask) = alpha_vals |
| bone_mask 全为 false | HU_SKULL_MIN 太高 | 打印 max(hu_img(:)) |
六、改参数看变化
实验 1:改 HU_SKULL_MIN,看骨区大小
matlab
HU_SKULL_MIN = 200; % 降低阈值
预期 :更多体素被划入骨区,bone_mask 体素数增加。
实验 2:改 HU_MAP_MAX,看声速范围
matlab
HU_MAP_MAX = 1500; % 降低上限
预期:声速上限被压低,因为归一化范围变窄。
实验 3:把 Aubry 参数改得和 Marsac 不同
matlab
AUBRY_C_WATER = 1480;
AUBRY_C_BONE = 3000;
预期:Aubry 的声速范围和 Marsac 出现差异,直方图形状不同。这是验证"三种模型是否真的用了不同参数"的有效手段。
七、背后的物理/数学(选读)
HU 的定义
text
HU = 1000 × (μ - μ_water) / μ_water
其中 μ 是 X 射线线性衰减系数。所以:
- 水:HU = 0
- 空气:HU = −1000
- 皮质骨:HU ≈ 800--2000
HU → 密度的线性标定
密度和 HU 在骨范围内近似线性:
text
ρ = 1.0 + (HU / 2000) × 1.0 (水 1.0,骨 2.0)
这是最常用的标定。更精确的做法是用电子密度模体做两点或三点标定。
三种映射模型的物理假设
| 模型 | 假设 | 适用场景 |
|---|---|---|
| Marsac 2017 | HU → 密度 → 声速,同步变化 | 有 CT 数据,追求异质性 |
| Aubry 2003 | 孔隙率决定密度和声速,独立混合 | 需要明确孔隙率参数 |
| Legacy | HU 直接线性映射 | 基线对比 |
衰减映射
衰减和密度的关系比声速更复杂。常用做法是分皮质/板障两层:
- 皮质骨:α ≈ 6.4 dB/cm @ 2.25 MHz
- 板障:α ≈ 21.0 dB/cm @ 2.25 MHz
更精细的做法是按孔隙率或密度线性插值。
八、学习心得
- HU 是"标定过的灰度":水和空气两个锚点让 HU 跨扫描仪可比,这是临床 CT 相对 micro-CT 的最大优势------不需要自己做 ABS 标定。
- 两步映射的物理链条更清晰:HU → 密度 → 声速,每一步都有明确的物理依据。直接 HU → 声速虽然也能算,但外推误差不可控。
- 衰减必须分层映射 :原代码
alpha_coeff = 0.5全区域统一是重大简化。补上分皮质/板障赋值后,板障 21 dB/cm 的高衰减才被正确表达------这是颅骨能量损失的主要来源。 - 离散 HU 数据看不出模型差异:模拟数据只有 400/800 两个值,三种模型直方图完全重合。要验证模型差异,必须用连续 HU 分布的真实 CT 数据,或像实验 3 那样人为改参数。