噪声正弦时间序列中的实时峰值检测

Posted

技术标签:

【中文标题】噪声正弦时间序列中的实时峰值检测【英文标题】:Real-time peak detection in noisy sinusoidal time-series 【发布时间】:2020-04-20 19:09:09 【问题描述】:

我一直在尝试实时检测正弦时间序列数据中的峰值,但是到目前为止我还没有成功。我似乎无法找到一种实时算法,可以以合理的准确度检测正弦信号中的峰值。我要么没有检测到峰值,要么沿着正弦波得到无数个点被检测为峰值。

对于类似正弦波且可能包含一些随机噪声的输入信号,什么是好的实时算法?


作为一个简单的测试用例,考虑一个频率和幅度始终相同的平稳正弦波。 (确切的频率和幅度无关紧要;我任意选择了 60 Hz 的频率,幅度为 +/- 1 个单位,采样率为 8 KS/s。)以下 MATLAB 代码将生成这样的正弦曲线信号:

dt = 1/8000;
t  = (0:dt:(1-dt)/4)';
x  = sin(2*pi*60*t);

使用the algorithm developed and published by Jean-Paul,我要么没有检测到峰(左),要么检测到无数个“峰”(右):

在the "rules of thumb" that Jean-Paul gives 之后,我已经尝试了这 3 个参数的值的几乎所有组合,但我至今无法获得预期的结果。


我找到了一种替代算法,developed and published by Eli Billauer,它确实给了我想要的结果——例如

尽管 Eli Billauer 的算法要简单得多,并且确实倾向于可靠地产生我想要的结果,但它并不适合实时应用程序。


作为我想应用这种算法的信号的另一个示例,考虑 Eli Billauer 为他自己的算法给出的测试用例:

t = 0:0.001:10;
x = 0.3*sin(t) + sin(1.3*t) + 0.9*sin(4.2*t) + 0.02*randn(1, 10001);

这是一个更不寻常(不太均匀/规则)的信号,具有不同的频率和幅度,但通常仍为正弦波。绘制时,峰值很明显,但很难通过算法识别。


什么是正确识别正弦输入信号中的峰值的好的实时算法?在信号处理方面我不是真正的专家,所以得到一些会有所帮助考虑正弦输入的经验法则。或者,也许我需要修改例如Jean-Paul 的算法本身是为了在正弦信号上正常工作。如果是这种情况,需要进行哪些修改,我将如何进行这些修改?

【问题讨论】:

您的信号是纯正弦波还是包含其他噪声(如 Eli Billauer 的情况)? @Jean-Paul 我对这两种情况都感兴趣......实际上,我想在几个不同的应用程序中使用它。 问题不够清楚。它缺乏研究,要求的是一种算法,而不是一个具体的问题。 【参考方案1】:

案例1:正弦波没有噪声

如果您的正弦曲线不包含任何噪声,您可以使用非常经典的信号处理技术:取一阶导数并检测它何时等于零。

例如:

function signal = derivesignal( d )

% Identify signal
signal = zeros(size(d));
for i=2:length(d)
    if d(i-1) > 0 && d(i) <= 0
        signal(i) = +1;     % peak detected
    elseif d(i-1) < 0 && d(i) >= 0
        signal(i) = -1;     % trough detected
    end
end

end

使用您的示例数据:

% Generate data
dt = 1/8000;
t  = (0:dt:(1-dt)/4)';
y  = sin(2*pi*60*t);

% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';

% Approximate first derivative (delta y / delta x)
d = [0; diff(y)];

% Identify signal
signal = derivesignal(d);

% Plot result
figure(1); clf; set(gcf,'Position',[0 0 677 600])
subplot(4,1,1); hold on;
title('Data');
plot(t,y);
subplot(4,1,2); hold on;
title('First derivative');
area(d);
ylim([-0.05, 0.05]);
subplot(4,1,3); hold on;
title('Signal (-1 for trough, +1 for peak)');
plot(t,signal); ylim([-1.5 1.5]);
subplot(4,1,4); hold on;
title('Signals marked on data');
markers = abs(signal) > 0;
plot(t,y); scatter(t(markers),y(markers),30,'or','MarkerFaceColor','red');

