全聚焦TFM成像原理与Python实现:从FMC数据到延迟叠加
2026/9/14 13:41:20 网站建设 项目流程

简介:针对超声全聚焦(TFM)与相控阵成像技术的学习与仿真需求,这份压缩包提供了一套轻量级Python实现,面向从事无损检测、超声成像算法研究或相关课程设计的工程技术人员与研究者。其中tfm.py封装了全聚焦方法从信号采集到逐点聚焦成像的核心流程,gui.py则通过界面交互让用户调整阵元数量、偏转角度等参数,直观查看成像结果,便于将算法原理与代码实现相互对照。资源仅含2个py文件,压缩包大小约4KB,结构精简,适合快速运行与二次开发。目前已有258人浏览学习。通过阅读源码,可以掌握TFM延迟叠加求和、像素聚焦坐标计算、阵列布局配置等关键实现思路;代码注释清晰,配合示例能够复现基础成像流程,为后续扩展扫描策略或优化成像质量提供可直接修改的参考起点。

1. TFM 为什么比常规扇扫更适合超声相控阵成像:先把“全聚焦”想清楚

total-focusing-method(TFM,全聚焦法)在超声相控阵成像里的位置,恰好和传统扇扫(S 扫)相反:扇扫在采集之前就设计好聚焦法则,而 TFM 先把所有阵元“一发全收”的原始数据存下来,再在后处理阶段对图像上任意一个像素点重新计算声程并叠加。这个反转让每个像素都能获得独立聚焦,近表面分辨率和缺陷端部绕射表征能力明显优于固定聚焦成像。本文面向要把现有 16/32/64 通道相控阵仪器往全聚焦成像升级的工程师,从 FMC 数据格式、最小 Python 重建内核写到孔径、栅格间距和声速修正这三组最关键参数。阅读前提:你熟悉相控阵探头、A 扫和基本的传播时间(TOF)概念,但不一定要写过成像算法。

2. 全聚焦成像的物理基础:从全矩阵捕获到双程声程与延迟求和

TFM 的输入,行业内俗称“全矩阵捕获”(Full Matrix Capture,FMC)。这不是某种特殊探头,而是相控阵主机工作方式的一种改变:N 个阵元轮流单独发射,每次发射时所有 N 个阵元同时接收,把 N×N 条 A 扫一字排开保存。常规扇扫在单次发射里完成了一大半聚焦,TFM 则把这一工作推迟到了图像重建阶段,因此它不需要预置聚焦法则,只需要一组完整、同步的时域信号。

2.1 全矩阵捕获(FMC)的数据排布:N×N 个阵元对

先约定记号:把第i个阵元发射、第j个阵元接收得到的 A 扫记作a_ij(t),其中t是相对发射时刻的时间。FMC 数据集就是一张N × N × Ns的三维表,Ns是每条 A 扫的采样点数。实际仪器里,a_ii这类自发自收信号通常也保留,只是它的信噪比低于斜向路径,叠加时权重天然低一些,不必刻意剔除。

数据维度代表含义典型取值
N探头阵元数16 / 32 / 64 / 128
Ns单条 A 扫采样点数1024 ~ 4096
fs采样率50 ~ 100 MS/s
pitch阵元中心距0.4 ~ 1.5 mm

之所以要保存完整的 FMC 而不是只存几条合成孔径线,是因为后续图像重建时,每个像素点可能需要任意一个发射阵元和任意一个接收阵元的组合。提前把数据一次性采齐,后处理算法就可以自由决定每个阵元对的贡献权重,这正是 TFM 能在成像质量上超过固定聚焦规则的根源。

2.2 像素的双程声程:把一发一收变成某个像素点的幅值

设探头位于z = 0平面上,阵元中心坐标为(x_i, 0)(x_j, 0)。图像中任意一个像素点P的坐标为(x_p, z_p),其中z_p是从探头表面向被测材料内部计量的深度。对于均匀各向同性材料中的纵波直入射情形,发射声程和接收声程分别是

d_tx = sqrt((x_p - x_i)**2 + z_p**2) d_rx = sqrt((x_p - x_j)**2 + z_p**2) n = int(round((d_tx + d_rx) / c * fs)) # 采样点下标

这里c是材料声速,fs是采样率。计算出来的n就是我们在 A 扫a_ij上取幅值的下标。当n落在[0, Ns)之外时,说明该阵元对在该像素位置没有真实回波,直接跳过这一项。将所有阵元对的取值累加,得到的就是该像素的灰度值:

