Cod*_*ray 7 algorithm matlab signal-processing time-series data-analysis
我一直在尝试实时检测正弦时间序列数据中的峰值,但是到目前为止我还没有成功。我似乎找不到一种实时算法,可以以合理的准确度检测正弦信号中的峰值。我要么没有检测到峰值,要么在正弦波上有无数个点被检测为峰值。
对于类似于正弦波并且可能包含一些随机噪声的输入信号,有什么好的实时算法?
作为一个简单的测试案例,考虑一个频率和幅度始终相同的固定正弦波。(确切的频率和幅度无关紧要;我随意选择了 60 Hz 的频率,+/- 1 个单位的幅度,采样率为 8 KS/s。)以下 MATLAB 代码将生成这样的正弦曲线信号:
dt = 1/8000;
t = (0:dt:(1-dt)/4)';
x = sin(2*pi*60*t);
Run Code Online (Sandbox Code Playgroud)
使用Jean-Paul 开发和发布的算法,我要么没有检测到峰值(左),要么检测到无数个“峰值”(右):
我已经尝试了我能想到的这 3 个参数的几乎所有值组合,遵循让-保罗给出的“经验法则”,但到目前为止我还没有得到我预期的结果。
我找到了由 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);
Run Code Online (Sandbox Code Playgroud)
这是一个更不寻常(不太均匀/规则)的信号,具有变化的频率和幅度,但通常仍是正弦波。绘制时,峰值对眼睛来说是显而易见的,但很难用算法识别。
正确识别正弦输入信号中的峰值的好的实时算法是什么?在信号处理方面,我并不是真正的专家,因此获得一些考虑正弦输入的经验法则会很有帮助。或者,也许我需要修改例如 Jean-Paul 的算法本身,以便在正弦信号上正常工作。如果是这种情况,需要进行哪些修改,我将如何进行这些修改?
Jea*_*aul 21
如果您的正弦曲线不包含任何噪声,您可以使用一种非常经典的信号处理技术:取一阶导数并检测它何时等于零。
例如:
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
Run Code Online (Sandbox Code Playgroud)
使用您的示例数据:
% 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');
Run Code Online (Sandbox Code Playgroud)
这产生:
这种方法对于任何类型的正弦曲线都非常有效,唯一的要求是输入信号不包含噪声。
一旦您的输入信号包含噪声,微分方法就会失败。例如:
% 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);
Run Code Online (Sandbox Code Playgroud)
现在将生成此结果,因为第一个差异会放大噪声:
现在处理噪声的方法有很多种,最标准的方法是应用移动平均滤波器。移动平均线的一个缺点是它们适应新信息的速度很慢,因此信号可能会在信号发生后被识别(移动平均线有滞后)。
另一种非常典型的方法是使用傅立叶分析来识别输入数据中的所有频率,忽略所有低幅度和高频正弦曲线,并将剩余的正弦曲线用作滤波器。剩余的正弦波将(大部分)从噪声中清除,然后您可以再次使用一阶差分来确定波峰和波谷(或者对于单个正弦波,您知道波峰和波谷发生在 1/4 和 3/4 pi阶段)。我建议您拿起任何信号处理理论书籍来了解有关此技术的更多信息。Matlab 也有一些关于这方面的教育材料。
如果你想在硬件中使用这个算法,我建议你也看看 WFLC(加权傅立叶线性组合器),例如 1 个振荡器或 PLL(锁相环),它可以估计噪声波的相位,而无需执行完整的快速傅立叶变换。您可以在 Wikipedia 上找到锁相环的 Matlab 算法。
我将在这里建议一种稍微复杂的方法,它可以实时识别波峰和波谷:使用移动最小二乘最小化和傅立叶分析的初始估计,将正弦波函数拟合到您的数据中。
这是我的功能:
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
Run Code Online (Sandbox Code Playgroud)
如您所见,我首先执行傅立叶变换以找到数据幅度和频率的初始估计值。然后我使用模型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]);
Run Code Online (Sandbox Code Playgroud)
这种方法的主要优点是:
interval
代码中的参数)缺点是您需要选择回溯window
,但是您使用任何用于实时检测的方法都会遇到此问题。
Data
是输入数据,Model fit
是数据的拟合正弦波(见代码),Signal
表示波峰和波谷,并Signals marked on data
给出算法准确度的印象。注意:观察模型拟合将自身调整到图表中间的趋势!
这应该让你开始。还有很多关于信号检测理论的优秀书籍(只是谷歌那个术语),它们将更深入地研究这些类型的技术。祝你好运!