MATLAB滑动RMS如何接报警:窗口未满和缺样不能当成正常
平方、平均、开方并不是实时RMS最难的部分。真正容易出错的是:现在平均了多少个有效样本,它们是否覆盖同一段连续时间,当前"不报警"究竟是数值正常还是数据不足。本文把value、valid和alarm作为一个整体处理。
环境、运行与真实结果
实测环境:MATLAB R2025b(25.2.0.2998904),Windows PCWIN64,2026-10-19。主实验与独立复核仅依赖基础MATLAB;没有用Python/Octave结果代替MATLAB运行。
关键结果:W=32时第31号样本才首次有效;第125号NaN和140号Inf使125至171号窗口无效,172号恢复。
把下方完整代码保存为 causal_rms_demo.m,在该目录运行:
matlab
r = causal_rms_demo;
也可以从该目录执行 matlab -batch "r=causal_rms_demo;"。本次实际批处理由总入口调用同一份demo函数;这里给出单篇直接调用方式,未另行声称启动过第二种独立进程。默认在当前源码旁的 results 目录保存PNG、CSV、MAT。
原理、接口与边界
接口 causal_rms_contract(x,W,threshold) 对每个计划采样槽给出 value、valid、alarm、count。W=32、Fs=200 Hz、阈值为 3;完整 W 个计划槽都有限才有效。NaN、+Inf、-Inf 都是无效测量。未满窗或任一槽无效时 RMS=NaN、valid=false、alarm=false;阈值比较为严格大于,等于阈值不报警。
适用范围限定为实数 double:有效值可以是 0,非零绝对值须在 1e-12,1e6;范围外抛 RMS:Range。本例严格 RMS 对每个有效窗独立取 scale=max(abs(w)),计算 scale*sqrt(mean((w/scale).^2));全零窗单独返回 0。没有全历史 cumsum 能量,也没有大历史累计值相减。此范围内非零比值最小 1e-18,平方不会下溢。count 使用 movsum 处理 0/1 有效标志;工程用途仍应依据采样率和量纲设置范围与阈值,不把教学接口当成无限动态范围数值库。
| 实测事件(样本从 0 计数) | 结果 |
|---|---|
| 首个有效 RMS | n=31,0.155 s |
| 输入 n=80 从 2 跳到 4 | 首个阈值报警 n=93;延迟 13 点/65 ms |
| n=125 为 NaN、n=140 为 Inf | n=125...171 无效,n=172 恢复 |
| n=260...299 连续缺失 | n=260...330 无效,n=331 恢复 |
| 首样本的三种启动策略 | 严格 NaN;缩窗 2;补零 0.353553390593 |
| 忽略坏点后直接比较阈值 | 有 47 个原本质量不合格的窗口给出报警 |
47 个窗口称为"质量不合格的报警",不能笼统称假阳性,因为其中输入真实幅度确实高。W/Fs=0.16 s 是窗口名义时长,首末采样间跨度 (W-1)/Fs=0.155 s;报警延迟取决于输入、窗口和阈值,不等于固定 W/2。
对照策略分别使用补零、缩短启动窗口、忽略无效值。比较前把 Inf 显式转为 NaN;仅写 omitmissing 并不自动把 Inf 当缺失。图 causal_rms.png 展示启动偏差、有限样本计数、坏点离窗恢复与报警门控。独立复核用未缩放的逐窗平方和核对本例输入,并新增"大值离窗后恢复 1e-12 小值"、全零、W=1、阈值相等、全缺失和范围错误检查。
接入传感器时按以下顺序处理:
- 校准 ADC 到物理量单位,把 CRC、超量程、饱和或设备状态异常映射为无效槽。
- 保持固定时间栅格;丢帧插入 NaN,不删除行或压缩时间。
- 分包处理时保存上一包末尾 W-1 个计划槽(包括坏点);每包重新调用无状态函数会重启满窗等待。
- 同时消费 valid 与 alarm。无效窗口不代表传感器恢复正常,也不应自动解除上层安全锁存;数据质量告警单独处理。
- 根据业务另行确定滞回、连续触发次数和锁存策略,并用注入故障检验恢复边界。
看图定位错误