img[x_p, z_p] = sum_ij a_ij[n_ij(x_p, z_p)]

这段代码背后是延迟叠加(delay-and-sum)思想,但注意它和相控阵硬件中的“聚焦法则”有一个本质区别:硬件聚焦是在发射时通过延时让声场在某个深度汇聚,而 TFM 是在成像阶段对每一个像素分别计算“这个像素如果是一个点散射体,哪条路径、什么时候会到达接收阵元”。因此近距离像素可以用小孔径,远距离像素自动过渡到大孔径,所有焦点都是后验的。

2.3 用有效孔径来描述哪些阵元对可以参加求和

虽然求和理论上要遍历全部N×N个阵元对,但实际工程中不会把所有阵元对都无差别加进去。原因是:当像素点靠近探头表面时,边缘阵元相对该像素的入射角接近 90 度,这些几乎平行于表面的路径不仅带入无效信号,还会带来强烈的串扰和边界伪影。常规做法是设定一个最大入射角阈值G,只有入射角小于G的阵元对才参与求和。入射角按下式计算:

sin_theta_tx = abs(x_p - x_i) / d_tx sin_theta_rx = abs(x_p - x_j) / d_rx

G的取值通常在 30 度到 45 度之间。取小角度时近表面图像干净,但深处有效孔径变小,灵敏度下降;取大角度时深处幅值更高,但近表面会出现明显的圆弧状伪影。这个参数会在第 4 章结合阵列设计细说,你现在只需要理解:TFM 的“全聚焦”并不是物理上把所有阵元对全部叠加,而是动态筛选可用孔径后再叠加。

3. 用 Python 实现 TFM 后处理:最小可运行代码与图像重建流程

这一章给出一个能在笔记本上跑通的最小实现。它不会是最快的 TFM 内核,但结构清晰,方便你验证仪器数据、调试孔径参数,也为后续换用 GPU 或多线程留出改造空间。我在这里假设你已经用探伤仪导出了 FMC 数据,并且知道探头阵元数、阵元间距、采样率和材料声速。

3.1 先定义像素网格和阵元坐标

重建图像前需要确定两个坐标系统:探头阵元坐标和成像区域网格。阵元坐标由pitch和阵元数唯一确定;成像网格则由被测区域范围与期望的横向/纵向分辨率约定。下面代码直接定义一个常见配置:宽度 40 mm、深度 60 mm、各取 400 个点。

import numpy as np # fmc: shape (N, N, Ns) # pitch: 阵元中心距, 单位 mm # c: 声速, 单位 mm/us # fs: 采样率, 单位 MHz, 即 1 us 内的采样点数 pitch = 0.8 N = fmc.shape[0] x_elem = (np.arange(N) - (N - 1) / 2) * pitch # 阵元 x 坐标, 探头中心为 0 # 成像网格: 横向 x, 深度 z, 单位都是 mm xg = np.linspace(-20, 20, 400) zg = np.linspace(0.5, 60, 400) img = np.zeros((zg.size, xg.size))

这里把zg的起点设为 0.5 mm 而不是 0,是为了避免探头表面附近的直耦合波和表面回波把图像顶部淹没。x_elem以探头中心为原点,左右对称,这是大多数线阵探头默认的坐标系。如果你的仪器导出的数据带有一个固定的前沿延迟,应该在计算距离时把前沿对应的路径长度先减掉,否则整个图像会比真实深度偏深一段固定值。

3.2 朴素延迟叠加:先跑对功能再优化

直接按照第 2 章的公式实现三层循环,这是最直观、也最容易和公式对照的版本。数据量小的时候,比如 32 阵元、每灰度取 200×200 像素,这个版本几秒钟内也能完成。

for ix, xp in enumerate(xg): for iz, zp in enumerate(zg): acc = 0.0 for i in range(N): # 发射阵元 d_tx = np.hypot(xp - x_elem[i], zp) for j in range(N): # 接收阵元 d_rx = np.hypot(xp - x_elem[j], zp) n_idx = int((d_tx + d_rx) / c * fs + 0.5) if 0 < n_idx < fmc.shape[2]: acc += fmc[i, j, n_idx] img[iz, ix] = acc

逻辑说明:内层循环中np.hypot计算的是每个阵元到当前像素点的直线距离;(d_tx + d_rx) / c是声波从发射阵元出发、到达像素点散射、再被接收阵元拾取的总时间;乘以fs后得到离散采样点下标。这里用int(... + 0.5)做四舍五入,是为了减少采样量化造成的幅值抖动。acc累加的是所有阵元对的贡献,最终赋值给当前像素。整个过程中没有任何波束成形系数,也没有动态聚焦的时序控制,全部计算都发生在取数据和求和这一步。

