web-dev-qa-db-ja.com

DSP-FFTによる周波数領域でのフィルタリング

私はFFTのExocortex実装で少し遊んでいますが、いくつか問題があります。

IFFTを呼び出す前に周波数ビンの振幅を変更すると、特に低周波数が信号に存在する場合(ドラムやベースなど)、結果の信号にクリック音やポップ音が含まれます。ただし、すべてのビンを同じ係数で減衰させた場合、これは発生しません。

4サンプルFFTの出力バッファの例を挙げましょう。

// Bin 0 (DC)
FFTOut[0] = 0.0000610351563
FFTOut[1] = 0.0

// Bin 1
FFTOut[2] = 0.000331878662
FFTOut[3] = 0.000629425049

// Bin 2
FFTOut[4] = -0.0000381469727
FFTOut[5] =  0.0

// Bin 3, this is the first and only negative frequency bin.
FFTOut[6] =  0.000331878662
FFTOut[7] = -0.000629425049

出力はフロートのペアで構成され、それぞれが単一のビンの実数部と虚数部を表します。したがって、ビン0(配列インデックス0、1)は、DC周波数の実数部と虚数部を表します。ご覧のとおり、ビン1と3は両方とも同じ値です(ただし、 Im部分の符号)なので、ビン3が最初の負の周波数であり、最後にインデックス(4、5)が最後の正の周波数ビンになると思います。

次に、周波数ビン1を減衰させるために、これを実行します。

// Attenuate the 'positive' bin
FFTOut[2] *= 0.5;
FFTOut[3] *= 0.5;

// Attenuate its corresponding negative bin.
FFTOut[6] *= 0.5;
FFTOut[7] *= 0.5;

実際のテストでは、1024の長さのFFTを使用しており、常にすべてのサンプルを提供しているため、0パディングは必要ありません。

// Attenuate
var halfSize = fftWindowLength / 2;
float leftFreq = 0f;
float rightFreq = 22050f; 
for( var c = 1; c < halfSize; c++ )
{
    var freq = c * (44100d / halfSize);

    // Calc. positive and negative frequency indexes.
    var k = c * 2;
    var nk = (fftWindowLength - c) * 2;

    // This kind of attenuation corresponds to a high-pass filter.
    // The attenuation at the transition band is linearly applied, could
    // this be the cause of the distortion of low frequencies?
    var attn = (freq < leftFreq) ? 
                    0 : 
                    (freq < rightFreq) ? 
                        ((freq - leftFreq) / (rightFreq - leftFreq)) :
                        1;

    // Attenuate positive and negative bins.
    mFFTOut[ k ] *= (float)attn;
    mFFTOut[ k + 1 ] *= (float)attn;
    mFFTOut[ nk ] *= (float)attn;
    mFFTOut[ nk + 1 ] *= (float)attn;
}

明らかに私は何か間違ったことをしているが、何を理解することができない。

非常に基本的な動的イコライザーを実装しようとしているので、FIR係数のセットを生成する手段としてFFT出力を使用したくありません。

周波数領域でフィルタリングする正しい方法は何ですか?私が欠けているものは何ですか?

また、負の周波数も本当に減衰させる必要がありますか?ネガティブなFFT実装を見てきました。周波数値は合成前にゼロにされます。

前もって感謝します。

20
Trap

FFTの使用方法と特定のフィルターの2つの問題があります。

フィルタリングは、従来、時間領域での畳み込みとして実装されていました。入力信号とフィルター信号のスペクトルを乗算することは同等です。ただし、離散フーリエ変換(DFT)(速度のために高速フーリエ変換アルゴリズムで実装)を使用する場合、実際には、真のスペクトルのサンプルバージョンを計算します。これには多くの影響がありますが、フィルタリングに最も関連するのは、時間領域信号が周期的であるという影響です。

これが例です。周期が1.5サイクルの正弦波入力信号xと、単純なローパスフィルターhについて考えてみます。 Matlab/Octave構文の場合:

