周波数が2つのビンの中心の間にある場合、信号のピーク値を取得する


12

以下を想定してください:

  • 信号の基本周波数は、FFTといくつかの周波数推定法を使用して推定されており、2つのビン中心の間にあります
  • サンプリング周波数は固定です
  • 計算努力は問題ではありません

周波数がわかっている場合、基本的な信号の対応するピーク値を推定する最も正確な方法は何ですか?

1つの方法は、ビンの中心が推定周波数に近くなるように、FFT分解能を高めるために時間信号をゼロで埋めることです。このシナリオで、私が確信していない点の1つは、必要なだけゼロパッドできるか、そうすることでいくつかの欠点があるかどうかです。もう1つは、ピーク値を取得するものとしてゼロパディング後に選択するビン中心です(ゼロパディングの後でも対象の周波数に正確にヒットしない可能性があるため)。

しかし、周囲の2つのビンの中心のピーク値を使用して目的の周波数でのピーク値を推定する推定器など、より良い結果を提供できる別の方法があるかどうかも疑問に思っています。


2
FFTの前のゼロパディングは一方向です。もう1つの方法は、ニーズに合ったウィンドウ関数を適用することです。フラットトップウィンドウはまさにこの目的のために設計されました。もちろん、すでに周波数を正確に知っていて、1つのアンプルタイドだけに興味がある場合は、FFTよりも安価な方法がおそらくあります。
sellibitze

1
ゼロパディングは不要:単純な放物線補間(3ポイント:imax-1、imax、imax + 1、ここimaxでFFTピーク)は正確な結果を提供します
Basj

補間関数がウィンドウ関数と一致することを確認してください。フラットトップは簡単です。それ以外の場合は、一致するペアが必要です(たとえば、矩形ウィンドウ+ sinc補間、ガウスウィンドウ+ガウス補間など)
-finnw

@CedronDawgこの質問とその回答は、正確な頻度の式に関連しています(ただし、同じではありません)。面白いかもしれません。
-Fat32

回答:


5

思い浮かぶ最初のアルゴリズムは、Goertzelアルゴリズムです。このアルゴリズムは通常、対象の周波数が基本周波数の整数倍であると想定しています。ただし、このペーパーでは、関心のあるケースに(一般化された)アルゴリズムを適用します。


もう1つの問題は、信号モデルが正しくないことです。を使用し2*%pi*(1:siglen)*(Fc/siglen)ます。使用する必要があります2*%pi*(0:siglen-1)*(Fc/siglen)フェーズが正しく出てくるために。

また、周波数に問題があると思います Fc=21.3が非常に低い。低周波数の実数値信号は、位相/周波数推定の問題になるとバイアスを示す傾向があります。

また、位相推定の粗いグリッド検索を試みましたが、Goertzelアルゴリズムと同じ答えが得られました。

以下は、2つの異なる周波数Fc=21.3(実線)とFc=210.3(破線)の両方の推定値(Goertzel:blue、Coarse:red)のバイアスを示すプロットです。ご覧のとおり、高周波数のバイアスははるかに小さくなっています。

プロット γ軸が0から変化初期段階で2 πバツ2π

ここに画像の説明を入力してください


論文に基づいてGoerzelアルゴリズムのコードをテストしました。出力DTFT値を使用すると、ピークを非常に正確に取得できます。ただし、スケーリング係数は正確に1000です。したがって、元のピークが1,234の場合、Goerzelの後は1234になります。
lR8n6i

その間にいくつかの研究をしました。おそらく、それは振幅スケーリングに関係しています:スケーリング時間領域振幅=周波数領域係数* 2 / N、ここでNは信号の長さです。この仮定は正しいですか?
lR8n6i


こんにちは!Goertzelアルゴリズムを使用すると、結果の複素係数の振幅は非常に正確ですが、位相が完全に間違っていることがわかりました。誰かがこれがどこから来るのか考えていますか?「位相」とは、元の信号の基本で指定された位相遅れを意味します。
lR8n6i

1
ω0t+ϕj2[ejϕδω+ω0+2πke+jϕδωω0+2πk]π/2

4

2つだけでなく、複数の隣接するFFTビンを使用する場合、ウィンドウの幅に応じて、複雑なビンの結果間でウィンドウ化されたSinc補間を使用すると、非常に正確な推定値を生成できます。

ウィンドウ化されたSinc補間は、高品質のオーディオアップサンプラーで一般的に見られるため、その主題に関する論文には、エラー分析を備えた適切な補間式があります。


コメントありがとうございます。このアプローチも試してみます。
lR8n6i

4

πバツπバツ
とピーク振幅の周りの最終用途放物線補間であなたは素晴らしい結果を得ることができ、私はそれが最善の方法です検討し、今日は、私はそれを使用されており、その結果は常に非常にしっかりしている:-)

[1] JLフラナガンとRMゴールデン、「フェーズボコーダー」、Bell Systems Technical Journal、vol。45、pp。1493–1509、1966。

