基于Python的声发射RA-AF裂纹分类:从数据处理到可视化
2026/9/20 2:20:29 网站建设 项目流程

做声发射(AE)检测的朋友,对RA-AF这两个参数肯定不陌生。尤其是做混凝土、岩石、复合材料断裂机理分析的人,拿到一批声发射数据后,第一步往往就是在RA-AF图上点开看裂纹类型。这个方法的逻辑非常直观:通过上升时间、振幅、振铃计数和持续时间这几个基础时域参数,把每一次声发射事件映射到一张二维图上,然后根据点落在分界线的哪一侧,来判别它是拉伸裂纹还是剪切裂纹。以前大家多用Excel或者设备自带的商业软件来画,批量处理不灵活,阈值线也调起来也麻烦。这阵子我正好用Python做了一套完整的RA-AF裂纹模式分析流程,从数据清洗、参数计算、裂纹分类到可视化,一条龙跑通。这篇就把思路、代码和踩过的坑一起分享出来,尤其适合手头有AE数据、想自己掌控分析细节的研究生和检测工程师参考。

1. RA-AF裂纹分类的原理与判断逻辑

1.1 什么是RA与AF,它们怎么来的

RA和AF是两个从声发射波形时域参数里派生出来的指标,计算逻辑很简单。

RA的全称是Rise Amplitude,也就是上升时间与振幅的比值,单位通常是µs/dB。它反映的是声发射信号上升段的陡峭程度。上升时间短、振幅高,RA值就小,说明波形起得快、峰值高;上升时间长、振幅低,RA值就大,波形就显得“拖沓”。

AF的全称是Average Frequency,平均频率,单位是kHz,由振铃计数除以持续时间再乘以1000得到。它衡量的是声发射事件的“密集程度”,其实是对信号频谱特征的一种时域近似。高频信号在同样的持续时间内会跨越更多次阈值,振铃计数自然就多,AF值就高。

把这两个指标放在一起,就构成了一张RA-AF特征图。材料内部开裂时,不同类型的裂纹会激发出不同特征的弹性波。拉伸裂纹以张开型位移为主,波形在起跳阶段陡峭、频率成分偏高,所以表现的低RA、高AF;剪切裂纹以滑移型位移为主,波形起跳相对平缓、频率成分偏低沉,所以表现的高RA、低AF。这个物理背景就是整张判别图的底层逻辑,不需要做太多频域分析就能快速给裂纹“定性”。

1.2 拉伸裂纹与剪切裂纹的判别判据

理论上说,RA值和AF值在图上大致是两类点:一类聚在左上角,是拉伸裂纹;一类聚在右下角,是剪切裂纹。但实际数据并不能做到完美分离,两类点往往有交叠,所以业界一般用一条过原点的判别线来划分区域。

判别线方程通常写成这样:

AF = RA / k

也就是AF等于RA除以一个常数k。k的取值,在日本混凝土声发射监测标准JCMS-IIIB5706以及很多文献里建议,大约在80到200之间。落在线的上方判为拉伸裂纹,落在下方判为剪切裂纹。实际项目里这个k值不是随便抄的,最好用同类材料、同类传感器布置方式下做过已知裂纹模式验证的数据来标定。如果你只是做个初步筛查,可以先用k=100跑一遍,再看点的分布调整。

这里有一个容易踩的坑,就是大家可能直接用RA数值和AF数值去比较,比如认为RA小、AF大就是拉伸。但“小”和“大”没有绝对标准,数据量一旦大起来,人眼分不过来,代码里必须用一个明确的判据。所以要么用比值阈值,要么用这条斜率线,写成逻辑表达式才能真正落地。

1.3 为什么用Python重新造轮子

市面上很多AE设备商自带后处理软件,确实能画RA-AF图,但用起来有几处不舒服。一是数据格式封闭,导出的结果经常是整理好的“结论”,不是原始的每事件特征参数;二是批量对比多组试件、多个工况时,得一个一个点,效率低;三是判别线的k值调整、颜色区分、按时间序列看裂纹演化这类定制需求,商业软件往往实现不灵活。