这会产生:

这种方法对于任何类型的正弦曲线都非常有效,唯一的要求是输入信号不包含噪声。


案例2:正弦噪声

一旦您的输入信号包含噪声,导数方法就会失败。例如:

% Generate data
dt = 1/8000;
t  = (0:dt:(1-dt)/4)';
y  = sin(2*pi*60*t);

% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';

% Add some noise
y = y + 0.2.*randn(2000,1);

现在会生成这个结果,因为first differences amplify noise:

现在处理噪音的方法有很多,最标准的方法是申请moving average filter。移动平均线的一个缺点是它们适应新信息的速度很慢,因此信号可能会在它们发生后被识别(移动平均线有滞后)。

另一种非常典型的方法是使用Fourier Analysis 来识别输入数据中的所有频率,忽略所有低幅度和高频正弦波,并使用剩余的正弦波作为滤波器。剩余的正弦波将(大部分)从噪声中清除,然后您可以再次使用一阶差分来确定波峰和波谷(或者对于单个正弦波,您知道波峰和波谷发生在 1/4 和 3/4 pi相)。我建议您阅读任何信号处理理论书籍以了解有关此技术的更多信息。 Matlab 也有一些关于这个的educational material。

如果你想在硬件中使用这个算法,我建议你也看看 WFLC (Weighted Fourier Linear Combiner),例如1 个振荡器或 PLL (Phase-Locked Loop), 无需进行完整的快速傅立叶变换即可估计噪声波的相位。您可以找到锁相环的 Matlab 算法on Wikipedia。

我将在这里建议一种稍微复杂一点的方法,该方法将实时识别波峰和波谷:使用移动 least squares minimization 和傅立叶分析的初步估计,为您的数据拟合正弦波函数。

这是我的功能:

function [result, peaks, troughs] = fitsine(y, t, eps)

% Fast fourier-transform
f = fft(y);
l = length(y);
p2 = abs(f/l);
p1 = p2(1:ceil(l/2+1));
p1(2:end-1) = 2*p1(2:end-1);
freq = (1/mean(diff(t)))*(0:ceil(l/2))/l;

% Find maximum amplitude and frequency
maxPeak = p1 == max(p1(2:end)); % disregard 0 frequency!
maxAmplitude = p1(maxPeak);     % find maximum amplitude
maxFrequency = freq(maxPeak);   % find maximum frequency

% Initialize guesses
p = [];
p(1) = mean(y);         % vertical shift
p(2) = maxAmplitude;    % amplitude estimate
p(3) = maxFrequency;    % phase estimate
p(4) = 0;               % phase shift (no guess)
p(5) = 0;               % trend (no guess)

% Create model
f = @(p) p(1) + p(2)*sin( p(3)*2*pi*t+p(4) ) + p(5)*t;
ferror = @(p) sum((f(p) - y).^2);
% Nonlinear least squares
% If you have the Optimization toolbox, use [lsqcurvefit] instead!
options = optimset('MaxFunEvals',50000,'MaxIter',50000,'TolFun',1e-25);
[param,fval,exitflag,output] = fminsearch(ferror,p,options);

% Calculate result
result = f(param);

% Find peaks
peaks = abs(sin(param(3)*2*pi*t+param(4)) - 1) < eps;

% Find troughs
troughs = abs(sin(param(3)*2*pi*t+param(4)) + 1) < eps;

end

如您所见,我首先执行Fourier transform 来查找数据幅度和频率的初始估计值。然后,我使用模型 a + b sin(ct + d) + et 将正弦曲线拟合到数据中。拟合值代表一个正弦波,我知道 +1 和 -1 分别是波峰和波谷。因此,我可以将这些值识别为信号。

这对于具有(缓慢变化)趋势和一般(白)噪声的正弦曲线非常有效:

% Generate data
dt = 1/8000;
t  = (0:dt:(1-dt)/4)';
y  = sin(2*pi*60*t);

% Add some trends
y(1:1000) = y(1:1000) + 0.001*(1:1000)';
y(1001:2000) = y(1001:2000) - 0.002*(1:1000)';

% Add some noise
y = y + 0.2.*randn(2000,1);