[2] K.ドレスラー、「多重解像度FFTの効率的な実装を使用した正弦波抽出」、Proc。第9国際空港 確認 デジタルオーディオエフェクト(DAFx-06)、カナダ、モントリオール、2006年9月、247〜252ページ。


こんにちは!あなたのすべてのコメントをありがとう。Goertzelフィルターと放物線ピーク補間を組み合わせて位相を取得するようにコードを拡張しました(以下を参照)。ただし、結果はまだ正確ではありません(±3-4度)。これはできるだけ近いものですか、それとも理解やコーディングに間違いがありますか?
lR8n6i


3

数年前、私はこの正確な問題に多くの困難を抱えていました。

この質問を投稿しました:

/programming/4633203/extracting-precise-frequencies-from-fft-bins-using-phase-change-between-frames

私は最初から計算を行うことになり、自分の質問に対する答えを投稿しました。

インターネットで同様の説明を見つけることができなかったことに驚いています。

ここに回答を再度投稿します。コードは、FFTウィンドウを4倍オーバーラップするシナリオ用に設計されていることに注意してください。

π


このパズルのロックを解除するには、2つのキーが必要です。

グラフ3.3:

ここに画像の説明を入力してください

グラフ3.4:

ここに画像の説明を入力してください

コード:

for (int k = 0; k <= fftFrameSize/2; k++) 
{
    // compute magnitude and phase 
    bins[k].mag = 2.*sqrt(fftBins[k].real*fftBins[k].real + fftBins[k].imag*fftBins[k].imag);
    bins[k].phase = atan2(fftBins[k].imag, fftBins[k].real);

    // Compute phase difference Δϕ fo bin[k]
    double deltaPhase;
    {
        double measuredPhaseDiff = bins[k].phase - gLastPhase[k];
        gLastPhase[k] = bins[k].phase;

        // Subtract expected phase difference <-- FIRST KEY
        // Think of a single wave in a 1024 float frame, with osamp = 4
        //   if the first sample catches it at phase = 0, the next will 
        //   catch it at pi/2 ie 1/4 * 2pi
        double binPhaseExpectedDiscrepancy = M_TWOPI * (double)k / (double)osamp;
        deltaPhase = measuredPhaseDiff - binPhaseExpectedDiscrepancy;

        // Wrap delta phase into [-Pi, Pi) interval 
        deltaPhase -= M_TWOPI * floor(deltaPhase / M_TWOPI + .5);
    }

    // say sampleRate = 40K samps/sec, fftFrameSize = 1024 samps in FFT giving bin[0] thru bin[512]
    // then bin[1] holds one whole wave in the frame, ie 44 waves in 1s ie 44Hz ie sampleRate / fftFrameSize
    double bin1Freq = (double)sampleRate / (double)fftFrameSize;
    bins[k].idealFreq = (double)k * bin1Freq;

    // Consider Δϕ for bin[k] between hops.
    // write as 2π / m.
    // so after m hops, Δϕ = 2π, ie 1 extra cycle has occurred   <-- SECOND KEY
    double m = M_TWOPI / deltaPhase;

    // so, m hops should have bin[k].idealFreq * t_mHops cycles.  plus this extra 1.
    // 
    // bin[k].idealFreq * t_mHops + 1 cycles in t_mHops seconds 
    //   => bins[k].actualFreq = bin[k].idealFreq + 1 / t_mHops
    double tFrame = fftFrameSize / sampleRate;
    double tHop = tFrame / osamp;
    double t_mHops = m * tHop;

    bins[k].freq = bins[k].idealFreq + 1. / t_mHops;
}

OPは周波数を認識しており、振幅を補間したいのに対して、周波数を補間しています。
finnw

2

このpythonコードは、放物線補間(McAulay Quatieri、Serraなどが高調波+残差で成功的に使用する方法)で非常に正確な結果(多くの音符に使用し、半音の0.01%未満のエラーを取得しました)を提供します分離技術)

import matplotlib.pyplot as plt
import numpy as np
from scipy.io.wavfile import read
from scipy.fftpack import fft, ifft
import math

(fs, x) = read('test.wav')
if (len(x.shape) == 2):    # if stereo we keep left channel only
 x = x[:,1]

n=x.size
freq = np.arange(n)*1.0/n*fs 
xfft = abs(fft(x))

imax=np.argmax(xfft)  
p=1.0/2*(xfft[imax-1]/xfft[imax]-xfft[imax+1]/xfft[imax])/(xfft[imax-1]/xfft[imax]-2+xfft[imax+1]/xfft[imax])   # parabolic interpolation 
print 'Frequence detectee avec interpolation parabolique :',(imax+p)*1.0/n*fs, 'Hz'

1
clear all
clc

for phase_orig = 0:pi/18:pi,

%% Specify and generate signal
Amp = 1;                     % Amplitude of signal
Fs = 8000;                   % samples per second
dt = 1/Fs;                   % seconds per sample
Fc = 21.3;                   % Hz
StopTime = 0.25;             % seconds
t = (0:dt:StopTime-dt)';     % seconds

