相位补偿学习记录:把被颅骨"打乱"的声波重新对齐
学习主题:直线射线追踪 vs 时间反转-相位补偿,对比补偿后焦点偏移与声压变化
涉及工具:k-Wave 2D + 三层颅骨 medium + 三种补偿策略
一、这一章要解决什么问题
颅骨让声波产生相位畸变,焦点偏移、声压降低。这一章用三种方法补偿相位:
- 直线射线追踪
- 时间反转-相位补偿
- 时间反转-群延时
对比补偿后焦点偏移和声压的变化。
二、最小基础知识
概念卡片 1:相位补偿的本质
是什么 :每个阵元发射的声波穿过颅骨时,经历的时间延迟不同。相位补偿就是给每个阵元加一个额外的延时,抵消颅骨导致的差异,让所有声波重新同时到达焦点。
为什么需要:不补偿,声波在焦点处相位不一致,无法叠加,焦点声压低、位置偏。
不这么做会怎样:焦点偏移几毫米,声压衰减 50% 以上,经颅超声无法有效聚焦。
概念卡片 2:直线射线追踪
是什么 :假设声波沿直线从阵元传到焦点,用几何路径长度除以声速,算出每个阵元的延时。穿过颅骨时用颅骨声速,穿过水时用水声速。
为什么需要:这是最简单的补偿方法,计算量小,适合快速估算。
不这么做会怎样:如果连射线追踪都不做,延时完全按水算,焦点偏移最大。
概念卡片 3:时间反转-相位补偿 vs 群延时
是什么:
- 相位补偿 :从仿真中提取每个阵元位置接收到的声波相位,反转后作为发射信号;
- 群延时 :不只反转相位,还对齐包络的峰值时间(群延时),对宽带信号更稳健。
为什么需要:相位补偿只对齐单一频率的相位;群延时对齐整个波包的到达时间,对色散介质更有效。
不这么做会怎样:如果颅骨色散严重,纯相位补偿可能失效,焦点仍然偏移。
三、动手做:三种补偿方法的完整仿真
3.1 建网格与三层颅骨 medium
matlab
clearvars;
%% ===== 1. 建网格 =====
Nx = 256; Ny = 256;
dx = 0.1e-3; dy = 0.1e-3;
kgrid = kWaveGrid(Nx, dx, Ny, dy);
%% ===== 2. 建介质:三层颅骨 =====
c_outer = 2800; rho_outer = 2310;
c_diploe = 2200; rho_diploe = 2030;
c_inner = 2800; rho_inner = 2310;
d_outer_pts = 10; % 1.0 mm
d_diploe_pts = 30; % 3.0 mm
d_inner_pts = 10; % 1.0 mm
skull_start = 120;
medium.sound_speed = 1480 * ones(Nx, Ny);
medium.density = 1000 * ones(Nx, Ny);
medium.alpha_coeff = 0.5 * ones(Nx, Ny);
medium.alpha_power = 1.5;
medium.sound_speed(skull_start : skull_start+d_outer_pts-1, :) = c_outer;
medium.density(skull_start : skull_start+d_outer_pts-1, :) = rho_outer;
medium.sound_speed(skull_start+d_outer_pts : skull_start+d_outer_pts+d_diploe_pts-1, :) = c_diploe;
medium.density(skull_start+d_outer_pts : skull_start+d_outer_pts+d_diploe_pts-1, :) = rho_diploe;
medium.sound_speed(skull_start+d_outer_pts+d_diploe_pts : skull_start+d_outer_pts+d_diploe_pts+d_inner_pts-1, :) = c_inner;
medium.density(skull_start+d_outer_pts+d_diploe_pts : skull_start+d_outer_pts+d_diploe_pts+d_inner_pts-1, :) = rho_inner;
3.2 源、焦点与时间步
matlab
%% ===== 3. 定义源和焦点 =====
source.p_mask = zeros(Nx, Ny);
source_x = 20;
source_y = 108:10:148;
source.p_mask(source_x, source_y) = 1;
focus_x = 180; focus_y = 128;
focus_mm = [kgrid.x_vec(focus_x)*1e3, kgrid.y_vec(focus_y)*1e3];
%% ===== 4. 时间步 =====
c_max = max(medium.sound_speed(:));
cfl = 0.3;
dt = cfl * dx / (c_max * sqrt(3));
Nt = 3000;
kgrid.setTime(Nt, dt);
t_phys = (0:Nt-1) * dt;
f0 = 1e6;
n_cycles = 3;
tone_burst = toneBurst(1/dt, f0, n_cycles);
t_burst = (0:length(tone_burst)-1) * dt;
输出:
text
Focus: (5.10, -0.10) mm
dt = 6.19e-09 s, Nt = 3000, tEnd = 18.56 us
3.3 几何聚焦源 + 粗仿真找峰值
matlab
%% ===== 5. 生成几何聚焦源 =====
c0 = 1480;
delays_geom = sqrt((focus_x - source_x)^2 + (focus_y - source_y).^2) * dx / c0;
delays_geom = max(delays_geom) - delays_geom;
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
%% ===== 6. 粗仿真找峰值 =====
sensor_coarse.mask = zeros(Nx, Ny);
sensor_coarse.mask(focus_x-30 : focus_x+30, focus_y-30 : focus_y+30) = 1;
sensor_coarse.record = {'p_max'};
sensor_data_coarse = kspaceFirstOrder2D(kgrid, medium, source, sensor_coarse);
输出:
text
Coarse peak: (164, 128) = (3.50, -0.10) mm
3.4 Baseline 仿真(几何聚焦,无补偿)
matlab
%% ===== 7. 以峰值为中心建传感器 =====
roi_half = 15;
roi_x = max(1, peak_x-roi_half) : min(Nx, peak_x+roi_half);
roi_y = max(1, peak_y-roi_half) : min(Ny, peak_y+roi_half);
sensor.mask = zeros(Nx, Ny);
sensor.mask(roi_x, roi_y) = 1;
sensor.record = {'p_max', 'p_min', 'p_rms'};
%% ===== 8. Baseline 仿真 =====
sensor_data_base = kspaceFirstOrder2D(kgrid, medium, source, sensor);
3.5 方法一:直线射线追踪
matlab
%% ===== 9. 射线追踪 =====
delays_ray = zeros(size(source_y));
for i = 1:length(source_y)
y_line = linspace(source_y(i), focus_y, 100);
x_line = linspace(source_x, focus_x, 100);
t_total = 0;
for j = 1:99
x_mid = round((x_line(j) + x_line(j+1)) / 2);
y_mid = round((y_line(j) + y_line(j+1)) / 2);
x_mid = max(1, min(Nx, x_mid));
y_mid = max(1, min(Ny, y_mid));
c_local = medium.sound_speed(x_mid, y_mid);
d_seg = sqrt((x_line(j+1)-x_line(j))^2 + (y_line(j+1)-y_line(j))^2) * dx;
t_total = t_total + d_seg / c_local;
end
delays_ray(i) = t_total;
end
delays_ray = max(delays_ray) - delays_ray;
输出(均匀颅骨):
text
delays_geom: 0.0000 0.0630 0.0841 0.0630 0.0000 us
delays_ray: 0.0000 0.0554 0.0739 0.0554 0.0000 us
观察 :射线追踪算出的延时比几何延时更小,因为颅骨声速高,穿过颅骨的时间比按水算的更短。但两者差异只有约 0.01 µs 量级。
3.6 方法二:时间反转
matlab
%% ===== 10. 时间反转 =====
% 正向仿真,记录阵元位置波形
sensor_tr.mask = zeros(Nx, Ny);
sensor_tr.mask(source_x, source_y) = 1;
sensor_tr.record = {'p'};
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
sensor_data_tr = kspaceFirstOrder2D(kgrid, medium, source, sensor_tr);
% 时间翻转,作为新的源
source_tr.p_mask = sensor_tr.mask;
source_tr.p = flip(sensor_data_tr.p, 2);
source_tr.p = source_tr.p / max(abs(source_tr.p(:))) * 1e4;
source_tr.p_mode = 'dirichlet';
sensor_data_tr_focus = kspaceFirstOrder2D(kgrid, medium, source_tr, sensor);
3.7 后处理:提取焦点
matlab
%% ===== 11. 后处理 =====
function [peak_mm, peak_val, focus_err] = extract_focus(sensor_data, sensor, kgrid, focus_mm, Nx, Ny)
p_max_full = zeros(Nx, Ny);
mask_linear = find(sensor.mask);
if isfield(sensor_data, 'p_max')
p_data = sensor_data.p_max;
elseif isfield(sensor_data, 'p')
p_data = max(abs(sensor_data.p), [], 2);
end
p_max_full(mask_linear) = p_data(:);
[peak_val, peak_idx] = max(p_max_full(:));
[ix, iy] = ind2sub([Nx, Ny], peak_idx);
peak_mm = [kgrid.x_vec(ix)*1e3, kgrid.y_vec(iy)*1e3];
focus_err = norm(peak_mm - focus_mm);
end
四、第一次结果:均匀颅骨------补偿"无效"
text
--- Focus Comparison ---
Method Peak [mm] Pressure [Pa] Error [mm]
Baseline ( 3.50,-0.10) 1.60e+03 1.600
Ray tracing ( 3.50,-0.10) 1.60e+03 1.600
Time reversal ( 3.40,-0.10) 7.22e+02 1.700
三种方法几乎一样,焦点还是偏 1.6 mm。
为什么补偿没效果?
根本原因:颅骨沿 y 方向完全均匀。
5 个阵元的射线在颅骨内穿过的厚度完全相同 → 相位延迟完全相同 → 没有需要补偿的相位差 。射线追踪算出的延时虽然数值变了,但各阵元之间的相对差异极小,不足以改变焦点位置。
关键教训 :均匀介质没有相位畸变可补偿 。想看补偿效果,必须让颅骨不对称------厚度沿 y 渐变,或整体倾斜。
时间反转声压偏低(7.22e+02 vs 1.60e+03 Pa) 也提示需要检查信号幅度和 p_mode 设置。
五、不对称实验一:颅骨整体倾斜
matlab
clearvars;
%% ===== 1. 建网格 =====
Nx = 256; Ny = 256;
dx = 0.1e-3; dy = 0.1e-3;
kgrid = kWaveGrid(Nx, dx, Ny, dy);
fprintf('Grid: %d x %d, dx = %.2f mm\n', Nx, Ny, dx*1e3);
%% ===== 2. 建介质:三层颅骨(整体倾斜)=====
c_outer = 2800; rho_outer = 2310;
c_diploe = 2200; rho_diploe = 2030;
c_inner = 2800; rho_inner = 2310;
d_outer_pts = 10; % 1.0 mm
d_diploe_pts = 30; % 3.0 mm
d_inner_pts = 10; % 1.0 mm
skull_start = 120;
tilt_slope = 0.3; % 倾斜斜率
medium.sound_speed = 1480 * ones(Nx, Ny);
medium.density = 1000 * ones(Nx, Ny);
medium.alpha_coeff = 0.5 * ones(Nx, Ny);
medium.alpha_power = 1.5;
for j = 1:Ny
% 每个 y 的颅骨起始位置不同,形成倾斜
offset = round(tilt_slope * (j - Ny/2));
x_start = skull_start + offset;
% 外板
idx1 = x_start : x_start + d_outer_pts - 1;
idx1 = idx1(idx1 >= 1 & idx1 <= Nx);
medium.sound_speed(idx1, j) = c_outer;
medium.density(idx1, j) = rho_outer;
% 板障
idx2 = x_start + d_outer_pts : x_start + d_outer_pts + d_diploe_pts - 1;
idx2 = idx2(idx2 >= 1 & idx2 <= Nx);
medium.sound_speed(idx2, j) = c_diploe;
medium.density(idx2, j) = rho_diploe;
% 内板
idx3 = x_start + d_outer_pts + d_diploe_pts : ...
x_start + d_outer_pts + d_diploe_pts + d_inner_pts - 1;
idx3 = idx3(idx3 >= 1 & idx3 <= Nx);
medium.sound_speed(idx3, j) = c_inner;
medium.density(idx3, j) = rho_inner;
end
% 画图确认颅骨倾斜
figure;
imagesc(kgrid.y_vec*1e3, kgrid.x_vec*1e3, medium.sound_speed);
colormap(jet); colorbar;
set(gca, 'YDir', 'normal'); axis image;
xlabel('y [mm]'); ylabel('x [mm]');
title('Tilted Skull: Sound Speed Distribution');
%% ===== 3. 定义源和焦点 =====
source.p_mask = zeros(Nx, Ny);
source_x = 20;
source_y = 108:10:148;
source.p_mask(source_x, source_y) = 1;
focus_x = 180; focus_y = 128;
focus_mm = [kgrid.x_vec(focus_x)*1e3, kgrid.y_vec(focus_y)*1e3];
fprintf('Focus: (%.2f, %.2f) mm\n', focus_mm(1), focus_mm(2));
%% ===== 4. 时间步 =====
c_max = max(medium.sound_speed(:));
cfl = 0.3;
dt = cfl * dx / (c_max * sqrt(3));
Nt = 3000;
kgrid.setTime(Nt, dt);
t_phys = (0:Nt-1) * dt;
f0 = 1e6;
n_cycles = 3;
tone_burst = toneBurst(1/dt, f0, n_cycles);
t_burst = (0:length(tone_burst)-1) * dt;
fprintf('dt = %.2e s, Nt = %d, tEnd = %.2f us\n', dt, Nt, Nt*dt*1e6);
%% ===== 5. 生成几何聚焦源 =====
c0 = 1480;
delays_geom = sqrt((focus_x - source_x)^2 + (focus_y - source_y).^2) * dx / c0;
delays_geom = max(delays_geom) - delays_geom;
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
%% ===== 6. 粗仿真找峰值 =====
sensor_coarse.mask = zeros(Nx, Ny);
sensor_coarse.mask(focus_x-30 : focus_x+30, focus_y-30 : focus_y+30) = 1;
sensor_coarse.record = {'p_max'};
fprintf('Running coarse simulation...\n');
sensor_data_coarse = kspaceFirstOrder2D(kgrid, medium, source, sensor_coarse);
p_coarse = zeros(Nx, Ny);
p_coarse(find(sensor_coarse.mask)) = sensor_data_coarse.p_max(:);
[~, idx] = max(p_coarse(:));
[peak_x, peak_y] = ind2sub([Nx, Ny], idx);
fprintf('Coarse peak: (%d, %d) = (%.2f, %.2f) mm\n', ...
peak_x, peak_y, kgrid.x_vec(peak_x)*1e3, kgrid.y_vec(peak_y)*1e3);
%% ===== 7. 以峰值为中心建传感器 =====
roi_half = 15;
roi_x = max(1, peak_x-roi_half) : min(Nx, peak_x+roi_half);
roi_y = max(1, peak_y-roi_half) : min(Ny, peak_y+roi_half);
sensor.mask = zeros(Nx, Ny);
sensor.mask(roi_x, roi_y) = 1;
sensor.record = {'p_max', 'p_min', 'p_rms'};
fprintf('Sensor ROI: [%d, %d] x [%d, %d], nnz = %d\n', ...
roi_x(1), roi_x(end), roi_y(1), roi_y(end), nnz(sensor.mask));
%% ===== 8. Baseline 仿真 =====
fprintf('\nRunning baseline (geometric focus)...\n');
sensor_data_base = kspaceFirstOrder2D(kgrid, medium, source, sensor);
%% ===== 9. 射线追踪 =====
delays_ray = zeros(size(source_y));
for i = 1:length(source_y)
y_line = linspace(source_y(i), focus_y, 100);
x_line = linspace(source_x, focus_x, 100);
t_total = 0;
for j = 1:99
x_mid = round((x_line(j) + x_line(j+1)) / 2);
y_mid = round((y_line(j) + y_line(j+1)) / 2);
x_mid = max(1, min(Nx, x_mid));
y_mid = max(1, min(Ny, y_mid));
c_local = medium.sound_speed(x_mid, y_mid);
d_seg = sqrt((x_line(j+1)-x_line(j))^2 + (y_line(j+1)-y_line(j))^2) * dx;
t_total = t_total + d_seg / c_local;
end
delays_ray(i) = t_total;
end
delays_ray = max(delays_ray) - delays_ray;
fprintf('delays_geom: %.4f %.4f %.4f %.4f %.4f us\n', delays_geom*1e6);
fprintf('delays_ray: %.4f %.4f %.4f %.4f %.4f us\n', delays_ray*1e6);
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_ray(i), 'linear', 0);
end
source.p = 1e4 * source.p;
fprintf('Running ray tracing compensation...\n');
sensor_data_ray = kspaceFirstOrder2D(kgrid, medium, source, sensor);
%% ===== 10. 时间反转 =====
sensor_tr.mask = zeros(Nx, Ny);
sensor_tr.mask(source_x, source_y) = 1;
sensor_tr.record = {'p'};
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
fprintf('Running forward simulation for time reversal...\n');
sensor_data_tr = kspaceFirstOrder2D(kgrid, medium, source, sensor_tr);
source_tr.p_mask = sensor_tr.mask;
source_tr.p = flip(sensor_data_tr.p, 2);
source_tr.p = source_tr.p / max(abs(source_tr.p(:))) * 1e4;
source_tr.p_mode = 'dirichlet';
fprintf('Running time reversal compensation...\n');
sensor_data_tr_focus = kspaceFirstOrder2D(kgrid, medium, source_tr, sensor);
%% ===== 11. 后处理 =====
function [peak_mm, peak_val, focus_err] = extract_focus(sensor_data, sensor, kgrid, focus_mm, Nx, Ny)
p_max_full = zeros(Nx, Ny);
mask_linear = find(sensor.mask);
if isfield(sensor_data, 'p_max')
p_data = sensor_data.p_max;
elseif isfield(sensor_data, 'p')
p_data = max(abs(sensor_data.p), [], 2);
else
error('sensor_data 里既没有 p_max 也没有 p');
end
p_max_full(mask_linear) = p_data(:);
[peak_val, peak_idx] = max(p_max_full(:));
[ix, iy] = ind2sub([Nx, Ny], peak_idx);
fprintf('Global peak: (%d, %d) = (%.2f, %.2f) mm, %.2e Pa\n', ...
ix, iy, kgrid.x_vec(ix)*1e3, kgrid.y_vec(iy)*1e3, p_max_full(ix, iy));
peak_mm = [kgrid.x_vec(ix)*1e3, kgrid.y_vec(iy)*1e3];
focus_err = norm(peak_mm - focus_mm);
end
[peak_base, val_base, err_base] = extract_focus(sensor_data_base, sensor, kgrid, focus_mm, Nx, Ny);
[peak_ray, val_ray, err_ray] = extract_focus(sensor_data_ray, sensor, kgrid, focus_mm, Nx, Ny);
[peak_tr, val_tr, err_tr] = extract_focus(sensor_data_tr_focus, sensor, kgrid, focus_mm, Nx, Ny);
fprintf('\n--- Focus Comparison ---\n');
fprintf('%-18s %14s %14s %10s\n', 'Method', 'Peak [mm]', 'Pressure [Pa]', 'Error [mm]');
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Baseline', peak_base(1), peak_base(2), val_base, err_base);
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Ray tracing', peak_ray(1), peak_ray(2), val_ray, err_ray);
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Time reversal',peak_tr(1), peak_tr(2), val_tr, err_tr);
%% ===== 12. 出图 =====
figure;
subplot(1,3,1);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_base.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Baseline'); xlabel('y [mm]'); ylabel('x [mm]');
subplot(1,3,2);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_ray.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Ray tracing'); xlabel('y [mm]'); ylabel('x [mm]');
subplot(1,3,3);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_tr_focus.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Time reversal'); xlabel('y [mm]'); ylabel('x [mm]');
sgtitle('Focus Comparison: Tilted Skull');
matlab
%% ===== 2. 建介质:三层颅骨(整体倾斜)=====
tilt_slope = 0.3; % 倾斜斜率
for j = 1:Ny
offset = round(tilt_slope * (j - Ny/2));
x_start = skull_start + offset;
idx1 = x_start : x_start + d_outer_pts - 1;
idx1 = idx1(idx1 >= 1 & idx1 <= Nx);
medium.sound_speed(idx1, j) = c_outer;
medium.density(idx1, j) = rho_outer;
% 板障、内板同理...
end
结果
text
Coarse peak: (164, 135) = (3.50, 0.60) mm
delays_geom: 0.0000 0.0630 0.0841 0.0630 0.0000 us
delays_ray: 0.1398 0.1431 0.1258 0.1073 0.0000 us
--- Focus Comparison ---
Method Peak [mm] Pressure [Pa] Error [mm]
Baseline ( 3.50, 0.60) 1.26e+03 1.746
Ray tracing ( 3.40, 0.00) 1.32e+03 1.703
Time reversal ( 2.90,-0.90) 1.29e+02 2.341
结果解读


