做声发射(Acoustic Emission,简称AE)实验的人应该都有这种体会:传感器贴好、门槛设好、数据采了一大堆,最后导出来的是一长串“事件表”——时间、幅度、能量、振铃计数、持续时间,密密麻麻几万行。真正头疼的不是采集,而是怎么从这堆数字里看出材料到底损伤到什么程度了。HI(历史指数)和Sr(强度比)就是专门干这个的两个统计指标,它们能从信号强度序列里提炼出“损伤是不是在加速”的关键信息。这篇文章我就用Matlab把这两个指标从定义到代码到绘图完整走一遍,提供一个可以直接复现的流程,适合正在做混凝土损伤评估、岩石破裂预警、复合材料失效监测这类方向的研究生和工程师参考。
先说明一下,这里的Sr是声发射强度分析里的Severity Ratio,跟数字电路里的SR锁存器、触发器完全是两码事,别搜错方向了。我自己第一次接触这两个指标时也绕了不少弯路,尤其是HI的k值取值规则,不同文献写法还不一样,懵了很久。这次我把踩过的坑和验证过能跑的代码一并整理出来,你照着做基本能一次跑通。
1. 声发射强度分析的整体思路与指标含义
1.1 为什么要用“信号强度”而不是“幅度”
声发射系统导出的参数里,幅度(Amplitude)和能量(Energy)是最常被直接拿来看的。幅度直观,但在统计意义上有个问题:它只取了波形峰值那一个点,对损伤过程的整体能量释放不敏感。比如一次大的裂纹扩展事件,幅度可能只比普通摩擦事件高一点,但它的持续时间和包络面积远远超过普通事件,这个差异幅度体现不出来。
所以做强度分析时,我更推荐使用“信号强度”(Signal Strength),也就是信号包络对时间的积分面积,单位通常是pVs(皮伏秒)。物理含义可以理解成“这个事件总共释放了多少可被传感器捕捉的振动能量”。把几万个事件的信号强度串成一个序列,分析这个序列的统计特征变化,比单独看某一个幅度值可靠得多。
HI和Sr这两个指标的设计思路,本质上回答两个问题:第一,最近发生的这一批事件,平均强度是不是比全历史平均要高?第二,当前事件序列里,有没有出现异常剧烈的单次事件?前者反映“损伤趋势”,后者反映“极端事件”。两者结合起来,就能对材料当前所处的损伤阶段做相对靠谱的判断。
1.2 HI和Sr的数学定义与参数选择
关于HI的定义,比较常见的形式是:
code复制HI(n) = n * sum(S(k:n)) / ((n - k + 1) * sum(S(1:n)))
其中n是当前累计的事件数,S是信号强度序列,k的取值为:当n小于等于20时,k=1;当n大于20时,k=floor(n/20)。这个式子的含义可以拆成两部分看:分母的sum(S(1:n))/n是全部事件的平均强度,分子的sum(S(k:n))/(n-k+1)是“最近”一部分事件的平均强度。两个平均值一比,就得到一个相对值。
那k为什么要取floor(n/20)呢?我的理解是,这个窗口大约覆盖最近95%的事件(因为k约等于n/20,窗口长度就是n-k+1约等于0.95n),也就是把最老的5%事件剔除掉,尽量避免早期偶然的强信号对“近期平均值”造成干扰。当n比较小的时候,数据量不足,没法做这种剔除,所以k直接取1,这时候HI恒等于1,表示“暂无趋势判断”。
Sr的定义相对简洁:
code复制Sr(n) = n * max(S(1:n)) / sum(S(1:n))
这个式子等价于max(S(1:n))除以平均值,也就是当前所有事件中最大强度是平均强度的多少倍。如果Sr持续在1附近徘徊,说明事件强度分布很均匀;如果Sr突然往上冲,说明出现了远超平均水平的大事件,这在混凝土梁开裂、岩石破裂实验中通常意味着宏观裂纹的形成。
我在实际使用中还有一个体会:HI更适合观察缓慢的趋势变化,Sr更适合捕捉突发的临界事件。单独看任何一个都可能误判,组合起来用才稳。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 实验数据准备与预处理
2.1 数据从哪里来,长什么样
声发射系统(比如MISTRAS、Vallen、Sensoreye等)导出的数据通常是表格形式,每一行是一个声发射事件,列包括时间戳、幅度、能量、上升时间、持续时间、振铃计数、信号强度等。做HI和Sr分析,理论上只需要“信号强度”这一列,但强烈建议连同时间戳一起导入,因为后续可能要做时间窗口分析,而且可以用时间戳检查事件顺序是否有异常。
如果你手头还没有实测数据,想先跑通算法,我建议用Matlab生成一组带趋势和突变点的模拟数据。这组数据要模拟出材料损伤的三个阶段:初始稳定期、损伤发展期、临近破坏期,其中最后阶段还要加入几个突发的大强度事件。
matlab复制% 生成模拟AE信号强度序列
rng(42); % 固定随机种子,保证可复现
N = 200; % 总事件数
trend = linspace(80, 1200, N)'; % 强度整体上升趋势
S = trend + 80 * randn(N, 1); % 叠加噪声
S = max(S, 5); % 避免出现负值或接近0
% 人为加入几个突发强事件,模拟宏观开裂
S(120) = 3500;
S(145) = 5200;
S(170) = 7800;
S(185) = 11000;
这样生成的序列就具备了一个比较典型的AE事件强度特征:前面平稳、中间爬升、后面出现尖峰。后面算完HI和Sr,你会发现曲线在120、145、170、185这几个位置出现明显的响应,正好对应“异常事件”。
2.2 导入真实数据的标准操作
如果是真实实验数据,最常见的格式是CSV或者Excel表格。Matlab里我一般这样读:
matlab复制% 读取AE系统导出的表格(假设列名为Time, Amplitude, SignalStrength等)
T = readtable('ae_data.csv');
S_raw = T.SignalStrength; % 取出信号强度列
t = T.Time; % 取出时间列
读进来之后,别急着算指标,先做三件检查:
第一,排序。某些系统的原始导出文件不一定是严格按时间递增排列的,尤其是你手动合并过多个文件的情况下。先按时间排列,保证事件序列的物理顺序正确。
matlab复制[t_sorted, idx] = sort(t);
S = S_raw(idx);
第二,有效性过滤。部分事件可能因为触发异常,强度值极小(几乎为0)或者出现负值。这类事件虽然不会让程序报错,但会严重污染统计结果。我用的是这个规则:强度小于某阈值(比如最大强度的0.01%)的事件直接剔除,或者标记为无效。
第三,单位统一。不同AE系统的“信号强度”单位可能不同,有的是pVs,有的是aJ(attojoule),有的是无量纲的计数。如果在同一批次分析里混用了不同单位的数据,HI和Sr的绝对值会失真。建议在分析之前全部换算成同一单位。
2.3 先画一张原始数据图,心里有底
我每次做正式分析前都会先把信号强度序列画出来看一眼,这个习惯帮我避免了很多“算完才发现数据有问题”的尴尬。你不需要用多复杂的指令:
matlab复制figure;
stem(S, 'MarkerSize', 2);
xlabel('事件序号');
ylabel('信号强度 (pVs)');
title('原始AE信号强度序列');
这一步不是为了出图发表,而是让你对数据有一个直观认知:强度大致在什么量级?有没有明显的突变点?有没有大量的零值或离群值?心里有底之后,再进入正式计算。
3. 核心算法实现:HI和Sr的逐点计算
3.1 为什么要用循环“逐点累积”
HI和Sr有个共同特点:它们是随事件数n动态变化的。也就是说,每来一个新事件,都要重新用“目前所有已发生事件”的统计数据,计算当前时刻的HI和Sr。这种计算方式决定了它天然适合用一个从1到N的循环来写。
有些朋友会觉得Matlab里循环慢,想用向量化一次算完。但这两个指标在每个n处的计算公式都依赖前n个数据的统计量,强行向量化反而绕圈子,而且后期看不懂。我的建议是:先用清晰的for循环把逻辑写出来,如果实测数据量达到几万、几十万条再考虑优化,优化方法我在第5章会专门讲。
3.2 完整代码与逐行解释
下面的代码就是HI和Sr计算的核心,我把它写成了一个独立的脚本,方便你直接复制运行。
matlab复制%% 输入:S为信号强度列向量
N = length(S);
HI = zeros(N, 1); % 预分配历史指数数组
Sr = zeros(N, 1); % 预分配强度比数组
for n = 1:N
% 当前累积总强度
sum_all = sum(S(1:n));
% 防止所有强度全为0的情况
if sum_all == 0
HI(n) = NaN;
Sr(n) = NaN;
continue;
end
% 确定k值:n<=20时取1,否则取floor(n/20)
if n <= 20
k = 1;
else
k = floor(n / 20);
end
% 计算近期窗口内的强度总和
sum_recent = sum(S(k:n));
% 历史指数:近期平均强度 / 全局平均强度
HI(n) = n * sum_recent / ((n - k + 1) * sum_all);
% 强度比:最大强度 / 平均强度
Sr(n) = n * max(S(1:n)) / sum_all;
end
逐行说几个容易被忽略的细节:
sum_all == 0这个判断,实战中经常用到。比如你采集到一段全是门槛附近微小振动的数据,强度序列可能全是0,这时候不判断就会得到NaN,后续画图全是断线。置成NaN至少能让你在图上看出“这一段数据无效”,而不是被一个除以0的Inf误导。
k = floor(n / 20)这一步是HI计算最关键的参数。我前面踩过坑:最早我用了round(n/20),结果在小样本阶段k值跳变不规律,HI曲线出现锯齿状的异常跳动;后来统一改成floor,和大部分文献的取值方式对齐,曲线就干净多了。如果你在复现别人论文里的HI曲线时形状对不上,先检查一下对方用的是floor、round还是ceil,这是最常见的分歧来源。
max(S(1:n))在循环里每次都会从头扫描一遍当前所有数据,数据量小没问题,数据量大时这是性能瓶颈之一。后面讲优化时我会说怎么用递推方式替代。
3.3 前20个事件为什么HI恒等于1
很多初学者第一次看到HI曲线前20个点都是直线1,会以为程序写错了。实际上这就是定义决定的:n小于等于20时k=1,分子里的sum(S(1:n))和分母里的sum(S(1:n))完全一样,n / ((n - 1 + 1))也等于1,所以HI恒为1。
这个设计是有道理的:只有20个数据点时,任何统计指标都没有足够的样本量来支撑“趋势”判断。强行计算出来的HI波动也不可信,干脆让它回到基准值1,表示“数据量不足,不做趋势解释”。当n逐渐增大,k开始随n/20缓慢移动,HI才开始反映真实的强度趋势。
Sr则不同,它从一开始就能算,因为只要有一个事件,max和平均值就有意义。所以Sr曲线前面部分通常没有HI那种平台段,会直接开始波动。
4. 绘图复现与结果解读方法
4.1 四子图布局怎么做
算完HI和Sr,下一步就是画图。我建议用2×2的子图布局,把原始强度、累积强度、HI、Sr放在同一个figure里,方便对照分析。下面是完整的绘图代码。
matlab复制figure('Color', 'w', 'Position', [100 100 1000 750]);
% 子图1:原始信号强度
subplot(2, 2, 1);
stem(S, 'MarkerSize', 3, 'MarkerFaceColor', [0.3 0.5 0.7], ...
'MarkerEdgeColor', 'none');
xlabel('事件序号'); ylabel('信号强度 (pVs)');
title('(a) AE信号强度序列');
grid on; box on;
% 子图2:累积信号强度
subplot(2, 2, 2);
plot(cumsum(S), 'LineWidth', 1.5, 'Color', [0.2 0.3 0.6]);
xlabel('事件序号'); ylabel('累积强度 (pVs)');
title('(b) 累积信号强度');
grid on; box on;
% 子图3:历史指数HI
subplot(2, 2, 3);
plot(HI, 'LineWidth', 1.8, 'Color', [0.85 0.3 0.25]);
hold on;
yline(1, '--', '基准线', 'LineWidth', 1.2);
xlabel('事件序号'); ylabel('HI');
title('(c) 历史指数 Historic Index');
grid on; box on;
% 子图4:强度比Sr
subplot(2, 2, 4);
plot(Sr, 'LineWidth', 1.8, 'Color', [0.2 0.6 0.4]);
xlabel('事件序号'); ylabel('Sr');
title('(d) 强度比 Severity Ratio');
grid on; box on;
% 保存为高分辨率图片
exportgraphics(gcf, 'AE_HI_Sr_analysis.png', 'Resolution', 300);
这里用了stem画原始强度,好处是能清楚看到每个事件的位置和大小,尤其是突发强事件会表现为一根突兀的竖线。画HI时加了yline(1, '--'),这条基准线是必须的,因为HI大于1才有“异常”含义,没有基准线很难一眼看出趋势偏离程度。
4.2 关键参数与样式设置建议
如果你要发论文,图片样式有几点值得注意:
字体大小统一用set(gca, 'FontSize', 10)或者直接在xlabel、ylabel里指定'FontSize', 11。中文显示可能需要额外设置,我习惯把图里的标签都写成英文,避免Matlab默认字体不支持中文而出现方框乱码。
线宽建议在1.5到2之间,太细印出来看不清,太粗会盖住细节。曲线颜色尽量选对比度高的组合,比如红色、蓝色、绿色,避免用黄色,在白色背景上太浅。
图片保存用exportgraphics,这个函数是R2020a之后引入的,比旧的print函数清晰度高,而且能正确保留矢量信息。如果你的Matlab版本较老,可以用print(gcf, '-dpng', '-r300', 'AE_HI_Sr_analysis.png')代替。
4.3 图形上如何判断损伤阶段
模拟数据跑完之后,典型的结果是这样演变的:
前80个事件左右,强度在80到300之间波动,HI基本贴着1走,说明近期平均强度和全历史平均强度差不多,材料处于稳定期。到第120个事件,出现了一个3500的强事件,HI开始缓慢爬升,因为这一下把近期窗口的平均值抬高了;Sr则更敏感,直接从1跳到了接近10,说明当前序列里出现了一个远超平均的大事件。
到第145、170、185这几个点,随着突发事件的增多,HI的爬升速度明显加快,因为新事件的强度不仅高,而且占比越来越大;Sr则维持在高位震荡,每一次新的大事件都会让Sr重新冲高。这个阶段对应的是损伤快速发展期,宏观裂纹大概率正在形成和扩展。
所以在实际工程中,我一般这样读图:先看Sr有没有突然跳变,如果有,说明刚发生了显著事件;再看HI有没有连续抬升,如果有,说明损伤不是一次性偶然事件,而是进入了趋势性加速阶段。两个条件同时满足,就需要重点关注甚至停机检查。
5. 真实数据量下的性能优化与常见坑
5.1 一万条数据开始变慢?用前缀和优化
第3章的循环写法在几百条数据上运行毫无压力,但真实AE实验一次可能采到几万甚至几十万条事件。这时每次循环都调用sum(S(1:n))、sum(S(k:n))、max(S(1:n))会带来很大的重复计算量,时间复杂度接近O(N^2),跑起来会很痛苦。
优化的思路有三个层次。
第一层,预分配数组。前面代码里已经做了,这能避免Matlab在循环中动态扩展数组的开销,是提升性能的基础。
第二层,用前缀和(cumsum)把两个sum变成O(1)查询。
matlab复制prefix = cumsum(S); % 前缀和数组,prefix(n) = sum(S(1:n))
for n = 1:N
sum_all = prefix(n);
if sum_all == 0
HI(n) = NaN; Sr(n) = NaN;
continue;
end
if n <= 20
k = 1;
else
k = floor(n / 20);
end
if k == 1
sum_recent = prefix(n);
else
sum_recent = prefix(n) - prefix(k - 1);
end
HI(n) = n * sum_recent / ((n - k + 1) * sum_all);
Sr(n) = n * max(S(1:n)) / sum_all;
end
第三层,max(S(1:n))也可以用递推替代。维护一个current_max变量:
matlab复制current_max = 0;
for n = 1:N
if S(n) > current_max
current_max = S(n);
end
% 这里current_max就等于max(S(1:n))
end
把这三层优化组合起来,几十万条数据也能在几秒内算完,实测下来效率提升非常明显。我在处理一批5万条的混凝土梁数据时,优化前的脚本跑了将近五分钟,优化后眨眼的功夫就出结果了。
5.2 五个最容易踩的坑
先说说我自己踩过和帮别人排查过的几个高频问题。
强度序列里有零值事件时,分母可能变成0。解决方式是代码里加if sum_all == 0的判断,同时在实际分析中可以考虑把有效事件阈值设高一点,把门槛事件过滤掉。我见过有同行的数据里零值事件占到30%以上,这种数据直接算HI和Sr,结果基本没有参考价值。
k取整方式不一致导致HI曲线对不上。这个是复现论文时的重灾区。不同文献对k的定义有floor(n/20)、round(n/20)、ceil(n/20)三种写法,甚至有的用n/20的小数部分。如果你算出的HI曲线和论文里的形状差很多,先检查对方在方法部分有没有明确写k的取整规则。没有明确写的论文,只能靠试参数来复现,试的时候就从floor开始,因为它最常用。
强度值跨度过大导致绘图时小事件被压成一条线。比如数据从几十到上万,如果用线性坐标,大部分小事件的强度柱状图会完全贴在地板上。解决办法是改用对数坐标:set(gca, 'YScale', 'log'),虽然HI和Sr本身是无量纲比值,不需要取对数,但原始强度图取对数能让细节更清楚。
事件数太少时HI平台期太长。如果你的数据总共只有30个事件,那么前20个事件HI都是1,后面只有10个点在变动,整个曲线信息量很小。这种情况应该考虑降低k的阈值,比如把20改成10,或者直接用另一种定义里“n小于100时k=1”的规则。不过改了参数要在报告里明确说明,否则别人复现不了。
多组数据拼接时事件序号混乱。有时候一个试件有两组传感器数据,直接横着拼在一起,导致强度序列在中间出现断层。方法是按时间排序后再整体分析,或者分时间段分别计算HI和Sr,观察两个时间段的指标变化。
5.3 验证算法正确性的简单方法
写完之后怎么确认你算的HI和Sr是对的?我习惯造一组“理想数据”来验证。
比如构造一个全部是1的序列,长度为50:
matlab复制S_test = ones(50, 1);
按定义,前20个HI=1,后面因为所有值都相等,分子分母算出来也是1,所以整个HI序列应该全部等于1。Sr也全部等于1。如果程序跑出来不是这样,那基本就是公式写错了。
再比如构造一个前50个全1、第51个突然变成10的序列:
matlab复制S_test2 = ones(51, 1);
S_test2(51) = 10;
这个时候第51点的Sr应该等于11(最大值10除以平均值10/11?不对,最大值是10,平均值是(50+10)/51 = 60/51 ≈ 1.176,Sr=10/1.176≈8.5)。HI在第51点应该明显大于1,因为它把近期平均值拉高了。这种简单的数据可以手工算出期望值,再用程序对比,能快速定位公式错误。
6. 扩展应用方向与指标组合思路
6.1 和b值、RA值等其他AE参数一起看
HI和Sr不是声发射分析里仅有的统计指标。做岩石力学的人常用b值(Gutenberg-Richter关系的斜率),它反映小事件和大事件的数量比例;做混凝土损伤的常用RA值(上升时间/幅度)和平均频率联合图,用来区分拉伸裂纹和剪切裂纹。
这些指标和HI/Sr其实是互补关系。b值下降意味着大事件比例增加,这和Sr突然升高往往是同步的;RA值的突变则能告诉你裂纹类型正在变化。我在做综合判断时,通常把HI、Sr、b值、RA值四条曲线放在同一个时间轴上看,相互印证,比只看任何一个指标都可靠得多。
6.2 基于HI和Sr的简易预警逻辑
如果你想把分析结果做成一个自动预警模块,可以设计一个非常简单的逻辑规则:
当Sr超过设定阈值(比如5)且HI持续大于1.2时,判定为“一级预警”,提示近期出现了显著大事件且强度趋势上升。当Sr超过10且HI超过1.5时,判定为“二级预警”,建议立即停机检查。
阈值怎么定?不要拍脑袋。我建议先收集同一个试件或同一批试件在完好阶段的HI和Sr分布,用95%分位数作为基准阈值。不同材料、不同加载方式的阈值差异很大,没有统一标准,只有相对标准。
用Matlab写这个逻辑很简单,就是两个条件判断:
matlab复制warning_level = zeros(N, 1);
for n = 1:N
if Sr(n) > 10 && HI(n) > 1.5
warning_level(n) = 2;
elseif Sr(n) > 5 && HI(n) > 1.2
warning_level(n) = 1;
end
end
6.3 从脚本到一个小工具
我自己的经验是,脚本再好用,换个人就不会跑了。如果课题组有同学要复用你的代码,最好用Matlab的App Designer做一个简单界面,让使用者只需要选择文件、点击“分析”、就能自动出图,这样推广起来方便得多。
不过这是后话,前提还是先把核心算法和绘图逻辑吃透。HI和Sr本身并不复杂,复杂的是对数据的理解和对结果的解读。你把模拟数据跑通、真实数据验证过、阈值得出过合理范围之后,再做工具就是水到渠成的事情。
最后再分享一个我在实际项目中养成的小习惯:每次分析完,把参数设置(k的取整规则、Sr定义、数据过滤阈值)写进注释或者单独存一个文本文件。因为过三个月回头再看自己的代码,真的会忘记当时为什么这么设置。有了记录,不仅自己能复用,别人拿过你的代码也能跑得明白,这才是“可复现”的真正意义。