这个朴素版本有三个明显的问题:一是三层 for 循环在 Python 里很慢;二是没有做入射角筛选,靠近表面时会有圆弧伪影;三是没有对信号做包络检波或 Hilbert 变换,图像看起来会是振荡的明暗条纹。后两个才是影响图像可读性的关键,速度问题可以用numba或把像素循环改为向量化数组运算解决。

3.3 加入动态孔径筛选:过滤无效阵元对

在 2.3 节我们讨论了入射角阈值G。代码上只需要在内层循环入口处加一个判断:如果当前阵元相对该像素点的入射角超过阈值,就直接跳过。阈值用角度制写成常量,方便你反复调整。

G = np.deg2rad(35) for ix, xp in enumerate(xg): for iz, zp in enumerate(zg): acc = 0.0 for i in range(N): d_tx = np.hypot(xp - x_elem[i], zp) if d_tx == 0: continue if abs(xp - x_elem[i]) / d_tx > np.sin(G): continue for j in range(N): d_rx = np.hypot(xp - x_elem[j], zp) if d_rx == 0: continue if abs(xp - x_elem[j]) / d_rx > np.sin(G): continue n_idx = int((d_tx + d_rx) / c * fs + 0.5) if 0 < n_idx < fmc.shape[2]: acc += fmc[i, j, n_idx] img[iz, ix] = acc

参数说明:d_tx == 0的判断处理的是阵列正下方为整数倍 pitch 的像素点,这时候入射角没有意义,按中心路径直接接收集相当于i阵元正下方一点。np.sin(G)abs(x_p - x_i) / d_tx的比较,本质上是把入射角折换成“横向距离除以声程”的比例,省去每次计算arcsin的开销。35 度只是出发点,实际调试时建议从 25 度开始往上加,观察近表面伪影和深层灵敏度之间的平衡。

提示:如果你发现图像里有沿阵列方向展开的细密条纹,通常是某个固定阵元对的串扰信号被重复参与叠加。处理办法是去掉i == j的自发自收对,或者对所有 A 扫先做一次均值相减,把系统串扰成分压掉。

4. 网格间距、阵元孔径与声速:超声相控阵 TFM 成像的三个关键配置

跑通代码之后,真正决定图像质量的就不是算法框架,而是三个数值:像素网格间距、参与聚焦的孔径范围、以及重建所用的声速。这三者分别对应分辨率、旁瓣和定位精度,调试时不能只盯一个,否则图像会出现“明明很锐利但位置全偏”的错觉。

4.1 网格间距按波长缩放:太密不涨信息,太稀丢小缺陷

TFM 图像中相邻两个像素点的距离显然越小越好,但采样定理在这里同样适用:像素间距对应的是“成像采样率”,而信号的横向空间带宽受探头孔径和频率约束。理论极限近似是半波长,工程上常用四分之一波长作为网格间距下限。对钢中 5 MHz 纵波,声速约 5900 m/s,波长约 1.18 mm,四分之一波长就是 0.3 mm 左右。横向间距取 0.2 mm 就已经和这个小缺陷的物理尺度相当了。

材料纵波声速(m/s)5 MHz 波长(mm)λ/4(mm)
碳钢59001.180.30
63201.260.32
奥氏体不锈钢57401.150.29
有机玻璃27300.550.14

网格间距小于 λ/4 时,图像只是变得更平滑,并不会增加有效分辨率;网格间距大于 λ/2 时,小缺陷的幅值会出现栅瓣,定位也会跳到相邻像素上。我一般会把横向间距取为 λ/4,纵向间距同样取 λ/4,然后整体做归一化。这样既保证重建结果不会丢失小缺陷,又不会把 FMC 数据量不必要地放大。

4.2 阵列孔径与最大成像深度的关系:动态孔径的曲线

第 3.3 节的G角度实际上限制的是每个深度上的有效孔径宽度。有效孔径宽度A_eff与深度z之间的关系约等于2z·tan(G)。当G = 35°时,tan(35°) ≈ 0.7,所以深度 40 mm 处的有效孔径宽度约 56 mm。这个值如果超过探头总孔径,说明该深度下所有阵元对都已经被纳入,图像等价于全孔径聚焦。反过来,深度 5 mm 处的有效孔径宽度只有约 7 mm,这时候大约只有七八个阵元真正参与叠加,这正是 TFM 近表面分辨率依然可观的原因。