% Loop through data (moving window) and fit sine wave
window = 250;   % How many data points to consider
interval = 10;  % How often to estimate
result = nan(size(y));
signal = zeros(size(y));
for i = window+1:interval:length(y)
    data = y(i-window:i);   % Get data window
    period = t(i-window:i); % Get time window
    [output, peaks, troughs] = fitsine(data,period,0.01);
    
    result(i-interval:i) = output(end-interval:end);
    signal(i-interval:i) = peaks(end-interval:end) - troughs(end-interval:end);
end

% Plot result
figure(1); clf; set(gcf,'Position',[0 0 677 600])
subplot(4,1,1); hold on;
title('Data');
plot(t,y); xlim([0 max(t)]); ylim([-4 4]);
subplot(4,1,2); hold on;
title('Model fit');
plot(t,result,'-k'); xlim([0 max(t)]); ylim([-4 4]);
subplot(4,1,3); hold on;
title('Signal (-1 for trough, +1 for peak)');
plot(t,signal,'r','LineWidth',2); ylim([-1.5 1.5]);
subplot(4,1,4); hold on;
title('Signals marked on data');
markers = abs(signal) > 0;
plot(t,y,'-','Color',[0.1 0.1 0.1]);
scatter(t(markers),result(markers),30,'or','MarkerFaceColor','red');
xlim([0 max(t)]); ylim([-4 4]);

这种方法的主要优点是:

您拥有数据的实际模型,因此您可以在信号发生之前预测未来的信号! (例如修复模型并通过输入未来时间段来计算结果) 您不需要每期都估计模型(见代码中的参数interval

缺点是需要选择回溯window,但是任何用于实时检测的方法都会有这个问题。

视频演示

Data 是输入数据,Model fit 是拟合到数据的正弦波(见代码),Signal 表示波峰和波谷,Signals marked on data 表示算法的准确度。 注意:观察模型拟合调整到图表中间的趋势!

这应该可以帮助您入门。还有很多关于信号检测理论的优秀书籍(只需 google 那个术语),它们将更深入地介绍这些类型的技术。祝你好运!

【讨论】:

【参考方案2】:

考虑使用 findpeaks,它速度很快,这对于实时来说可能很重要。您应该过滤高频噪声以提高准确性。这里我用一个移动的窗口来平滑数据。

t = 0:0.001:10;
x = 0.3*sin(t) + sin(1.3*t) + 0.9*sin(4.2*t) + 0.02*randn(1, 10001);
[~,iPeak0] = findpeaks(movmean(x,100),'MinPeakProminence',0.5);

您可以为该过程计时(0.0015 秒)

f0 = @() findpeaks(movmean(x,100),'MinPeakProminence',0.5)
disp(timeit(f0,2))

为了比较,处理斜率只是稍微快一点(0.00013 秒),但 findpeaks 有很多有用的选项,例如峰之间的最小间隔等。

iPeaks1 = derivePeaks(x);
f1 = @() derivePeaks(x)
disp(timeit(f1,1))

derivePeaks 在哪里:

function iPeak1 = derivePeaks(x)
xSmooth = movmean(x,100);
goingUp = find(diff(movmean(xSmooth,100)) > 0);
iPeak1 = unique(goingUp([1,find(diff(goingUp) > 100),end]));
iPeak1(iPeak1 == 1 | iPeak1 == length(iPeak1)) = [];
end

【讨论】:

以上是关于噪声正弦时间序列中的实时峰值检测的主要内容,如果未能解决你的问题,请参考以下文章

数字信号处理相关函数应用 ( 正弦信号 的 自相关函数 分析 二 | 在白噪声中检测正弦信号 )

数字信号处理相关函数应用 ( 正弦信号 的 自相关函数 分析 二 | 在白噪声中检测正弦信号 )

如何测正弦信号的峰值?(测峰值的电路是啥?)

实时时间序列数据中的峰值信号检测Matlab R Golang Python Swift Groovy C ++ C ++ Rust Scala Kotlin Ruby Fortran Julia C

怎么用ADC采样正弦信号来计算峰值

生成的正弦波 pcm 声音中的静态噪声