siglen = length(t);
sig = Amp * 1.5 * sin(2*pi*(0:siglen-1)*(Fc/siglen) + phase_orig) + 1.5 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 3) ...
  + 1.5 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 5)+ 0.3 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 7) ...
  + 1.3 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 9)+ 1.4 * Amp * sin(2*pi*(0:siglen-1)*(Fc/siglen) * 11);

%% Estimate the peak value of the signals fundamental using Goertzel algorithm
peak = 0;
indvec = [Fc-1 Fc Fc+1];

% Check the input data
if ~isvector(sig) || isempty(sig)
  error('X must be a nonempty vector')
end

if ~isvector(indvec) || isempty(indvec)
  error('INDVEC must be a nonempty vector')
end
if ~isreal(indvec)
  error('INDVEC must contain real numbers')
end

% forcing x to be column
sig = reshape(sig,siglen,1);

% initialization
no_freq = length(indvec); %number of frequencies to compute
y = zeros(no_freq,1); %memory allocation for the output coefficients

% Computation via second-order system
% loop over the particular frequencies
for cnt_freq = 1:no_freq
  %for a single frequency:
  %a/ precompute the constants
  pik_term = 2*pi*(indvec(cnt_freq))/(siglen);
  cos_pik_term2 = cos(pik_term) * 2;
  cc = exp(-1i*pik_term); % complex constant
  %b/ state variables
  s0 = 0;
  s1 = 0;
  s2 = 0;
  %c/ 'main' loop
  for ind = 1:siglen-1 %number of iterations is (by one) less than the length of signal
    %new state
    s0 = sig(ind) + cos_pik_term2 * s1 - s2;  % (*)
    %shifting the state variables
    s2 = s1;
    s1 = s0;
  end
  %d/ final computations
  s0 = sig(siglen) + cos_pik_term2 * s1 - s2; %correspond to one extra performing of (*)
  y(cnt_freq) = s0 - s1*cc; %resultant complex coefficient

  %complex multiplication substituting the last iterationA
  %and correcting the phase for (potentially) non-integer valued
  %frequencies at the same time
  y(cnt_freq) = y(cnt_freq) * exp(-1i*pik_term*(siglen-1));
end

  % perfom amplitude scaling
  peak = abs(y(2)) * 2 / siglen

% perform parabolic interpolation to get the phase estimate
phase_orig=phase_orig*180/pi
ym1 = angle(unwrap(y(1)));
y0 = angle(unwrap(y(2)));
yp1 = angle(unwrap(y(3)));

p = (yp1 - ym1)/(2*(2*y0 - yp1 - ym1)); 
phase = y0 - 0.25*(ym1-yp1)*p;
phase_est = phase * 180/pi + 90;
phase_est = mod(phase_est+180,360)-180
end

扱っている周波数(8kHzでサンプリングされた21.3Hz)は非常に低いです。これらは実数値の信号であるため、**任意**周波数の位相推定にバイアスが生じます。

この画像が示すバイアスのプロット(phase_est - phase_orig)のためにFc = 210.3;(赤)のバイアス対Fc = 21.3;。ご覧のとおり、この場合、オフセットははるかに重要です21.3

別のオプションは、サンプリングレートを下げることです。緑の曲線は、のFs = 800代わりのバイアスを示してい8000ます。

ここに画像の説明を入力してください


1
更新していただきありがとうございます!私のプロットをご覧ください。私はまだ位相推定器はこの低周波数にバイアスをかけると考えています。これを回避する1つの方法は、既知の周波数(既知の場合!)を使用して、ルックアップテーブルを介して位相推定バイアスを修正することです。ただし、注意が必要です。バイアスは周波数とともに変化します。別の方法は、サンプリングレートを下げることです。
ピーターK。

1
こちらこそありがとう!ただし、210.3の代わりにFs = 8000 HzとFc = 210を使用している場合、バイアスはさらに悪くなります。これはどこから来るのでしょうか?
lR8n6i

1
エルク!わからない。FWIW、Geortzel推定量に問題はありません:goertzel = atan(imag(y(2)),real(y(2)))*180/%pi + 90;。:-)もう少し掘り下げます。このスペースをご覧ください。
ピーターK。

1
放物線補間は、あなたが思っていることをしていません。あなたがあなたの計算を交換する場合は特に、pp2 = (abs(y(3)) - abs(y(1)))/(2*(2*abs(y(2)) - abs(y(3)) - abs(y(1)))); phase2 = y0 - 0.25*(ym1-yp1)*p2;あなたはさえのために---より良い答えを得ますFc=210。の現在のバージョンがp賢明なものを提供してくれるかどうかはまったくわかりません。補間式は放物線の振幅をp補間するためのものですが、ちょうど...奇数の位相を補間しています。
ピーターK。

1
p = (yp1 - ym1)/(2*(2*y0 - yp1 - ym1))振幅の代わりにPHASESを使用している場合、ピーク位置()が時々不正確になることを除いて、これらはすべて問題ありません。これは、位相 +/- 180度の境界を飛び越える可能性があるためです。フェーズで修正するために必要なのは、その行をp2上記の計算に変更することだけです。
ピーターK。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.