从 DICOM 到声学属性学习记录:HU 值怎么变成密度和声速

从 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

更精细的做法是按孔隙率或密度线性插值。


八、学习心得

  1. HU 是"标定过的灰度":水和空气两个锚点让 HU 跨扫描仪可比,这是临床 CT 相对 micro-CT 的最大优势------不需要自己做 ABS 标定。
  2. 两步映射的物理链条更清晰:HU → 密度 → 声速,每一步都有明确的物理依据。直接 HU → 声速虽然也能算,但外推误差不可控。
  3. 衰减必须分层映射 :原代码 alpha_coeff = 0.5 全区域统一是重大简化。补上分皮质/板障赋值后,板障 21 dB/cm 的高衰减才被正确表达------这是颅骨能量损失的主要来源。
  4. 离散 HU 数据看不出模型差异:模拟数据只有 400/800 两个值,三种模型直方图完全重合。要验证模型差异,必须用连续 HU 分布的真实 CT 数据,或像实验 3 那样人为改参数。

相关推荐
自信1504130575916 分钟前
重生之从0开始学习c++之——vector-中篇
c++·学习·算法
架构师伯子明19 分钟前
ffmpeg学习4:opencv在音视频领域常用指令
opencv·学习·ffmpeg
wangqiaowq13 小时前
Ontology 学习
学习
choumou_M14 小时前
算法_1:由浅入深学习快速排序QuickSort
学习·算法·排序算法
蒸蒸yyyyzwd19 小时前
后端学习笔记 day111
笔记·学习
叼烟扛炮20 小时前
[LangChain] 学习笔记(08):工具调用底层闭环与第三方搜索(Tavily)实战
人工智能·笔记·学习·langchain
Century_Dragon20 小时前
让新能源故障诊断实训“触手可及“——仿真教学软件(VR)介绍
学习
楚山之石21 小时前
【Radiology】影像组学的标准化与可重复性:IBSI 卷积滤波器共识及其未能覆盖的环节
人工智能·学习·机器学习·mri
小李不想当小白1 天前
HAL库之UART(STM32 HAL库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式