Python的优势在于,把数据读取、参数计算、分类判别、可视化放在同一个脚本里,跑一次就能出全套图件。而且pandas、numpy、matplotlib这些库的生态已经很成熟,处理几十万条AE事件不吃力。配合Jupyter Notebook,还能做到交互式调整k值,立刻看到分类比例变化。这也是我把整套流程迁到Python上的直接动力。

2. 数据准备与核心参数计算

2.1 声发射数据从哪里来,怎么读进来

做RA-AF分析,需要的是每一次声发射事件的“特征参数”,也就是由声发射采集系统实时提取的时域指标,而不是原始波形。不同厂家的导出格式不一样,但基本上都会包含以下几列:上升时间(rise time)、振幅(amplitude)、振铃计数(counts)、持续时间(duration),以及可选的到达时间、能量、信号强度等。

我拿到的数据通常是CSV或Excel格式,从PAC、Vallen这些设备软件里导出来的常见形态是:第一行列名,后面每行一次事件。有些时候设备会带一个总表,包含大量和裂纹判别无关的列,没关系,读进来之后只挑需要的四列即可。如果你的数据是文本格式或者数据库导出的,第一步统一转成DataFrame就够了。

需要特别提醒的是单位。上升时间可能是ns、µs、ms三种单位,持续时间也可能是µs或ms,不同软件导出设置不一样,如果单位不统一,算出来的RA和AF会出现量级灾难。我建议在读取之后立刻做一次单位检查,先看数值范围,再决定是否需要乘以换算系数。

2.2 RA、AF的公式与单位转换细节

计算公式并不复杂,但单位转换是重灾区。

RA = 上升时间 / 振幅

振幅在AE系统里通常以dB为单位记录,这是对数标度。如果设备给的是电压值(比如mV或µV),就需要先转换成dB。转换公式是:

dB = 20 * log10(A / A0)

其中参考值A0通常是1µV,也有些系统用1mV。如果你确认不了,就找设备说明书或者系统设置里的参考电压。搞错参考值,RA整体会偏移一个固定倍数,分类线怎么调都别扭。

AF = 振铃计数 / 持续时间(ms)

更安全的写法是:

AF(kHz) = counts / (duration_us / 1000)

也就是说,如果持续时间以µs为单位,先除以1000转成ms,再用counts去除。为什么AF的单位直接是kHz,因为1/ms就对应kHz。这一步不要在Excel里手算,脚本里用向量化运算一次搞定,几万条数据也不卡。

还有一点,有些设备导出的振铃计数包含首波,有些则不含,这个不会对分类结果造成颠覆性影响,但如果你要跟别人对数据,最好统一口径。

2.3 数据清洗:剔除无效事件

真实工程数据不是教科书那么干净。环境噪声、电磁干扰、设备自激都可能导致个别事件的参数异常。RA-AF图上偶尔会出现孤零零飞出去的点,往往就是这些脏数据。

我常用的清洗规则有下面几条。

第一,振幅为0或负数的直接删掉。振幅是RA公式的分母,为0会直接产生无穷大。

第二,振铃计数为0的删掉。AF分子为0,算出来就是0,这类事件多为极弱噪声信号,没有分析意义。

第三,持续时间为0的删掉,避免除零错误。

第四,上升时间为0的,有人直接删掉或替换成极小值。上升时间为0说明系统捕捉到了极陡的起始波,理论上偏低频噪声不会这样,所以可以保留,但要留意它对RA的影响。

第五,可以根据设备的背景噪声水平设置一个振幅下限。比如实验环境噪声在35dB左右,那么低于40dB的事件,大概率不是有效声发射,建议过滤掉。

这些清洗步骤在做裂纹比例统计时尤其重要,因为你统计的是所有事件的占比,如果混入了10%的噪声事件,最终拉伸/剪切的比例会失真,工程结论就可能出问题。