实际设置G时有一个容易被忽略的耦合因素:角度太大时,边缘阵元与像素点之间的路径在近表面区域与楔块底面反射混淆,造成图像上出现一条平行于探头表面的低幅值亮带。角度太小时,近表面虽然干净,但 30 mm 以下的缺陷幅值可能比全孔径时低 6 dB 以上。一种常见做法是让G随深度线性展开:近表面取 20 度,每 10 mm 深度增加 2 度,直到 40 度封顶。这个策略对大多数平面线阵探头都适用,但如果你用的是曲面楔块,孔径限制还应该考虑楔块到零件界面处的折射,不能只看阵元几何角度。

4.3 声速误差在图像上产生什么样的位移与散焦

声速c错得再小,也会同时造成两类后果:图像散焦和缺陷横向偏移。散焦原因是所有阵元的延迟时间都按错误声速计算,导致不同阵元对的相位不能对齐,叠加后主瓣幅值下降、旁瓣抬高。横向偏移更大,因为声程的横向分量对声速变化更敏感。简单估算一下:如果真实声速是 5900 m/s,你用了 5790 m/s,即低估约 2%,那么在 40 mm 深度、入射角 35 度的反射体上,重建位置会横向偏移约 0.8~1 mm。这个偏移量对缺陷定量来说不可忽略。

处理声速误差的标准方法是在标准试块上找一个孔径已知的小横孔,先用第 3 章的代码重建图像,然后调整c值重新重建,观察横孔图像是否同时满足两个条件:深度位置等于试块标称深度,横向位置在探头中心线正下方。任何一个条件不满足,都说明声速设置不合理。值得强调的是,最好不要同时微调c和像素原点,否则两者误差会互相掩盖,导致看起来“位置对准了”,实际声程计算仍然是错的。

5. 用试块横孔验证一幅 TFM 图像的实用技巧

TFM 图像看起来“很清楚”很容易,但“清楚并且几何正确”就需要一组验证动作。这一章给出我每次在试样或管道上切换到 TFM 模式时都会做的两个技巧,耗时不超过十分钟,但能挡住大部分低级错误。

5.1 用三枚不同深度的横孔校准声速与图像原点

在试块上选三个深度相差明显的横孔,比如 10 mm、25 mm、45 mm,让它们位于探头下方同一水平线上。采集一次 FMC 数据,重建 TFM 图后,在每个孔的真实深度附近取横向切面,记录图像上孔峰值的深度和横向坐标。如果三个孔的横向偏移都朝同一个方向,且偏移量随深度近似线性增加,那基本可以判定是声速误差,按第 4.3 节的方向修正c。如果只有浅孔偏移、深孔位置正常,则多半是探头前沿延迟或图像原点设置错误,应该整体平移声程起点,而不是动声速。

5.2 叠加横波模式图像,判断缺陷是近表面裂纹还是倾斜反射体

标准 TFM 重建通常默认纵波-纵波路径,也就是发射和接收都用纵波。当缺陷是倾斜平面或近表面裂纹时,纵波路径回波幅值可能很低,重建图上几乎看不到缺陷。解决手法是单独做一次横波模式的延迟计算,把声速换成横波速度,并将像素点到组件的路径按横波折射角重算。实际代码里只需要把前文c的取值从纵波速度改成横波速度,再把成像区域的坐标系做一次倾斜修正即可。多模式图叠加时,横向位置会因声速不同产生毫米级的差异,这时以横孔校准结果为准,不要为了让两幅图像重合去硬调声速。这一招在奥氏体不锈钢和异种钢焊缝检测里非常实用。

5.3 用子孔径差分快速排除某组阵元的硬件串扰

当 FMC 数据里某一两个阵元存在间歇性接触问题时,整幅 TFM 图会在对应位置出现一条贯穿深度方向的纵向条纹。判断方法是把阵元分成两组,比如偶数下标和奇数下标各拿出一半做一次子孔径重建,然后两幅图像相减。正常信号会被保留,固定模式的硬件串扰会因两个子集对称而抵消。如果相减后的图像在高幅值条纹处依然清晰可见,说明需要检查对应阵元的前端电路或耦合剂,而不是继续调整算法参数。这个技巧不需要额外采集数据,直接从已有 FMC 数据里抽子集即可,对现场快速判断数据质量很有用。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询