- 倾斜颅骨让焦点在 y 方向偏移 0.7 mm(−0.10 → 0.60 mm)------相位畸变出现了!
- 射线追踪修正了 y 方向偏移 :y 从 0.60 回到 0.00 mm,说明射线追踪确实补偿了颅骨导致的相位差异。
- 但 x 方向固有偏差(约 1.6 mm)没修正 :这是几何聚焦的固有偏差(衍射),不是颅骨造成的,射线追踪无能为力。所以总误差只从 1.746 改善到 1.703,改善很小。
- 时间反转失败 :声压降 90%(1.26e+03 → 1.29e+02 Pa),焦点散开(误差 2.341 mm)。需要排查
p_mode、信号幅度归一化、翻转方向等问题。
核心结论 :射线追踪能修正颅骨导致的相位差异,但不能修正几何聚焦的固有偏差。
六、不对称实验二:颅骨厚度沿 y 线性渐变
matlab
clearvars;
%% ===== 1. 建网格 =====
Nx = 256; Ny = 256;
dx = 0.1e-3; dy = 0.1e-3;
kgrid = kWaveGrid(Nx, dx, Ny, dy);
fprintf('Grid: %d x %d, dx = %.2f mm\n', Nx, Ny, dx*1e3);
%% ===== 2. 建介质:三层颅骨(厚度沿 y 线性渐变)=====
c_outer = 2800; rho_outer = 2310;
c_diploe = 2200; rho_diploe = 2030;
c_inner = 2800; rho_inner = 2310;
% 基准厚度(网格点数)
d_outer_base = 10; % 1.0 mm
d_diploe_base = 30; % 3.0 mm
d_inner_base = 10; % 1.0 mm
skull_start = 120;
gradient = 0.5; % 渐变强度:y 越大,颅骨越厚
medium.sound_speed = 1480 * ones(Nx, Ny);
medium.density = 1000 * ones(Nx, Ny);
medium.alpha_coeff = 0.5 * ones(Nx, Ny);
medium.alpha_power = 1.5;
for j = 1:Ny
% y 越大,厚度越大
factor = 1 + gradient * (j - Ny/2) / (Ny/2);
d_outer_j = max(1, round(d_outer_base * factor));
d_diploe_j = max(1, round(d_diploe_base * factor));
d_inner_j = max(1, round(d_inner_base * factor));
% 外板
idx1 = skull_start : skull_start + d_outer_j - 1;
idx1 = idx1(idx1 >= 1 & idx1 <= Nx);
medium.sound_speed(idx1, j) = c_outer;
medium.density(idx1, j) = rho_outer;
% 板障
idx2 = skull_start + d_outer_j : skull_start + d_outer_j + d_diploe_j - 1;
idx2 = idx2(idx2 >= 1 & idx2 <= Nx);
medium.sound_speed(idx2, j) = c_diploe;
medium.density(idx2, j) = rho_diploe;
% 内板
idx3 = skull_start + d_outer_j + d_diploe_j : ...
skull_start + d_outer_j + d_diploe_j + d_inner_j - 1;
idx3 = idx3(idx3 >= 1 & idx3 <= Nx);
medium.sound_speed(idx3, j) = c_inner;
medium.density(idx3, j) = rho_inner;
end
% 画图确认颅骨厚度渐变
figure;
imagesc(kgrid.y_vec*1e3, kgrid.x_vec*1e3, medium.sound_speed);
colormap(jet); colorbar;
set(gca, 'YDir', 'normal'); axis image;
xlabel('y [mm]'); ylabel('x [mm]');
title('Graded Skull: Thickness Varies Linearly with y');
%% ===== 3. 定义源和焦点 =====
source.p_mask = zeros(Nx, Ny);
source_x = 20;
source_y = 108:10:148;
source.p_mask(source_x, source_y) = 1;
focus_x = 180; focus_y = 128;
focus_mm = [kgrid.x_vec(focus_x)*1e3, kgrid.y_vec(focus_y)*1e3];
fprintf('Focus: (%.2f, %.2f) mm\n', focus_mm(1), focus_mm(2));
%% ===== 4. 时间步 =====
c_max = max(medium.sound_speed(:));
cfl = 0.3;
dt = cfl * dx / (c_max * sqrt(3));
Nt = 3000;
kgrid.setTime(Nt, dt);
t_phys = (0:Nt-1) * dt;
f0 = 1e6;
n_cycles = 3;
tone_burst = toneBurst(1/dt, f0, n_cycles);
t_burst = (0:length(tone_burst)-1) * dt;
fprintf('dt = %.2e s, Nt = %d, tEnd = %.2f us\n', dt, Nt, Nt*dt*1e6);
%% ===== 5. 生成几何聚焦源 =====
c0 = 1480;
delays_geom = sqrt((focus_x - source_x)^2 + (focus_y - source_y).^2) * dx / c0;
delays_geom = max(delays_geom) - delays_geom;
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
%% ===== 6. 粗仿真找峰值 =====
sensor_coarse.mask = zeros(Nx, Ny);
sensor_coarse.mask(focus_x-30 : focus_x+30, focus_y-30 : focus_y+30) = 1;
sensor_coarse.record = {'p_max'};
fprintf('Running coarse simulation...\n');
sensor_data_coarse = kspaceFirstOrder2D(kgrid, medium, source, sensor_coarse);
p_coarse = zeros(Nx, Ny);
p_coarse(find(sensor_coarse.mask)) = sensor_data_coarse.p_max(:);
[~, idx] = max(p_coarse(:));
[peak_x, peak_y] = ind2sub([Nx, Ny], idx);
fprintf('Coarse peak: (%d, %d) = (%.2f, %.2f) mm\n', ...
peak_x, peak_y, kgrid.x_vec(peak_x)*1e3, kgrid.y_vec(peak_y)*1e3);
%% ===== 7. 以峰值为中心建传感器 =====
roi_half = 15;
roi_x = max(1, peak_x-roi_half) : min(Nx, peak_x+roi_half);
roi_y = max(1, peak_y-roi_half) : min(Ny, peak_y+roi_half);
sensor.mask = zeros(Nx, Ny);
sensor.mask(roi_x, roi_y) = 1;
sensor.record = {'p_max', 'p_min', 'p_rms'};
fprintf('Sensor ROI: [%d, %d] x [%d, %d], nnz = %d\n', ...
roi_x(1), roi_x(end), roi_y(1), roi_y(end), nnz(sensor.mask));
%% ===== 8. Baseline 仿真 =====
fprintf('\nRunning baseline (geometric focus)...\n');
sensor_data_base = kspaceFirstOrder2D(kgrid, medium, source, sensor);
%% ===== 9. 射线追踪 =====
delays_ray = zeros(size(source_y));
for i = 1:length(source_y)
y_line = linspace(source_y(i), focus_y, 100);
x_line = linspace(source_x, focus_x, 100);
t_total = 0;
for j = 1:99
x_mid = round((x_line(j) + x_line(j+1)) / 2);
y_mid = round((y_line(j) + y_line(j+1)) / 2);
x_mid = max(1, min(Nx, x_mid));
y_mid = max(1, min(Ny, y_mid));
c_local = medium.sound_speed(x_mid, y_mid);
d_seg = sqrt((x_line(j+1)-x_line(j))^2 + (y_line(j+1)-y_line(j))^2) * dx;
t_total = t_total + d_seg / c_local;
end
delays_ray(i) = t_total;
end
delays_ray = max(delays_ray) - delays_ray;
fprintf('delays_geom: %.4f %.4f %.4f %.4f %.4f us\n', delays_geom*1e6);
fprintf('delays_ray: %.4f %.4f %.4f %.4f %.4f us\n', delays_ray*1e6);
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_ray(i), 'linear', 0);
end
source.p = 1e4 * source.p;
fprintf('Running ray tracing compensation...\n');
sensor_data_ray = kspaceFirstOrder2D(kgrid, medium, source, sensor);
%% ===== 10. 时间反转 =====
% 正向仿真,记录阵元位置波形
sensor_tr.mask = zeros(Nx, Ny);
sensor_tr.mask(source_x, source_y) = 1;
sensor_tr.record = {'p'};
source.p = zeros(length(source_y), Nt);
for i = 1:length(source_y)
source.p(i, :) = interp1(t_burst, tone_burst, t_phys - delays_geom(i), 'linear', 0);
end
source.p = 1e4 * source.p;
fprintf('Running forward simulation for time reversal...\n');
sensor_data_tr = kspaceFirstOrder2D(kgrid, medium, source, sensor_tr);
% 时间翻转,作为新的源
source_tr.p_mask = sensor_tr.mask;
source_tr.p = flip(sensor_data_tr.p, 2);
source_tr.p = source_tr.p / max(abs(source_tr.p(:))) * 1e4;
source_tr.p_mode = 'dirichlet';
fprintf('Running time reversal compensation...\n');
sensor_data_tr_focus = kspaceFirstOrder2D(kgrid, medium, source_tr, sensor);
%% ===== 11. 后处理 =====
function [peak_mm, peak_val, focus_err] = extract_focus(sensor_data, sensor, kgrid, focus_mm, Nx, Ny)
p_max_full = zeros(Nx, Ny);
mask_linear = find(sensor.mask);
if isfield(sensor_data, 'p_max')
p_data = sensor_data.p_max;
elseif isfield(sensor_data, 'p')
p_data = max(abs(sensor_data.p), [], 2);
else
error('sensor_data 里既没有 p_max 也没有 p');
end
p_max_full(mask_linear) = p_data(:);
[peak_val, peak_idx] = max(p_max_full(:));
[ix, iy] = ind2sub([Nx, Ny], peak_idx);
fprintf('Global peak: (%d, %d) = (%.2f, %.2f) mm, %.2e Pa\n', ...
ix, iy, kgrid.x_vec(ix)*1e3, kgrid.y_vec(iy)*1e3, p_max_full(ix, iy));
peak_mm = [kgrid.x_vec(ix)*1e3, kgrid.y_vec(iy)*1e3];
focus_err = norm(peak_mm - focus_mm);
end
[peak_base, val_base, err_base] = extract_focus(sensor_data_base, sensor, kgrid, focus_mm, Nx, Ny);
[peak_ray, val_ray, err_ray] = extract_focus(sensor_data_ray, sensor, kgrid, focus_mm, Nx, Ny);
[peak_tr, val_tr, err_tr] = extract_focus(sensor_data_tr_focus, sensor, kgrid, focus_mm, Nx, Ny);
fprintf('\n--- Focus Comparison ---\n');
fprintf('%-18s %14s %14s %10s\n', 'Method', 'Peak [mm]', 'Pressure [Pa]', 'Error [mm]');
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Baseline', peak_base(1), peak_base(2), val_base, err_base);
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Ray tracing', peak_ray(1), peak_ray(2), val_ray, err_ray);
fprintf('%-18s (%5.2f,%5.2f) %14.2e %10.3f\n', 'Time reversal',peak_tr(1), peak_tr(2), val_tr, err_tr);
%% ===== 12. 出图 =====
figure;
subplot(1,3,1);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_base.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Baseline'); xlabel('y [mm]'); ylabel('x [mm]');
subplot(1,3,2);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_ray.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Ray tracing'); xlabel('y [mm]'); ylabel('x [mm]');
subplot(1,3,3);
p_map = zeros(Nx, Ny);
p_map(find(sensor.mask)) = sensor_data_tr_focus.p_max(:);
imagesc(kgrid.y_vec(roi_y)*1e3, kgrid.x_vec(roi_x)*1e3, p_map(roi_x, roi_y));
set(gca, 'YDir', 'normal'); axis image; colorbar;
title('Time reversal'); xlabel('y [mm]'); ylabel('x [mm]');
sgtitle('Focus Comparison: Graded Skull (Thickness Varies with y)');
matlab
%% ===== 2. 建介质:三层颅骨(厚度沿 y 线性渐变)=====
gradient = 0.5; % 渐变强度:y 越大,颅骨越厚
for j = 1:Ny
factor = 1 + gradient * (j - Ny/2) / (Ny/2);
d_outer_j = max(1, round(d_outer_pts * factor));
d_diploe_j = max(1, round(d_diploe_pts * factor));
d_inner_j = max(1, round(d_inner_pts * factor));
% 按 d_*_j 逐列填充三层...
end
结果
text
Coarse peak: (163, 127) = (3.40, -0.20) mm
delays_geom: 0.0000 0.0630 0.0841 0.0630 0.0000 us
delays_ray: 0.0000 0.0554 0.0739 0.0554 0.0000 us
--- Focus Comparison ---
Method Peak [mm] Pressure [Pa] Error [mm]
Baseline ( 3.40,-0.20) 1.68e+03 1.703
Ray tracing ( 3.40,-0.20) 1.68e+03 1.703
Time reversal ( 3.30,-0.30) 7.36e+02 1.811
结果解读
matlab
Grid: 256 x 256, dx = 0.10 mm
Focus: (5.10, -0.10) mm
dt = 6.19e-09 s, Nt = 3000, tEnd = 18.56 us
Running coarse simulation...
Running k-Wave simulation...
start time: 2026-10-08 17:37:39
reference sound speed: 2800m/s
dt: 6.1859ns, t_end: 18.5515us, time steps: 3000
input grid size: 256 by 256 grid points (25.6 by 25.6mm)
maximum supported frequency: 7.4MHz
precomputation completed in 0.64979s
starting time loop...
estimated simulation time 38.6425s...
simulation completed in 39.4375s
total computation time 40.1394s
Coarse peak: (163, 127) = (3.40, -0.20) mm
Sensor ROI: [148, 178] x [112, 142], nnz = 961
Running baseline (geometric focus)...
Running k-Wave simulation...
start time: 2026-10-08 17:38:19
reference sound speed: 2800m/s
dt: 6.1859ns, t_end: 18.5515us, time steps: 3000
input grid size: 256 by 256 grid points (25.6 by 25.6mm)
maximum supported frequency: 7.4MHz
precomputation completed in 0.31159s
starting time loop...
estimated simulation time 31.9581s...
simulation completed in 32.1219s
total computation time 32.4953s
delays_geom: 0.0000 0.0630 0.0841 0.0630 0.0000 us
delays_ray: 0.0000 0.0554 0.0739 0.0554 0.0000 us
Running ray tracing compensation...
Running k-Wave simulation...
start time: 2026-10-08 17:38:52
reference sound speed: 2800m/s
dt: 6.1859ns, t_end: 18.5515us, time steps: 3000
input grid size: 256 by 256 grid points (25.6 by 25.6mm)
maximum supported frequency: 7.4MHz
precomputation completed in 0.32121s
starting time loop...
estimated simulation time 31.9848s...
simulation completed in 32.376s
total computation time 32.7461s
Running forward simulation for time reversal...
Running k-Wave simulation...
start time: 2026-10-08 17:39:25
reference sound speed: 2800m/s
dt: 6.1859ns, t_end: 18.5515us, time steps: 3000
input grid size: 256 by 256 grid points (25.6 by 25.6mm)
maximum supported frequency: 7.4MHz
precomputation completed in 0.28192s
starting time loop...
estimated simulation time 29.9066s...
simulation completed in 29.0337s
total computation time 29.3612s
Running time reversal compensation...
Running k-Wave simulation...
start time: 2026-10-08 17:39:54
reference sound speed: 2800m/s
dt: 6.1859ns, t_end: 18.5515us, time steps: 3000
input grid size: 256 by 256 grid points (25.6 by 25.6mm)
maximum supported frequency: 7.4MHz
precomputation completed in 0.30774s
starting time loop...
estimated simulation time 24.991s...
simulation completed in 28.6042s
total computation time 28.9641s
Global peak: (163, 127) = (3.40, -0.20) mm, 1.68e+03 Pa
Global peak: (163, 127) = (3.40, -0.20) mm, 1.68e+03 Pa
Global peak: (162, 126) = (3.30, -0.30) mm, 7.36e+02 Pa
--- Focus Comparison ---
Method Peak [mm] Pressure [Pa] Error [mm]
Baseline ( 3.40,-0.20) 1.68e+03 1.703
Ray tracing ( 3.40,-0.20) 1.68e+03 1.703
Time reversal ( 3.30,-0.30) 7.36e+02 1.811