3. 基于Python的RA-AF分类实现与可视化

3.1 数据读取与预处理脚本

下面的脚本我按最常用的CSV输入来写。如果你的数据是Excel,把pd.read_csv换成pd.read_excel即可。

import numpy as np import pandas as pd # 读取原始AE数据 # 列名根据实际导出文件调整 df = pd.read_csv('ae_events.csv') # 统一列名,方便后续操作 df = df.rename(columns={ 'Rise Time': 'rise_time_us', 'Amplitude': 'amplitude_db', 'Counts': 'counts', 'Duration': 'duration_us' }) # 仅保留需要的列 df = df[['rise_time_us', 'amplitude_db', 'counts', 'duration_us']].copy() # 数据清洗:剔除无效事件 df = df[df['amplitude_db'] > 0] df = df[df['counts'] > 0] df = df[df['duration_us'] > 0] df = df.dropna() # 可选的背景噪声过滤 # 如果环境底噪是35dB,则只保留40dB以上事件 df = df[df['amplitude_db'] >= 40] # 重置索引 df = df.reset_index(drop=True) print(f"有效声发射事件数量:{len(df)}")

这里提醒一下列名映射。我见过上千个字段的超宽表,别直接上来就全读,最好在pd.read_csv的时候用usecols参数只加载需要的列,省内存也省排查时间。

# 更推荐的做法:只读这几列 df = pd.read_csv('ae_events.csv', usecols=['Rise Time', 'Amplitude', 'Counts', 'Duration'])

3.2 计算RA、AF并生成分类标签

接下来是核心计算部分。这一段我强烈建议用向量化操作,不要用for循环。numpy和pandas处理十万条记录也只要几十毫秒,而Python原生for循环可能要等到你怀疑人生。

# 计算RA和AF # RA = 上升时间(µs) / 振幅(dB) df['RA'] = df['rise_time_us'] / df['amplitude_db'] # AF = 振铃计数 / 持续时间(ms) df['AF'] = df['counts'] / (df['duration_us'] / 1000.0) # 如果振幅不是dB而是电压值,先转换 # df['amplitude_db'] = 20 * np.log10(df['amplitude_uv'] / 1.0) # 设定判别线斜率k,默认100 k = 100 # 判别逻辑:AF >= RA / k 判为拉伸裂纹,否则判为剪切裂纹 df['crack_type'] = np.where(df['AF'] >= df['RA'] / k, 'Tensile', 'Shear') # 统计各类占比 type_counts = df['crack_type'].value_counts() print(type_counts)

这里有个细节想多说一句。为什么判别式不是一根竖直或水平线,而是一根斜线?因为RA和AF对裂纹类型的区分是联合的:拉伸裂纹既要看RA小,又要看AF大,两个条件要同时参与。如果只设一个固定阈值,比如AF超过300就是拉伸,那遇到一条高RA但高AF的信号就会误判。斜线虽然简单,但在物理上对应了两类裂纹波形的天然差异。

如果想看一下各类别的数值分布,可以用groupby快速统计。

# 按类别查看RA和AF的统计特征 group_stats = df.groupby('crack_type')[['RA', 'AF']].describe() print(group_stats)

3.3 绘制RA-AF散点分类图

画图是整个流程里最有“成果感”的一步。散点图里两种颜色分别对应拉伸和剪切,判别线标出来,图中的信息量已经足够用于报告了。

