如何找到周期性声音信号的频率?

How to find the frequency of a periodic sound signal?

我正在研究一种行走模式的声音信号,它具有明显的规则模式:

然后我想我可以使用 FFT 函数得到行走的频率(从图像中大约 1.7Hz):

    x = walk_5; % Walking sound with a size of 711680x2 double
    Fs = 48000; % sound frquency
    L=length(x); 

    t=(1:L)/Fs; %time base
    plot(t,x);
    figure;

    NFFT=2^nextpow2(L);      
    X=fft(x,NFFT);       
    Px=X.*conj(X)/(NFFT*L); %Power of each freq components       
    fVals=Fs*(0:NFFT/2-1)/NFFT;      
    plot(fVals,Px(1:NFFT/2),'b','LineSmoothing','on','LineWidth',1);         
    title('One Sided Power Spectral Density');       
    xlabel('Frequency (Hz)')         
    ylabel('PSD');

但是它并没有给我预期的结果:

FFT 结果:

缩放图像有很多噪音:

1.7Hz附近没有信息

这是日志域使用

的图表
    semilogy(fVals,Px(1:NFFT));

虽然它非常对称:

我没有发现我的代码有任何问题。您是否有任何解决方案可以轻松地从步行模式中提取 1.7Hz?

这里是 mat 中音频文件的 link https://www.dropbox.com/s/craof8qkz9n5dr1/walk_sound.mat?dl=0

非常感谢!

我建议您忘记 DFT 方法,因为您的信号由于多种原因不适合此类分析。即使通过查看您感兴趣的频率范围内的频谱,也没有简单的方法来估计峰值:

当然,您可以尝试使用 PSD/STFT 和其他时髦的方法,但这是一种矫枉过正的做法。对于这项任务,我可以想到两种相当简单的方法。


第一个仅基于自相关函数。

  1. 计算 ACF
  2. 定义它们之间的最小距离。由于您知道预期频率约为 1.7Hz,因此它对应于 0.58s。设0.5s为最小距离吧
  3. 计算找到的峰之间的平均距离。

这给了我大约 1.72 赫兹的频率。


第二种方法是基于观察到你的信号已经有一些周期性的峰值。因此我们可以使用 findpeaks 函数简单地搜索它们。

  1. 以与之前相同的方式定义最小峰距。
  2. 定义最小峰高。例如最大峰值的 10%。
  3. 求平均差值。

这给了我 1.7 赫兹的平均频率。

简单快捷的方法。显然还有一些可以改进的地方,比如:

  • 精炼门槛
  • 同时找到正峰和负峰
  • 处理一些丢失的峰,即由于低振幅

无论如何,这应该让你开始,而不是被蹩脚的 FFT 和懒惰的 semilogx 困住。


代码片段:

load walk_sound

fs = 48000;
dt = 1/fs;

x = walk_5(:,1);
x = x - mean(x);
N = length(x);
t = 0:dt:(N-1)*dt;

% FFT based
win = hamming(N);
X = abs(fft(x.*win));
X = 2*X(1:N/2+1)/sum(win);
X = 20*log10(X/max(abs(X)));
f = 0:fs/N:fs/2;

subplot(2,1,1)
plot(t, x)
grid on
xlabel('t [s]')
ylabel('A')
title('Time domain signal')

subplot(2,1,2)
plot(f, X)
grid on
xlabel('f [Hz]')
ylabel('A [dB]')
title('Signal Spectrum')

% Autocorrelation
[ac, lag] = xcorr(x);
min_dist = ceil(0.5*fs);
[pks, loc] = findpeaks(ac, 'MinPeakDistance', min_dist);

% Average distance/frequency
avg_dt = mean(gradient(loc))*dt;
avg_f = 1/avg_dt;

figure
plot(lag*dt, ac);
hold on
grid on
plot(lag(loc)*dt, pks, 'xr')
title(sprintf('ACF - Average frequency: %.2f Hz', avg_f))


% Simple peak finding in time domain
[pkst, loct] = findpeaks(x, 'MinPeakDistance', min_dist, ...
                            'MinPeakHeight', 0.1*max(x));

avg_dt2 = mean(gradient(loct))*dt;
avg_f2 = 1/avg_dt2;

figure
plot(t, x)
grid on
hold on
plot(loct*dt, pkst, 'xr')
xlabel('t [s]')
ylabel('A')
title(sprintf('Peak search in time domain - Average frequency: %.2f Hz', avg_f2))

这是一个绝妙的解决方案:

在进行 FFT 之前对原始数据取绝对值。数据包含大量高频噪声,淹没了信号中存在的任何低频周期性。高频噪声的幅度每 1.7 秒变大,幅度的增加是肉眼可见的,并且是周期性的,但是当您将信号乘以低频正弦波并将所有内容相加时,您仍然会得到接近零。取绝对值会改变这一点,使这些振幅调制在低频处呈周期性。

尝试使用以下代码比较常规数据的 FFT 和 abs(data) 的 FFT。请注意,我对您的代码进行了一些改动,例如将我假设的两个立体声通道组合成一个单声道。

x = (walk_5(:,1)+walk_5(:,2))/2; % Convert from sterio to mono
Fs = 48000; % sampling frquency
L=length(x); % length of sample
fVals=(0:L-1)*(Fs/L); % frequency range for FFT

walk5abs=abs(x); % Take the absolute value of the raw data

Xold=abs(fft(x)); % FFT of the data (abs in Matlab takes complex magnitude)
Xnew=abs(fft(walk5abs-mean(walk5abs))); % FFT of the absolute value of the data, with average value subtracted

figure;
plot(fVals,Xold/max(Xold),'r',fVals,Xnew/max(Xnew),'b')
axis([0 10 0 1])
legend('old method','new method')

[~,maxInd]=max(Xnew); % Index of maximum value of FFT
walkingFrequency=fVals(maxInd) % print max value

并绘制旧方法和新方法的 FFT,从 0 到 10 Hz 给出:

如您所见,它在大约 1.686 Hz 处检测到一个峰值,对于此数据,这是 FFT 频谱中的最高峰。