_N = 1024;
n = (1:N)'-1; %'# define the time index
x = sin(2*pi*1.5*n/N); %# input with 1.5 cycles per 1024 points
h = hanning(129) .* sinc(0.25*(-64:1:64)'); %'# windowed sinc LPF, Fc = pi/4
h = [h./sum(h)]; %# normalize DC gain

y = ifft(fft(x) .* fft(h,N)); %# inverse FT of product of sampled spectra
y = real(y); %# due to numerical error, y has a tiny imaginary part
%# Depending on your FT/IFT implementation, might have to scale by N or 1/N here
plot(y);
_

そして、これがグラフです: IFFT of product

ブロックの最初のグリッチは、私たちが期待するものではありません。しかし、fft(x)を検討する場合、それは理にかなっています。離散フーリエ変換は、信号が変換ブロック内で周期的であると想定しています。 DFTが知る限り、私たちはこの1つの期間の変換を要求しました。 Aperiodic input to DFT

これは、DFTでフィルタリングする場合の最初の重要な考慮事項につながります。実際には、線形畳み込みではなく、 巡回畳み込み を実装しています。したがって、最初のグラフの「グリッチ」は、数学を考えると実際にはグリッチではありません。したがって、問題は次のようになります。周期性を回避する方法はありますか?答えはイエスです:使用 重複保存処理 。基本的に、上記のようにN-long積を計算しますが、N/2ポイントのみを保持します。

_Nproc = 512;
xproc = zeros(2*Nproc,1); %# initialize temp buffer
idx = 1:Nproc; %# initialize half-buffer index
ycorrect = zeros(2*Nproc,1); %# initialize destination
for ctr = 1:(length(x)/Nproc) %# iterate over x 512 points at a time
    xproc(1:Nproc) = xproc((Nproc+1):end); %# shift 2nd half of last iteration to 1st half of this iteration
    xproc((Nproc+1):end) = x(idx); %# fill 2nd half of this iteration with new data
    yproc = ifft(fft(xproc) .* fft(h,2*Nproc)); %# calculate new buffer
    ycorrect(idx) = real(yproc((Nproc+1):end)); %# keep 2nd half of new buffer
    idx = idx + Nproc; %# step half-buffer index
end
_

そして、これがycorrectのグラフです。 ycorrect

この図は理にかなっています。フィルターからの起動トランジェントが予想され、結果は定常状態の正弦波応答に落ち着きます。現在、xは任意の長さにすることができます。制限はNproc > 2*min(length(x),length(h))です。

次に、2番目の問題である特定のフィルターについて説明します。ループでは、スペクトルが本質的にH = [0 (1:511)/512 1 (511:-1:1)/512]';であるフィルターを作成します。hraw = real(ifft(H)); plot(hraw)を実行すると、次のようになります。 hraw

見づらいですが、グラフの左端にゼロ以外の点がたくさんあり、右端にさらにたくさんあります。 Octaveの組み込みのfreqz関数を使用して、表示される周波数応答を確認します(freqz(hraw)を実行することにより)。 freqz(hraw)

振幅応答には、ハイパスエンベロープからゼロまでの多くのリップルがあります。この場合も、DFTに固有の周期性が機能しています。 DFTに関する限り、hrawは何度も繰り返されます。しかし、hrawのように、freqzの1周期を取ると、そのスペクトルは周期バージョンのものとはかなり異なります。

それでは、新しい信号を定義しましょう。hrot = [hraw(513:end) ; hraw(1:512)];生のDFT出力を回転させて、ブロック内で連続させます。次に、freqz(hrot)を使用して周波数応答を見てみましょう。 freqz(hrot)

ずっといい。すべての波紋がなく、目的のエンベロープがそこにあります。もちろん、実装は今ではそれほど単純ではありません。各複素数ビンをスケーリングするだけでなく、完全な複素数にfft(hrot)を掛ける必要がありますが、少なくとも正しい答えが得られます。

速度については、通常、パッド付きのhのDFTを事前に計算することに注意してください。元のデータと比較しやすいように、ループ内にそのままにしておきました。

36
mtrw

あなたの主な問題は、周波数が短い時間間隔で十分に定義されていないことです。これは特に低周波数に当てはまります。そのため、そこで最も問題に気づきます。

したがって、サウンドトレインから非常に短いセグメントを取り出してフィルター処理すると、フィルター処理されたセグメントは連続波形を生成する方法でフィルター処理されず、セグメント間のジャンプが聞こえます。これがクリックを生成します。 。

たとえば、いくつかの妥当な数値を取り上げます。27.5Hz(ピアノではA0)の波形から始め、44100 Hzでデジタル化すると、次のようになります(赤い部分の長さは1024サンプル)。

alt text

したがって、最初に40Hzのローパスから始めます。したがって、元の周波数は40Hz未満であるため、カットオフが40Hzのローパスフィルターは実際には効果がなく、入力とほぼ正確に一致する出力が得られます。正しい? 間違った、間違った、間違った–そしてこれは基本的にあなたの問題の核心です。問題は、短いセクションでは、27.5 Hzのideaが明確に定義されておらず、DFTで適切に表現できないことです。

下の図のDFTを見ると、短いセグメントでは27.5Hzが特に意味がないことがわかります。長いセグメントのDFT(黒い点)は27.5 Hzにピークを示しますが、短いセグメント(赤い点)はそうではないことに注意してください。

alt text

明らかに、40Hz未満でフィルタリングすると、DCオフセットがキャプチャされ、40Hzローパスフィルターの結果が下に緑色で表示されます。

alt text

青い曲線(200 Hzのカットオフで取得)は、はるかによく一致し始めています。しかし、それをうまく一致させているのは低周波数ではなく、高周波数が含まれていることに注意してください。短いセグメントに可能なすべての周波数(最大22KHz)を含めるまで、最終的に元の正弦波を適切に表現することはできません。

このすべての理由は、27.5 Hzの正弦波の小さなセグメントがnot27.5 Hzの正弦波であり、DFTにないためです。 27.5Hzと関係があります。

11
tom10

DC周波数サンプルの値をゼロに減衰させていますか?例ではまったく減衰していないようです。ハイパスフィルターを実装しているため、次のように設定する必要があります。 DC値もゼロになります。

これは低周波歪みを説明します。遷移が大きいためにDC値がゼロ以外の場合、低周波数での周波数応答に多くのリップルが発生します。

何が起こっているのかを示すためのMATLAB/Octaveの例を次に示します。

N = 32;
os = 4;
Fs = 1000;
X = [ones(1,4) linspace(1,0,8) zeros(1,3) 1 zeros(1,4) linspace(0,1,8) ones(1,4)];
x = ifftshift(ifft(X));
Xos = fft(x, N*os);
f1 = linspace(-Fs/2, Fs/2-Fs/N, N);
f2 = linspace(-Fs/2, Fs/2-Fs/(N*os), N*os);

hold off;
plot(f2, abs(Xos), '-o');
hold on;
grid on;
plot(f1, abs(X), '-ro');
hold off;
xlabel('Frequency (Hz)');
ylabel('Magnitude');

frequency response

私のコードでは、DC値がゼロ以外で、その後にゼロに急激に変化し、その後ランプアップする例を作成していることに注意してください。次に、IFFTを使用して変換します。次に、その時間領域信号に対してゼロパディングfft(入力信号よりも大きいfftサイズを渡すとMATLABによって自動的に実行されます)を実行します。時間領域でのゼロパディングの結果これを使用して、フィルターがフィルターサンプル間でどのように応答するかを確認できます。

覚えておくべき最も重要なことの1つは、DFTの出力を減衰させることによって特定の周波数でフィルター応答値を設定している場合でも、これはサンプルポイント間で発生する周波数に対して何も保証しないことです。これは、変更が急激であるほど、サンプル間のオーバーシュートと振動が発生することを意味します。

ここで、このフィルタリングをどのように行うべきかについての質問に答えます。いくつかの方法がありますが、実装と理解が最も簡単な方法の1つは、ウィンドウの設計方法です。現在の設計の問題は、遷移幅が大きいことです。ほとんどの場合、リップルをできるだけ少なくして、できるだけ迅速に遷移する必要があります。

次のコードでは、理想的なフィルターを作成し、応答を表示します。

N = 32;
os = 4;
Fs = 1000;
X = [ones(1,8) zeros(1,16) ones(1,8)];
x = ifftshift(ifft(X));
Xos = fft(x, N*os);
f1 = linspace(-Fs/2, Fs/2-Fs/N, N);
f2 = linspace(-Fs/2, Fs/2-Fs/(N*os), N*os);

hold off;
plot(f2, abs(Xos), '-o');
hold on;
grid on;
plot(f1, abs(X), '-ro');
hold off;
xlabel('Frequency (Hz)');
ylabel('Magnitude'); 

frequency response

急激な変化による振動が多いことに注意してください。

FFTまたは離散フーリエ変換は、フーリエ変換のサンプルバージョンです。フーリエ変換は、無限大から無限大までの連続範囲の信号に適用されますが、DFTは有限数のサンプルに適用されます。これは事実上、有限数のサンプルのみを処理しているため、DFTを使用する場合、時間領域で正方形のウィンドウ処理(切り捨て)が発生します。残念ながら、方形波のDFTはsincタイプの関数(sin(x)/ x)です。

フィルタに鋭い遷移(1つのサンプルで0から1へのクイックジャンプ)がある場合の問題は、これが時間領域で非常に長い応答を持ち、正方形のウィンドウによって切り捨てられることです。したがって、この問題を最小限に抑えるために、時間領域信号にさらに段階的なウィンドウを掛けることができます。次の行を追加してハニングウィンドウを乗算すると、次のようになります。

x = x .* hanning(1,N).';

iFFTを実行した後、次の応答が返されます。

frequency response

したがって、ウィンドウの設計方法はかなり単純なので、実装を試みることをお勧めします(より良い方法はありますが、より複雑になります)。イコライザーを実装しているので、減衰をその場で変更できるようにしたいので、パラメーターが変更されたときはいつでも周波数領域でフィルターを計算して保存することをお勧めします。そうすれば、それを適用できます。入力バッファのfftを取得し、周波数領域フィルタのサンプルを乗算してから、ifftを実行して時間領域に戻すことにより、各入力オーディオバッファに接続します。これは、各サンプルに対して実行しているすべての分岐よりもはるかに効率的です。

2
Jason B

まず、正規化について:これは既知の(非)問題です。 DFT/IDFTは、それぞれに係数1/sqrt(N)(標準の余弦/正弦係数を除く)を必要とします(逆を直接)それらを対称的で真に可逆にするために。もう1つの可能性は、それらの1つ(直接または逆)を[〜#〜] n [〜#〜]で除算することです。これは問題です。便利さと味の。多くの場合、FFTルーチンはこの正規化を実行しません。ユーザーはそれを認識し、好みに応じて正規化することが期待されます。 参照

2番目:(たとえば)16ポイントDFTでは、bin 0と呼ばれるものは、ゼロ周波数(DC)に対応しますビン1低周波数...ビン4中周波数、ビン8を最高周波数に、ビン9 ... 15を「負の周波数」に。あなたの例では、bin 1は実際には低周波数と中周波数の両方です。この考慮事項を除けば、「イコライゼーション」には概念的に問題はありません。 「信号が低周波数で歪む」の意味がわかりません。それをどのように観察しますか?

1
leonbloy