import matplotlib.pyplot as plt # 设置图表风格 plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] plt.rcParams['axes.unicode_minus'] = False fig, ax = plt.subplots(figsize=(10, 8)) # 按裂纹类型分组描点 colors = {'Tensile': '#1f77b4', 'Shear': '#d62728'} for ct, color in colors.items(): subset = df[df['crack_type'] == ct] ax.scatter(subset['RA'], subset['AF'], s=20, alpha=0.6, c=color, label=ct, edgecolors='none') # 绘制判别线 x_line = np.linspace(0, df['RA'].max() * 1.1, 200) y_line = x_line / k ax.plot(x_line, y_line, 'k--', linewidth=1.5, label=f'AF = RA / {k}') ax.set_xlabel('RA (µs/dB)', fontsize=12) ax.set_ylabel('AF (kHz)', fontsize=12) ax.set_title('RA-AF Crack Classification', fontsize=14) ax.legend(fontsize=11) ax.grid(True, linestyle='--', alpha=0.4) plt.tight_layout() plt.savefig('ra_af_classification.png', dpi=300) plt.show()

如果你的数据量特别大,比如超过几万个点,散点图会变成一坨黑,很难看清密度差异。这种情况我建议加密度参数或者直接画二维直方图。改用hexbin可以很好解决高密度的重叠问题。

fig, ax = plt.subplots(figsize=(10, 8)) hb = ax.hexbin(df['RA'], df['AF'], gridsize=80, cmap='viridis', bins='log') cb = fig.colorbar(hb, ax=ax) cb.set_label('log10(Count)') ax.plot(x_line, y_line, 'r--', linewidth=2, label=f'AF = RA / {k}') ax.set_xlabel('RA (µs/dB)', fontsize=12) ax.set_ylabel('AF (kHz)', fontsize=12) ax.legend() plt.show()

这招在数据量超过10万条时特别好用,能直观看出高密度区域落在判别线的哪一侧。

3.4 时间演化与裂纹比例统计图

RA-AF图看的是整体分布,但工程上更关心的是裂纹模式“随着加载过程怎么演变”。材料破坏一般是先拉伸微裂纹、后剪切裂纹为主,这个过程在RA-AF图上完全看不出来,必须引入时间维度。

做法是把事件按时间顺序分成若干个窗口,每个窗口内统计拉伸裂纹占比、剪切裂纹占比,然后画堆叠面积图或者折线图。如果没有时间戳,也可以用事件序号排序后分批,因为事件序号本身就对应时间先后顺序。

# 模拟时间列,实际数据中一般有time列 if 'time_s' not in df.columns: df['event_id'] = np.arange(len(df)) # 按窗口分组:例如每200个事件一组 window_size = 200 df['batch'] = df['event_id'] // window_size # 统计每个窗口内各类裂纹数量 summary = df.groupby('batch')['crack_type'].value_counts().unstack(fill_value=0) # 补充缺失列 for col in ['Tensile', 'Shear']: if col not in summary.columns: summary[col] = 0 summary['total'] = summary['Tensile'] + summary['Shear'] summary['tensile_ratio'] = summary['Tensile'] / summary['total'] summary['shear_ratio'] = summary['Shear'] / summary['total'] # 绘制堆叠面积图 fig, ax = plt.subplots(figsize=(12, 6)) ax.stackplot(summary.index, summary['tensile_ratio'], summary['shear_ratio'], labels=['Tensile', 'Shear'], colors=['#1f77b4', '#d62728'], alpha=0.85) ax.set_xlabel('Batch Index (200 events per batch)', fontsize=12) ax.set_ylabel('Crack Type Ratio', fontsize=12) ax.set_title('Crack Type Evolution over Time', fontsize=14) ax.legend(loc='upper left') ax.set_ylim(0, 1) ax.grid(True, linestyle='--', alpha=0.4) plt.tight_layout() plt.savefig('crack_evolution.png', dpi=300) plt.show()

这个图做出来之后,很多报告根本不需要额外解释,读者一眼就能看出破坏过程中拉伸、剪切裂纹的角色转换。我通常会在这个图下面再附一张累计事件数曲线,展示加载不同阶段声发射活动性,这样整条破坏链条就完整了。

3.5 交互式可视化方案

static图适合出报告,但如果自己日常分析,我推荐用Plotly做交互式版本。鼠标悬停能查看每个点的事件序号和特征参数,还能手动调k值即时更新分类,这对标定阈值非常有帮助。