- y 方向偏移很小(0.1 mm) :因为射线几乎垂直穿过颅骨,各阵元的路径长度差异很小。
- 射线追踪没效果 :
delays_ray与delays_geom差异极小,补偿量可忽略。 - 时间反转失败:声压降 56%(1.68e+03 → 7.36e+02 Pa)。
核心结论 :厚度渐变对垂直入射的射线影响小。 想让补偿生效,需要让射线斜穿颅骨------倾斜颅骨、弧形阵元、或焦点偏离轴线。
七、两种不对称实验对比
| 不对称方式 | y 偏移 | 射线追踪效果 | 时间反转 |
|---|---|---|---|
| 整体倾斜 | 0.7 mm | y 修正回 0.00,总误差改善有限 | 声压降 90%,失败 |
| 厚度渐变 | 0.1 mm | 几乎无效果 | 声压降 56%,失败 |
两条实验共同揭示的规律:
- 相位畸变的大小取决于射线穿过颅骨的路径差异。整体倾斜让各阵元路径长度差异大,畸变明显;厚度渐变在垂直入射时路径差异小,畸变弱。
- 射线追踪只补偿"颅骨引入的"相位差,对几何聚焦固有的衍射偏差(x 方向约 1.6 mm)无能为力。
- 时间反转在当前设置下不work,声压大幅下降,需要单独调试。
八、背后的物理/数学(选读)
直线射线追踪的公式
声波从阵元到焦点的总时间:
text
t = Σ (d_seg / c_local)
其中 d_seg 是每段路径的长度,c_local 是该段的局部声速。穿过颅骨时 c_local 高,穿过水时 c_local 低。
时间反转的原理
时间反转基于互易性 :如果声波从焦点传到阵元,那么把接收到的信号时间翻转后从阵元发射,声波会原路返回焦点。
数学上:p_tr(t) = p(T - t),其中 T 是总时长。时间翻转后,所有频率成分的相位被共轭,畸变被抵消。
群延时 vs 相位补偿
- 相位补偿:只对齐中心频率的相位;
- 群延时:对齐整个波包的到达时间,对宽带信号更有效。
在色散介质中,不同频率的群延时不同,纯相位补偿可能失效。
为什么均匀颅骨没有补偿空间
相位补偿的前提是不同阵元之间存在相位差 。均匀颅骨下,所有阵元穿过相同厚度的颅骨,相位差为零,补偿算法自然"空转"。这不是算法失效,而是问题本身不存在 ------这也是做补偿实验时必须先构造不对称介质的原因。
九、学习心得
- 补偿实验的前提是"有畸变可补" :第一次跑均匀颅骨,三种方法结果一模一样,一度以为是代码 bug。实际上是物理上就没有相位差------均匀介质是补偿算法的"零输入"。
- 射线追踪的补偿边界很清晰 :它只修正颅骨引入的相位差,几何聚焦的衍射偏差(x 方向 1.6 mm)它管不了。评价补偿效果时要把这两种偏差分开看,否则会误判"补偿无效"。
- 不对称方式决定畸变强弱 :整体倾斜 > 厚度渐变(垂直入射时)。想让射线追踪"有用武之地",要让射线斜穿颅骨。
- 时间反转是当前最大的待解问题 :两次实验声压都大幅下降(90% / 56%),焦点散开。可能的原因包括
p_mode='dirichlet'的设置、信号归一化方式、翻转后源的时间窗对齐。这是下一步必须攻克的点。