启动段要同时看数值与count,不能因为曲线已经画出来就认定有效。缺样区间的NaN不是零,恢复点172和331体现的是最后一个坏点离开整个32点窗口。忽略坏点产生的47次报警是质量不合格,不能直接称假阳性。
RMS包含直流能量,去均值标准差回答另一个问题。本例以每个计划槽为单位;如果删除丢样行后直接输入,就会把时间压缩,函数无法仅凭数组恢复真实时间。
完整可复制代码
以下代码包含全部局部函数、实验输入、断言与出图,不依赖另外下载的辅助脚本。不要把局部函数单独从外部调用;修改数据时在同一demo入口中调用它。
matlab
function r = causal_rms_demo(outDir)
% Fixed complete-window RMS and data-quality-aware alarm; base MATLAB.
if nargin<1,outDir=fullfile(fileparts(mfilename('fullpath')),'results');end
if ~exist(outDir,'dir'),mkdir(outDir);end
Fs=200;W=32;threshold=3;n=(0:399)';t=n/Fs;
truth=2*ones(size(n));truth(n>=80 & n<200)=4;
x=truth;x(n==125)=NaN;x(n==140)=Inf;x(n>=260 & n<=299)=NaN;
[strict,valid,alarm,count]=causal_rms_contract(x,W,threshold);
finite=~isnan(x) & ~isinf(x);clean=x;clean(~finite)=0;
sumSq=movsum(clean.^2,[W-1 0],'Endpoints','shrink');
zeroFilled=sqrt(sumSq/W);
omitInvalid=sqrt(sumSq./max(count,1));omitInvalid(count==0)=NaN;
normalizedMissing=x;normalizedMissing(~finite)=NaN;
shrinking=sqrt(movmean(normalizedMissing.^2,[W-1 0],'includemissing','Endpoints','shrink'));
assert(find(valid,1,'first')-1==W-1,'RMS:Startup','Full window contract differs.');
assert(all(~valid(n>=125 & n<=171)) && valid(n==172),'RMS:Recovery','Invalid-sample recovery differs.');
assert(all(~valid(n>=260 & n<=330)) && valid(n==331),'RMS:LongGap','Long-gap recovery differs.');
assert(all(~alarm(~valid)),'RMS:Gate','Invalid window must never alarm.');
assert(find(alarm,1,'first')-1==93,'RMS:AlarmTime','Alarm latency differs.');
assert(abs(shrinking(1)-2)<1e-12 && abs(zeroFilled(1)-2/sqrt(W))<1e-12, ...
'RMS:StartupControls','Startup policies not exposed.');
unqualifiedOmit=nnz((omitInvalid>threshold) & ~valid);
assert(unqualifiedOmit>0,'RMS:Omission','Omission failed to hide missing quality.');
tab=table(n,t,x,truth,count,valid,strict,alarm,shrinking,zeroFilled,omitInvalid, ...
'VariableNames',{'Sample','Time','Input','InjectedTruth','FiniteCount','Valid','StrictRMS', ...
'Alarm','ShrinkingRMS','ZeroFilledRMS','OmitInvalidRMS'});
writetable(tab,fullfile(outDir,'causal_rms_samples.csv'));
fig=figure('Visible','off','Color','w','Position',[80 80 1150 850]);
subplot(2,2,1);plot(n,truth,'k-',n,strict,'b-',n,zeroFilled,'r--',n,omitInvalid,'m:');grid on;
xlabel('Sample index');ylabel('Input / RMS');ylim([0 4.6]);
legend('Injected truth','Strict','Zero filled','Omit invalid','Location','best');
title('Missing values are not known zeros');
subplot(2,2,2);plot(n,shrinking,'m-',n,zeroFilled,'r--',n,strict,'b-');grid on;
xlim([0 45]);ylim([0 2.3]);xlabel('Sample index');ylabel('RMS');
legend('Shrinking','Zero filled','Strict','Location','best');title('No qualified estimate before sample 31');
subplot(2,2,3);stairs(n,count,'k-');hold on;stairs(n,W*double(valid),'b--');grid on;
xlim([110 190]);ylim([0 35]);xlabel('Sample index');ylabel('Finite samples in last 32');
legend('Count','32 when valid','Location','best');title('NaN at 125, Inf at 140: recovery at 172');
subplot(2,2,4);stairs(n,double(alarm),'b-');hold on;
stairs(n,double(omitInvalid>threshold),'m--');grid on;
xlim([70 230]);ylim([-0.1 1.2]);xlabel('Sample index');ylabel('Alarm state');
legend('Qualified alarm','Omit-invalid threshold only','Location','best');
title('Quality gating is separate from the RMS threshold');
set(findall(fig,'Type','axes'),'FontSize',10);
print(fig,fullfile(outDir,'causal_rms.png'),'-dpng','-r150');close(fig);
r=struct('Fs',Fs,'windowSamples',W,'sampleSpanSeconds',(W-1)/Fs, ...
'nominalWindowSeconds',W/Fs,'threshold',threshold, ...
'firstValidIndex',find(valid,1,'first')-1,'firstAlarmIndex',find(alarm,1,'first')-1, ...
'recoveryAfterInfIndex',172,'recoveryAfterLongGapIndex',331, ...
'unqualifiedOmitAlarmSamples',unqualifiedOmit,'startupShrinkingRMS',shrinking(1), ...
'startupZeroFilledRMS',zeroFilled(1),'validSamples',nnz(valid),'alarmSamples',nnz(alarm), ...
'contract','Every one of the latest W scheduled samples must be present and finite; otherwise RMS=NaN and alarm=false.');
save(fullfile(outDir,'causal_rms.mat'),'r','x','truth','t','n','strict','valid','alarm','count','tab');
fprintf('RMS PASS first_valid=%d first_alarm=%d recovery=172 long_gap_recovery=331 omit_unqualified=%d\n', ...
r.firstValidIndex,r.firstAlarmIndex,unqualifiedOmit);
fprintf('RMS startup shrinking=%.12g zero_filled=%.12g strict=NaN\n',shrinking(1),zeroFilled(1));
end
function [value,valid,alarm,count] = causal_rms_contract(x,W,threshold)
% One value per scheduled sample. NaN and either infinity mean missing.
validateattributes(x,{'double'},{'real','vector','nonempty'});
validateattributes(W,{'double'},{'scalar','integer','positive','finite'});
validateattributes(threshold,{'double'},{'real','scalar','positive','finite'});
x=x(:);finite=~isnan(x) & ~isinf(x);
% Educational sensor range: zero, or finite magnitudes in [1e-12, 1e6].
assert(all(x(finite)==0 | (abs(x(finite))>=1e-12 & abs(x(finite))<=1e6)), ...
'RMS:Range','Supported finite inputs are zero or magnitudes from 1e-12 to 1e6.');
count=movsum(double(finite),[W-1 0],'Endpoints','shrink');
valid=((1:numel(x))'>=W) & (count==W);
value=nan(size(x));
% Scale each independent window; no cumulative energy or historical subtraction.
for k=W:numel(x)
if valid(k)
w=x(k-W+1:k);scale=max(abs(w));
if scale==0,value(k)=0;else,value(k)=scale*sqrt(mean((w/scale).^2));end
end
end
alarm=valid & value>threshold;
end
本次如何交叉核对
独立检查用逐窗口直接平方和核对本例正常幅值,并测试全零、W=1、阈值恰好相等、全缺失和范围错误。另加入大值离窗后恢复到1e-12小值的例子,确认结果不会被全历史累计和污染。
这些是实际运行后的数值交叉检查;公开代码中的断言保证核心实验可直接复现。输出里的NaN表示该指标不能由当前记录确认,不应替换成0。
相关入口与复现问题
把一个坏点放在阈值附近,补零、忽略坏点与严格完整窗会在哪些采样点给出不同结论?
官方资料
MathWorks movmean、movsum用于核对后向窗、端点缩窗及缺失值处理。Inf必须显式归类,不能假定omitmissing自动代表所有无效测量。
资料核查于2026-10-19;当前官方页面可能显示较新版本,本文运行环境以实际R2025b记录为准。