import plotly.express as px import plotly.graph_objects as go fig = px.scatter(df, x='RA', y='AF', color='crack_type', title='RA-AF Interactive Classification', labels={'RA': 'RA (µs/dB)', 'AF': 'AF (kHz)'}) fig.add_trace(go.Scatter(x=x_line, y=y_line, mode='lines', name=f'AF = RA / {k}', line=dict(dash='dash', color='black'))) fig.show()

需要留意的是,Plotly在Jupyter Notebook里用很顺手,但数据量过大的时候前端渲染会卡。真到几十万点,可以先做降采样再画交互图,或者只用plotly画统计图而不是原始散点。

4. 常见问题与实操避坑记录

4.1 典型报错与数据陷阱

这个流程跑下来,最常规的报错基本集中在除零和单位两个问题上。我整理了一个速查表,基本覆盖了80%的情况。

问题现象可能原因排查思路
RA算出来有inf振幅为0或NaN检查数据清洗步骤,删掉振幅<=0的事件
AF整体偏大或偏小持续时间单位判断错误打印duration列的最大最小,确认是µs还是ms
RA分布异常大振幅不是dB而是电压值换算成dB再做除法
图上所有点都堆在判别线一侧k值严重不合理先按默认100跑,结合文献和材料类型调整
数据条数特别少清洗时误删了有效事件检查振幅下限是否设置过高,或使用usecols漏读了列
散点图糊成一团数据量过大、重叠严重改用hexbin或采样后再画

4.2 分类阈值怎么定才靠谱

k值的取舍是RA-AF方法里最主观的一个环节,也是初学者最容易问的问题。老实讲,这个值没有普适标准,它和材料、传感器频响、门槛设置都有关联。日本JCMS建议混凝土结构用80或200附近的值,但不同研究者的实验条件差异很大,照抄并不科学。

我自己的做法是:先在完整数据上画出hexbin密度图,看两个高密度区域自然分布的方向。如果两类点有一条明显的分界走向,就调整k值,让判别线沿着这条“谷地”走。然后拿已知破坏模式的数据做验证,比如三点弯曲梁试件,理论上加载初期拉伸裂纹占比高、破坏期剪切裂纹占比高,反复微调k值,直到时间演化图与这个物理预期吻合。这个过程本质上是在做人工标定,虽然朴素,但比凭空选k要可靠得多。

4.3 从结果到工程结论的经验

最后说几个出报告前必须养成的习惯。

一是不要只放一张RA-AF图就下结论。RA-AF是快速分类工具,不是精确定量方法。它给出的拉伸/剪切比例是趋势性结果,最好配合累积能量、幅值分布、定位结果综合判断。比如RA-AF显示剪切裂纹比例上升,同时定位图上损伤区域在扩展,判断才更有底。

二是纵向对比时保持参数一致。不同试件的k值、门槛值、传感器布置如果不一致,拉伸剪切比例直接对比没有意义。做批处理对比时,我习惯把所有数据放到一个脚本里,用同一套清洗和分类规则跑完,再汇总比例。

三是加异常值标注。RA-AF图上那些离群点,不一定全是噪声,可能对应着特殊的损伤模式,比如纤维拔出、层间剥离。我在分析时会单独把离群点标到时间轴上,看看它们是不是集中出现在特定加载阶段,这有时候能挖出比裂纹比例更有价值的信息。

对我来说,RA-AF分析真正好用的地方不在于它能给你一个“精确”的裂纹类型,而在于它能把成千上万次声发射事件压缩成一张图、一条演化曲线,让你在一个宏观尺度上看到材料破坏的模式切换过程。这一点配合Python的批处理和可复现能力,实验分析效率确实提升了一大截。如果你手头也有一批AE数据还没好好利用,按这套脚本跑一遍,把图发给我一起讨论也行。

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

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

立即咨询