相位补偿学习记录:把被颅骨“打乱“的声波重新对齐

相位补偿学习记录:把被颅骨"打乱"的声波重新对齐

学习主题:直线射线追踪 vs 时间反转-相位补偿,对比补偿后焦点偏移与声压变化

涉及工具:k-Wave 2D + 三层颅骨 medium + 三种补偿策略


一、这一章要解决什么问题

颅骨让声波产生相位畸变,焦点偏移、声压降低。这一章用三种方法补偿相位:

  1. 直线射线追踪
  2. 时间反转-相位补偿
  3. 时间反转-群延时

对比补偿后焦点偏移和声压的变化。


二、最小基础知识

概念卡片 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

结果解读

  1. 倾斜颅骨让焦点在 y 方向偏移 0.7 mm(−0.10 → 0.60 mm)------相位畸变出现了!
  2. 射线追踪修正了 y 方向偏移 :y 从 0.60 回到 0.00 mm,说明射线追踪确实补偿了颅骨导致的相位差异。
  3. 但 x 方向固有偏差(约 1.6 mm)没修正 :这是几何聚焦的固有偏差(衍射),不是颅骨造成的,射线追踪无能为力。所以总误差只从 1.746 改善到 1.703,改善很小。
  4. 时间反转失败 :声压降 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
  1. y 方向偏移很小(0.1 mm) :因为射线几乎垂直穿过颅骨,各阵元的路径长度差异很小。
  2. 射线追踪没效果 :delays_ray 与 delays_geom 差异极小,补偿量可忽略。
  3. 时间反转失败:声压降 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 相位补偿

  • 相位补偿:只对齐中心频率的相位;
  • 群延时:对齐整个波包的到达时间,对宽带信号更有效。

在色散介质中,不同频率的群延时不同,纯相位补偿可能失效。

为什么均匀颅骨没有补偿空间

相位补偿的前提是不同阵元之间存在相位差 。均匀颅骨下,所有阵元穿过相同厚度的颅骨,相位差为零,补偿算法自然"空转"。这不是算法失效,而是问题本身不存在 ------这也是做补偿实验时必须先构造不对称介质的原因。


九、学习心得

  1. 补偿实验的前提是"有畸变可补" :第一次跑均匀颅骨,三种方法结果一模一样,一度以为是代码 bug。实际上是物理上就没有相位差------均匀介质是补偿算法的"零输入"。
  2. 射线追踪的补偿边界很清晰 :它只修正颅骨引入的相位差,几何聚焦的衍射偏差(x 方向 1.6 mm)它管不了。评价补偿效果时要把这两种偏差分开看,否则会误判"补偿无效"。
  3. 不对称方式决定畸变强弱 :整体倾斜 > 厚度渐变(垂直入射时)。想让射线追踪"有用武之地",要让射线斜穿颅骨。
  4. 时间反转是当前最大的待解问题 :两次实验声压都大幅下降(90% / 56%),焦点散开。可能的原因包括 p_mode='dirichlet' 的设置、信号归一化方式、翻转后源的时间窗对齐。这是下一步必须攻克的点。

相关推荐
Z21-X1 小时前
云原生运维学习笔记:ECShop全链路容器化部署与Docker底层资源隔离深度剖析
运维·笔记·学习·云原生
red_redemption1 小时前
自由学习记录(230)
学习
晓梦林6 小时前
BUUCTF-菜刀流量-MISC-学习笔记
笔记·学习
Godspeed Zhao15 小时前
从零开始学AI25——无监督学习之层次聚类
学习·机器学习·聚类
David猪大卫1 天前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习
HPtiger1 天前
C++ 入门笔记:从 C 到 C++ 的核心过渡(命名空间、输入输出、缺省参数、重载、引用、内联)
c语言·c++·学习
fengci.1 天前
CTF2MISC中等部分
开发语言·前端·学习
YueTann1 天前
mgtv5th-baseline学习
学习
蒸蒸yyyyzwd1 天前
从后端面经学到的 3 个知识
笔记·学习