简介:压缩包总大小仅 4KB,包含两个 Python 文件,是一份面向超声相控阵及全聚焦成像初学者的精简代码示例,聚焦于全聚焦方法(TFM)的信号处理与可视化流程。其中一个脚本围绕 TFM 的核心计算,演示多阵元激励、回波信号接收与延时叠加逐点成像;另一个脚本提供简单的图形界面,便于调整阵元参数并观察成像响应。这一实现虽小,却覆盖了相控阵成像中关键的聚焦计算与交互验证环节,适合无损检测、医疗超声等方向的学生或工程师快速建立算法概念,也可作为课程设计、算法对比或项目起步的参考模板。借助这份代码,读者可以理解 TFM 与普通相控阵聚焦在计算思路上的差异,进而延伸到管道检测、结构探伤等工业场景中的应用;脚本结构直接,易于阅读、调试和二次修改。目前已有 258 人学习浏览,具有一定参考价值;若结合超声无损检测背景,还能进一步体会阵元延时控制对成像分辨率的实际影响。
1. 超声相控阵成像的终局解法:total-focusing-method 为什么把分辨率推到物理极限
焊缝检测里最怕的一种情况是:常规相控阵扇形扫描找到一处指示,C 扫描显示是 elongated 的强反射,但换两个角度再看,它一会儿像未熔合,一会儿又像夹渣。返修切开后发现是 2mm 的气孔——问题不在探头,而在成像算法本身。常规相控阵成像的聚焦是发射固定焦点、接收动态聚焦,一个声束扫过去,只有焦点附近的区域真的被"看清楚了",其余位置靠的是声束宽度硬撑。total-focusing-method(TFM,全聚焦法)改变的是整个成像哲学:所有阵元轮流发射,所有阵元同时接收,得到 N×N 全矩阵数据后,对成像区域内每个像素点单独计算声程、逐点聚焦。每个像素都是全场动态聚焦的结果,分辨率接近物理孔径的极限。这篇博文把 TFM 的算法表达、采集参数边界、工程落地的坑和验证方法一次讲透,适合正在评估相控阵成像升级方案或者手头有全矩阵采集数据但不知道如何处理的人。
2. TFM 的核心算法表达:从全矩阵采集到逐点延迟叠加
2.1 TFM 与常规相控阵成像的本质区别:每个像素都是一次全孔径聚焦
相控阵成像的常规工作方式是电子扫查:一组阵元按延时法则激励,形成一个偏转聚焦声束,像手电筒一样扫过被检区域。接收时用动态聚焦修正声程差,但发射焦点只有一个,偏离焦点的区域声束扩散严重,横向分辨率随深度快速劣化。这也正是为什么当缺陷恰好不在焦点上时,回波看起来"发虚"、边缘轮廓模糊,定量测量误差大。
TFM 用一次 FMC(Full Matrix Capture,全矩阵采集)把发射-接收的所有组合记录下来。N 个阵元,每个阵元轮流发射,全部 N 个阵元接收,得到 N×N 条 A 扫信号。这些原始波形不做任何聚焦处理,保存的是未叠加的时域数据。成像时对图像区域内每个像素点,计算它到发射阵元和接收阵元的声程总和,除以对应介质的声速得到飞行时间,从各条 A 扫中取出该时刻的幅值累加。这等于在每一个像素点都做了一次"全孔径发射聚焦 + 全孔径接收聚焦",声束聚焦是处处成立的,不再有焦点分区概念。
两种算法的信号模型差异可以用声程公式直接看出。常规相控阵某个像素点的回波强度是:
I(x,z) = Σ sᵢ(t - (d_tx + d_rx)/c)
这里的 d_tx 是发射焦点到该点的声程,d_rx 是接收阵元到该点的声程。TFM 则把发射端也逐点化:
I(x,z) = Σᵢ Σⱼ sᵢⱼ(t - (d_i(x,z) + d_j(x,z))/c)
其中 i 是发射阵元、j 是接收阵元,sᵢⱼ 是全矩阵数据中第 i 个发射、第 j 个接收的时域信号。对比之下很清楚:常规方法发射声场只做一次聚焦,TFM 对每个点都做了完整的收发射线配对,聚焦质量由"一个焦点"变成了"每个点"。代价是算法复杂度从 O(N) 的延迟叠加变成了 O(N² × 像素数) 的计算量。
但要注意一个边界条件:TFM 的分辨率上界受限于阵元孔径和频率决定的衍射极限。它并不是"算法越复杂图像越清晰",而是让每一个像素点都接近该孔径下的最优聚焦状态。孔径小的探头用 TFM 也不会变成大孔径的成像效果,只是在小孔径下能做到最好的横向分辨率和信噪比。理解这一点,后续做工艺设计时就不会盲目追求 TFM 而忽视探头物理规格的匹配。
2.2 FMC 全矩阵数据组织:每条 A 扫的发射-接收索引不能丢
全矩阵采集得到的原始数据在内存里怎么摆放,直接决定后面延迟叠加的计算效率。常见做法是用一个三维数组或者等价的二维矩阵栈来存储:第一维是发射阵元索引 i,第二维是接收阵元索引 j,第三维是每个通道的采样点数。也就是说 A[i][j][:] 代表第 i 个阵元发射、第 j 个阵元接收的那条时域信号。
这个组织方式有一个容易踩的坑:很多相控阵仪器导出的 FMC 数据是按"时间片"存储的——每个发射事件(或每个激励脉冲)产生的所有通道数据作为一个块存储。如果之后做 TF M 成像时顺序搞混,将发射索引和接收索引调换,得到的是对称矩阵但物理上并不等价。尤其在各阵元频率响应一致时,调换索引不产生明显错误,可一旦阵元灵敏度有差异,图像上会出现伪影。
组织全矩阵数据时还需要同时保存每个阵元的坐标信息。对于直探头的线性阵列,阵元坐标通常是一维等间距排列;但对于相控阵楔块,阵元坐标要做楔块斜面的坐标变换。声程计算时如果直接使用探头坐标而忽略楔块界面,近场区域那一段的聚焦会出现系统性偏差——这个问题在后面的排错章节会详细展开。
2.3 Python 实现 TFM 聚焦循环:直接从原始信号取幅值
下面给出一段可以直接在本地跑通的 TFM 核心计算逻辑。它不依赖任何相控阵厂商的 SDK,只要提供全矩阵数据和阵元几何参数,就能完成单介质直入射的时域逐点聚焦。
import numpy as np def tfm_reconstruct(fmc_data, element_x, element_z, pixel_x, pixel_z, c, fs, t0=0.0): """ TFM 时域延迟叠加成像 fmc_data: (N, N, S) 全矩阵数据,N为阵元数,S为采样点数 element_x: (N,) 阵元x坐标,单位与pixel一致 element_z: (N,) 阵元z坐标(通常为0,表示表面) pixel_x, pixel_z: (P,) 成像网格坐标 c: 介质纵波声速 m/s fs: 采样率 Hz t0: 起始采样时间偏移 """ num_elem = fmc_data.shape[0] image = np.zeros((len(pixel_z), len(pixel_x)), dtype=np.float32) for px_idx, px in enumerate(pixel_x): for pz_idx, pz in enumerate(pixel_z): # 对当前成像点,计算到所有收发阵元的声程和延迟 tx_rx_sum = np.zeros(num_elem * num_elem, dtype=np.float32) for tx in range(num_elem): for rx in range(num_elem): d_tx = np.sqrt((px - element_x[tx])**2 + (pz - element_z[tx])**2) d_rx = np.sqrt((px - element_x[rx])**2 + (pz - element_z[rx])**2) time = t0 + (d_tx + d_rx) / c sample_idx = int(time * fs) if 0 <= sample_idx < fmc_data.shape[2]: tx_rx_sum[tx * num_elem + rx] = fmc_data[tx, rx, sample_idx] # 延迟叠加结果 = 所有收发组合在该时刻幅值的累加 image[pz_idx, px_idx] = np.sum(tx_rx_sum) return image逻辑说明:外层双重循环遍历成像网格的每个像素点;内层循环遍历所有 N×N 个收发阵元对,分别计算当前点到第 i 个发射阵元和到第 j 个接收阵元的欧氏距离。两段距离相加除以声速,得到声波从发射阵元出发、经像素点散射/反射回到接收阵元的总飞行时间,转换成采样点索引后直接取出 A 扫幅值,累加到该像素的强度值。
参数说明:c必须用被检工件中的实际声速。对于碳钢焊缝检测取 5920 m/s 左右;如果使用楔块,需要在楔块段和下表面段分别计算声程后叠加,不能直接用单一声速。fs的取值直接影响成像精度,采样率不足时延迟时间换算成整数采样点会损失聚焦精度,一般建议至少为探头中心频率的 5 倍以上。t0是触发射线到数据采集开始的时间偏移,通常由仪器触发延迟确定,单位是秒;如果数据采集的起点恰好和发射激励对齐,则设 0。
这段代码的计算效率较低,双重像素循环加上双重收发循环,对 64 阵元、1024 采样点、500×500 像素的成像网格来说纯 Python 循环要跑相当长时间。实际工作中我只把它当作算法验证和教学用的参考实现,工程化时要用下面的并行策略替换。
3. 工程落地:相控阵成像的采集参数与计算量控制
3.1 硬件参数边界:通道数、采样率和全矩阵数据量的取舍
做 TFM 之前所有参数设计都绕不开一个核心约束:FMC 数据量。64 阵元全矩阵采集需要 64×64 条 A 扫,每条 A 扫 2048 个采样点、单精度浮点存储,一个 FMC 文件就是 64×64×2048×4 字节 ≈ 32MB。仪器一次扫查如果移动 1000 个位置,就是 32GB 原始数据。这对采集端的传输带宽和存储容量都有要求。
| 参数 | 常规相控阵 | TFM/FMC | 说明 |
|---|---|---|---|
| 单次扫查触发次数 | 1 次(声束合成) | N 次(每个阵元一次) | TFM 发射轮次等于阵元数 |
| 单帧数据量 | 每角度一条 A 扫 | N×N 条 A 扫 | 64 阵元时数据量是 4096 倍 |
| 采样率要求 | 4~5 倍中心频率 | 5~10 倍中心频率 | 插值精度影响聚焦质量 |
| 成像帧率 | 可达 30fps+ | 通常 5~15fps | 取决于 GPU 并行度 |
| 最大深度 | 受 PRF 限制小 | 受总采集时间限制 | N 次发射 × A 扫时间 |
选择采样率的合理做法不是盲目提高。TFM 的时间延迟换算成采样点索引时做的是取整操作,如果只是单纯用int(time * fs),即使采样率再高也会引入量化误差。更稳妥的方案是在原始高采样率数据上做线性插值或 sinc 插值取幅值。采样率提高一倍,数据量和计算量跟着翻倍,但聚焦增益只在信号带宽范围内有效。对 5MHz 探头,采样率 50MHz 已经足够,100MHz 并不带来双倍分辨率的提升——分辨率由孔径和频率决定,而不是由采样密度决定。在写聚焦逻辑时用浮点时间索引配合插值比单纯堆采样率更划算。
阵元数对成像质量的影响同样有边界条件。从算法角度看,64 阵元比 32 阵元做 TFM 的图像信噪比高约 3dB,这是因为有效孔径变大、接收信号叠加次数增加。但阵元数增加到 128 时,单次 FMC 采集的数据量成了 16384 条 A 扫,采集时间拉长一倍,扫查速度下降,对大多数常规焊缝检测场景并不值得。16~32 阵元的 TFM 在处理薄壁小径管时往往已经够用。
3.2 聚焦法则重构:从硬件查表到软件逐点计算
常规相控阵仪器里有一套聚焦法则(focusing law)的概念:发射延迟、接收延迟、增益补偿都写进硬件查找表,由 FPGA 或专用的脉冲收发芯片在触发时执行。这些延迟值是预先计算好的,数量有限,通常只有几十组到几百组。
TFM 改变了这个模式:延迟值不是预先查找的,而是在成像过程中针对每一个像素实时计算。这意味着相控阵系统的软件架构要重新分三层——采集层只负责高速存储 FMC 原始数据;成像层负责声程计算、延迟叠加和图像合成;显示层才轮到常规的灰度映射和缺陷标注。硬件查表的延迟精度一般按 2~4ns 的步进量化,而 TFM 逐点计算把延迟量化直接做到了浮点精度级别,再通过插值获得亚采样点的幅值,时间精度理论上只受采样率约束。这带来的信噪比提升在大角度入射时尤其明显——常规扇扫在大偏转角度下聚焦延迟量化误差大,图像边缘区域分辨率剧烈下降,TFM 不存在这个问题。
虽然 TFM 不需要预设聚焦法则,但发射波形中仍然需要一个关键参数:激励脉冲的负方波宽度(pulse width)。它决定了发射声波的频带宽度。TFM 算法本身不改变探头激励条件,中心频率依旧由探头决定,脉冲宽度则需要根据探头的标称频率,按脉冲宽度 ≈ 0.5 / 中心频率的经验值设置,例如 5MHz 探头设 100ns 左右。如果把脉冲宽度调大,信号带宽变窄,时域分辨率下降,TFM 的图像轴向分辨率会劣化——这是少数几个被误认为"TFM 会自动优化"但实际仍需手动整定的参数。
3.3 图像网格划分和并行加速的常规做法
TFM 成像网格的划分规则:轴向步长取信号波长的一半左右,横向步长根据孔径和深度调整,一般取 0.2~0.5mm 为下限。网格比这个密度更小不会提高分辨率,只会拉高计算量;网格太粗则图像上本身清晰的小缺陷会被像素化抹平。
计算量控制方面,工程上最常见的加速切分方式是 GPU 上的像素级并行。在 NVIDIA GPU 上用 CUDA 做 TFM 时,每个线程处理一个像素点,该像素点内的 N×N 延迟叠加在单个线程内完成,或者用一个线程块处理一个像素、用 block 内的并行 reduce 累加收发组合。128 个线程共用一个像素,每个线程处理 64 个收发组合,归约得到最终的幅值。这种切分方式的优势是数组的读写局部性好:同一个像素点需要访问的 N² 条 A 扫数据是固定的,L2 缓存命中率高。
如果手头只有 CPU 环境,多线程 OpenMP 的并行方向应该打在收发组合上而非网格上。伪代码的核心循环这样组织:
#pragma omp parallel for collapse(2) for (int tx = 0; tx < N; tx++) { for (int rx = 0; rx < N; rx++) { // 对当前 tx,rx 组合,遍历所有像素点,累加该收发对的贡献 for (int p = 0; p < NPIXEL; p++) { img[p] += signal_interp(tx, rx, time_of_flight(tx, rx, p)); } } }这样做的原因是不同收发对之间的计算完全独立,没有共享写竞争,因为对同一个像素的累加是在遍历像素的循环内部完成的。每个线程处理一组收发对,遍历网格并做累加,属于 reduce 型并行。相比像素级并行,这种打法对缓存利用更友好:同一组收发对在连续像素点上的飞行时间变化是平滑的,可以预取一段连续信号。我一般建议 CPU 上先用收发对并行的版本验证 FPGA 或 GPU 的硬件实现对不对,两者的成像结果应该逐像素一致。
4. 相控阵 TFM 成像的排错与边界:看不见的盲区和算不准的声速
4.1 楔块模式下的声程计算:单一介质公式失效的场景
上一章代码里给的是单介质直入射模型,实际检测中大多数 TFM 应用都带楔块(有机玻璃斜楔或可重构楔块)。这时声波要穿过两层介质:先经过楔块内的纵波路径到达工件表面,再在工件内以横波或纵波传播。如果直接按整个声程都用工件的声速计算飞行时间,会导致近表面区域聚焦错乱,具体表现是表面回波下方出现一对对称的弧形伪影。
正确的做法是分两段计算声程。入射阵元 i 到像素点 P 的声程不是一条直线,而是经过楔块底面折射点的折线。折射点位置一般用费马原理迭代求解:在楔块底面上取一点 R,分别计算阵元到 R 和 R 到 P 的距离,除以对应介质的声速得到总飞行时间,改变 R 的位置让时间最小化,该极值点就是真实的声线折射点。
接收端的声程也做同样的计算。这样才能得到正确的双程飞行时间。如果不做折射校正,只把楔块整体当作工件的一部分,误差会在深度方向形成与入射角度成正比的错位——入射角越大,聚焦点越偏浅。现场通过小孔试块(SDH,side-drilled hole)验证 TFM 图像时,对比已知孔的深度和实测位置,也是靠这种方法确认声程计算是否正确。
4.2 声速敏感性:工件声速偏差 1% 会带来什么后果
TFM 对声速误差的耐受度比常规相控阵低。常规扇扫用的是固定聚焦法则,声速变化导致的是一个全局的深度偏移,缺陷横向位置大致不变;TFM 里声速直接进入飞行时间计算,声速偏大相当于所有延迟时间偏小,同一像素点在每条 A 扫上取的幅值点都偏离真实聚焦位置,图像表现为聚焦质量整体下降,边缘模糊,点状反射体在图像上出现圆环状的拖尾。
定量来看,对碳钢横波声速 3230 m/s,如果设置成 3260 m/s,偏差约 1%,在深度 50mm 处、孔径 20mm 的聚焦条件下,图像横向分辨率劣化大约 15%~20%,缺陷峰值幅值下降 2~4dB。对缺陷定量来说,4dB 已经足以影响缺陷评级的判定结果。
声速不准还分两种情况。焊缝母材和热影响区声速差异通常在 0.5% 以内,可以忽略;但粗晶奥氏体不锈钢或铸件组织中,声速可能偏离标称值 2% 以上。这种情况下纸上算的聚焦参数全部失真,工程上最可靠的校准方法是做一个同材质、同厚度的对比试块,里面埋设已知位置和尺寸的横孔,用仪器自动校准声速。校准后应确认聚焦图像中孔的位置和幅值都在公差范围内,再投入实际扫查。
4.3 把 TFM 当常规相控阵调参的后果:增益和 DAC 曲线不能照搬
将近 5 年经验的检测工程师也常在这一个地方翻车:直接沿用常规相控阵的增益曲线和时间增益补偿(TCG)来调 TFM 图像。TCG 的作用是补偿声束扩散和材料衰减随距离增加造成的幅值下降,但它是基于"单声束传播"模型的,而 TFM 的每个像素累加了 N² 条路径的贡献,距离相关的幅值变化规律和 TCG 模型完全不同。
TFM 图像中更基础的问题在于动态范围。常规相控阵 A 扫的噪声底在一条扫查线中基本稳定,而 TFM 图像中近场区域的噪声底远低于远场区域,因为近场像素累加的路径数不变,但到达近场点的多次散射路径更多且互不相干,平均后噪声反而更小。如果沿用常规的 40dB 动态范围,图像近场部分会显得特别暗,远场部分噪声偏高。推荐的显示动态范围通常在 30~35dB,但具体值要根据被检工件的衰减特性做一次对比试块扫查决定。
增益标定上也存在一个容易忽视的细节:TFM 图像的幅值正比于参与叠加的收发对数量 N²。32 阵元全矩阵的像素幅值是 16 阵元的 4 倍,但这不是真实的回波增强,而是叠加次数的统计增益。做缺陷定量时必须以同一套探头和阵元数下测得的参考反射体为基准建立曲线,不能跨配置直接比较幅值。
5. 一个能用的验证技巧:用点散射体模型确认你的 TFM 实现聚焦正确
前面聊了实现和排错,最后一章直接给一个工程验证方法。拿到一段 FMC 数据但不知道成像结果对不对时,最快、成本最低的验证手段是合成点散射体响应,把已知位置的点散射体回波送进聚焦算法,检查成像后点是否落在正确位置、聚焦是否锐利。
合成流程分四步。在选定位置放置一个理想点散射体,对每个阵元对 (tx, rx),计算该点到发射阵元和接收阵元的距离,除以声速得到延迟,在该时间位置放一个高斯包络脉冲。然后把所有 N² 条合成 A 扫送入 TFM 聚焦循环。聚焦结果应该在点散射体真实位置形成主峰,旁瓣水平与理论预期一致。如果主峰位置偏移超过一个像素或者出现明显的轴向拉长,说明时间延迟计算或坐标映射有 bug。
焦点锐度可以直接用一个数值指标来量化,不必依赖肉眼观察图像。提取成像结果中散射点附近 10×10 像素区域,计算峰值旁瓣水平(PSL):
def peak_sidelobe_level(img, peak_x, peak_z, exclusion_radius=3): """ 计算点扩展函数的峰值旁瓣比。 img: TFM成像结果 peak_x, peak_z: 理论点散射体应出现的像素坐标 exclusion_radius: 主瓣排除半径,单位像素 """ center_val = img[peak_z, peak_x] masked = img.copy() # 排除主瓣区域,只保留远场旁瓣和栅瓣 z_grid, x_grid = np.mgrid[0:img.shape[0], 0:img.shape[1]] dist = np.sqrt((z_grid - peak_z)**2 + (x_grid - peak_x)**2) masked[dist < exclusion_radius] = 0 max_sidelobe = np.max(masked) return 20 * np.log10(max_sidelobe / center_val)旁瓣比对幅值更敏感。对 32 阵元、5MHz、0.6mm 节距的线性阵列,TFM 点扩展函数的峰值旁瓣比通常在 -40dB 以下。如果计算得到的旁瓣比大于 -30dB,优先检查阵列孔径中心附近的阵元是否在合成响应时被重复计入,或者坐标轴方向是否接反。这个验证方式不依赖真实工件、不依赖仪器,纯算法层面就能定位大部分聚焦实现问题,也适合用来快速比较不同采样率、不同插值算法对成像质量的边际收益。它还可以在写论文或做工艺验证时,用来给出聚焦质量随深度的变化曲线:在多个深度分别放一个散射体,计算各自的 PSL,连成一条曲线,就能定量回答"这个探头配置在某深度范围里 TFM 的表现是否稳定"——这个深度相关的评估结论,比拍一张好看的 B 扫图像去说服评审人员要有力得多。
本文还有配套的精品资源,点击获取