一般的な非矩形ウィンドウ関数はすべて対称に見える。FFTの前に非対称ウィンドウ関数を使用したい場合はありますか?(FFTアパーチャの片側のデータがもう一方のデータよりも少し重要である、またはノイズが少ないなど)
もしそうなら、どんな種類の非対称窓関数が研究されており、それらは(より損失の多い)オフセット対称窓と比較して周波数応答にどのように影響しますか?
一般的な非矩形ウィンドウ関数はすべて対称に見える。FFTの前に非対称ウィンドウ関数を使用したい場合はありますか?(FFTアパーチャの片側のデータがもう一方のデータよりも少し重要である、またはノイズが少ないなど)
もしそうなら、どんな種類の非対称窓関数が研究されており、それらは(より損失の多い)オフセット対称窓と比較して周波数応答にどのように影響しますか?
回答:
「ウィンドウ関数」に省略形ウィンドウを使用します。
オーディオでは、プリリンギングまたはプリエコーに似た何かを作成する処理は、低ビットレートmp3のようにだらしない音になります。これは、たとえば、重複修正離散コサイン変換(MDCT)などの重複変換でスペクトルデータを変更することにより、過渡またはインパルスの局所エネルギーが時間的に後方に広がるときに発生します。このような処理では、オーディオはオーバーラップする分析ウィンドウによってウィンドウ化され、変換され、周波数ドメインで処理され(データ圧縮がより小さいビットレートにされるなど)、合成ウィンドウで再びウィンドウ化され、再び合計されます。分析および合成ウィンドウの積は、重なり合うウィンドウの合計が1になるようにする必要があります。
従来、使用されるウィンドウ関数は対称であり、その幅は周波数選択性(長いウィンドウ)と時間領域のアーティファクト回避(短いウィンドウ)の間の妥協点でした。ウィンドウが広いほど、処理が信号を拡散できる時間は長くなります。より最近の解決策は、非対称ウィンドウを使用することです。使用される2つのウィンドウは、互いにミラーイメージにすることができます。分析ウィンドウは、インパルスが事前に「検出」されないようにピークからゼロに急速に低下し、合成ウィンドウはゼロからピークに高速で上昇するため、処理の影響は時間的に後方に広がらない。これのもう1つの利点は、低遅延です。非対称ウィンドウは、良好な周波数選択性を持つことができ、ある種の万能薬のように、オーディオ圧縮の可変サイズの対称ウィンドウを置き換えることができます。見るM.シュネル、M。シュミット、M。ジャンダー、T。アルバート、R。ガイガー、V。ルオッピラ、P。エクストランド、M。ラッツキー、B。グリル、「MPEG-4 Enhanced Low Delay AAC- 高の新しい標準質の高いコミュニケーション」、125th AES Convention、サンフランシスコ、カリフォルニア、2008年10月、プレプリント7503、および彼らのウィンドウのフーリエ変換の大きさも示す別の会議論文: Schnell、M.、et al。2007. 拡張MPEG-4低遅延AAC –低ビットレート高品質通信。第122回AESコンベンション。
図1.ラップされた分析処理合成での非対称ウィンドウの使用の図。分析ウィンドウ(青)と合成ウィンドウ(黄色がかったオレンジ)の積(黒の破線)は、前のフレームのウィンドウ(灰色の破線)と一致します。MDCTを使用するときに完全な再構成を保証するには、さらに制約が必要です。
MDCTの代わりに離散フーリエ変換(DFT、FFT)を使用できますが、そのようなコンテキストでは冗長なスペクトルデータが得られます。DFTと比較して、MDCTはスペクトルデータの半分のみを提供し、適切なウィンドウが選択された場合でも完全な再構成を可能にします。
これは、完全な再構成を行わないMDCTではなく、DFTを使用した重複解析処理合成に適した、独自の非対称ウィンドウデザインです(図2)。このウィンドウは、潜在的に有用な時間領域特性を保持しながら、平均二乗時間と周波数帯域幅の積を最小化しようとします(制限されたガウスウィンドウに類似)。ウィンドウは相互の鏡像、関数および一次微分連続性、ウィンドウ関数の二乗が正規化されていない確率密度関数として解釈される場合のゼロ平均です。ウィンドウは、微分進化を使用して最適化されました。
図2.左:オーバーラップした分析、処理、再合成に適した非対称分析ウィンドウと、時間反転した対応する合成ウィンドウ。右:非対称ウィンドウと同じレイテンシーのコサインウィンドウ
図3.図2のコサインウィンドウ(青)と非対称ウィンドウ(オレンジ)のフーリエ変換の大きさ。非対称ウィンドウの方が周波数選択性が優れています。
以下は、プロットと非対称ウィンドウのオクターブソースコードです。プロットコードはウィキメディアコモンズから来ています。Linuxの私へのインストールをお勧めしますgnuplot
、epstool
、pstoedit
、transfig
第一およびlibrsvg2-bin
使用して表示するためdisplay
。
pkg load signal
graphics_toolkit gnuplot
set (0, "defaultaxesfontname", "sans-serif")
set (0, "defaultaxesfontsize", 12)
set (0, "defaultaxeslinewidth", 1)
function plotWindow (w, wname, wfilename = "", wspecifier = "", wfilespecifier = "")
M = 32; % Fourier transform size as multiple of window length
Q = 512; % Number of samples in time domain plot
P = 40; % Maximum bin index drawn
dr = 130; % Maximum attenuation (dB) drawn in frequency domain plot
N = length(w);
B = N*sum(w.^2)/sum(w)^2 % noise bandwidth (bins)
k = [0 : 1/Q : 1];
w2 = interp1 ([0 : 1/(N-1) : 1], w, k);
if (M/N < Q)
Q = M/N;
endif
figure('position', [1 1 1200 600])
subplot(1,2,1)
area(k,w2,'FaceColor', [0 0.4 0.6], 'edgecolor', [0 0 0], 'linewidth', 1)
if (min(w) >= -0.01)
ylim([0 1.05])
set(gca,'YTick', [0 : 0.1 : 1])
else
ylim([-1 5])
set(gca,'YTick', [-1 : 1 : 5])
endif
ylabel('amplitude')
set(gca,'XTick', [0 : 1/8 : 1])
set(gca,'XTickLabel',[' 0'; ' '; ' '; ' '; ' '; ' '; ' '; ' '; 'N-1'])
grid('on')
set(gca,'gridlinestyle','-')
xlabel('samples')
if (strcmp (wspecifier, ""))
title(cstrcat(wname,' window'), 'interpreter', 'none')
else
title(cstrcat(wname,' window (', wspecifier, ')'), 'interpreter', 'none')
endif
set(gca,'Position',[0.094 0.17 0.38 0.71])
H = abs(fft([w zeros(1,(M-1)*N)]));
H = fftshift(H);
H = H/max(H);
H = 20*log10(H);
H = max(-dr,H);
k = ([1:M*N]-1-M*N/2)/M;
k2 = [-P : 1/M : P];
H2 = interp1 (k, H, k2);
subplot(1,2,2)
set(gca,'FontSize',28)
h = stem(k2,H2,'-');
set(h,'BaseValue',-dr)
xlim([-P P])
ylim([-dr 6])
set(gca,'YTick', [0 : -10 : -dr])
set(findobj('Type','line'),'Marker','none','Color',[0.8710 0.49 0])
grid('on')
set(findobj('Type','gridline'),'Color',[.871 .49 0])
set(gca,'gridlinestyle','-')
ylabel('decibels')
xlabel('bins')
title('Fourier transform')
set(gca,'Position',[0.595 0.17 0.385 0.71])
if (strcmp (wfilename, ""))
wfilename = wname;
endif
if (strcmp (wfilespecifier, ""))
wfilespecifier = wspecifier;
endif
if (strcmp (wfilespecifier, ""))
savetoname = cstrcat('Window function and frequency response - ', wfilename, '.svg');
else
savetoname = cstrcat('Window function and frequency response - ', wfilename, ' (', wfilespecifier, ').svg');
endif
print(savetoname, '-dsvg', '-S1200,600')
close
endfunction
N=2^17; % Window length, B is equal for Triangular and Bartlett from 2^17
k=0:N-1;
w = -cos(2*pi*k/(N-1));
w .*= w > 0;
plotWindow(w, "Cosine")
freqData = [0.66697133904805994131, -0.20556692772918355727, 0.49267389481655493588, -0.25062332863369246594, -0.42388422228212319087, 0.42317609537724842905, -0.03930334287740060856, -0.11936153294075849129, 0.30201210285940127687, -0.15541616804857899536, -0.16208119255594669039, 0.12843871362286504723, -0.04470810646117385351, -0.00521885027256757845, 0.07185811583185619522, -0.02835116723496184862, -0.01393644785822748498, 0.00780746224568363342, -0.00748496824751256583, 0.00119325723511989282, 0.00194602547595042175];
freqData(1) /= 2;
scale = freqData(1) + sum(freqData.*not(mod(1:length(freqData), 2)));
freqData /= scale;
w = freqData(1)*ones(1, N);
for bin = 1:(length(freqData)/2)
w += freqData(bin*2)*cos(2*pi*bin*((1:N)-1)/N);
w += freqData(bin*2+1)*sin(2*pi*bin*((1:N)-1)/N);
endfor
w(N/4+1:N/2+1) = 0;
w(N/8+2:N/4) = (1 - w(N/8:-1:2).*w(7*N/8+2:N))./w(7*N/8:-1:6*N/8+2);
w = shift(w, -N/2);
plotWindow(w, "Asymmetrical");
ゼロで開始および終了するため、ウィンドウの2番目のサンプルのみを使用することができます。次のC ++コードはこれを行うため、ウィンドウの4分の1がどこでもゼロである場合を除いて、ゼロサンプルは取得されません。分析ウィンドウの場合、これは最初の四半期であり、合成ウィンドウの場合、これは最後の四半期です。積の計算のために、分析ウィンドウの後半を合成ウィンドウの前半に揃える必要があります。また、このコードは、ウィンドウの平均を(確率密度関数として)テストし、重複した再構成の平坦性を示します。
#include <stdio.h>
#include <math.h>
int main() {
const int windowSize = 400;
double *analysisWindow = new double[windowSize];
double *synthesisWindow = new double[windowSize];
for (int k = 0; k < windowSize/4; k++) {
analysisWindow[k] = 0;
}
for (int k = windowSize/4; k < windowSize*7/8; k++) {
double x = 2 * M_PI * ((k+0.5)/windowSize - 1.75);
analysisWindow[k] = 2.57392230162633461887-1.58661480271141974718*cos(x)+3.80257516644523141380*sin(x)
-1.93437090055110760822*cos(2*x)-3.27163999159752183488*sin(2*x)+3.26617449847621266201*cos(3*x)
-0.30335261753524439543*sin(3*x)-0.92126091064427817479*cos(4*x)+2.33100177294084742741*sin(4*x)
-1.19953922321306438725*cos(5*x)-1.25098147932225423062*sin(5*x)+0.99132076607048635886*cos(6*x)
-0.34506787787355830410*sin(6*x)-0.04028033685700077582*cos(7*x)+0.55461815542612269425*sin(7*x)
-0.21882110175036428856*cos(8*x)-0.10756484378756643594*sin(8*x)+0.06025986430527170007*cos(9*x)
-0.05777077835678736534*sin(9*x)+0.00920984524892982936*cos(10*x)+0.01501989089735343216*sin(10*x);
}
for (int k = 0; k < windowSize/8; k++) {
analysisWindow[windowSize-1-k] = (1 - analysisWindow[windowSize*3/4-1-k]*analysisWindow[windowSize*3/4+k])/analysisWindow[windowSize/2+k];
}
printf("Analysis window:\n");
for (int k = 0; k < windowSize; k++) {
printf("%d\t%.10f\n", k, analysisWindow[k]);
}
double accu, accu2;
for (int k = 0; k < windowSize; k++) {
accu += k*analysisWindow[k]*analysisWindow[k];
accu2 += analysisWindow[k]*analysisWindow[k];
}
for (int k = 0; k < windowSize; k++) {
synthesisWindow[k] = analysisWindow[windowSize-1-k];
}
printf("\nSynthesis window:\n");
for (int k = 0; k < windowSize; k++) {
printf("%d\t%.10f\n", k, synthesisWindow[k]);
}
printf("Mean of square of analysis window as probability density function:\n%f", accu/accu2);
printf("\nProduct of analysis and synthesis windows:\n");
for (int k = 0; k < windowSize/2; k++) {
printf("%d\t%.10f\n", k, analysisWindow[windowSize/2+k]*synthesisWindow[k]);
}
printf("\nSum of overlapping products of windows:\n");
for (int k = 0; k < windowSize/4; k++) {
printf("%d\t%.10f\n", k, analysisWindow[windowSize/2+k]*synthesisWindow[k]+analysisWindow[windowSize/2+k+windowSize/4]*synthesisWindow[k+windowSize/4]);
}
delete[] analysisWindow;
delete[] synthesisWindow;
}
そして、Kiss FFTと最適化ライブラリで使用される最適化コスト関数のソースコード:
class WinProblem : public Opti::Problem {
private:
int numParams;
double *min;
double *max;
kiss_fft_scalar *timeData;
kiss_fft_cpx *freqData;
int smallSize;
int bigSize;
kiss_fftr_cfg smallFFTR;
kiss_fftr_cfg smallIFFTR;
kiss_fftr_cfg bigFFTR;
kiss_fftr_cfg bigIFFTR;
public:
// numParams must be odd
WinProblem(int numParams, int smallSize, int bigSize, double* candidate = NULL) : numParams(numParams), smallSize(smallSize), bigSize(bigSize) {
min = new double[numParams];
max = new double[numParams];
if (candidate != NULL) {
for (int i = 0; i < numParams; i++) {
min[i] = candidate[i]-fabs(candidate[i])*(1.0/65536);
max[i] = candidate[i]+fabs(candidate[i])*(1.0/65536);
}
} else {
for (int i = 0; i < numParams; i++) {
min[i] = -1;
max[i] = 1;
}
}
timeData = new kiss_fft_scalar[bigSize];
freqData = new kiss_fft_cpx[bigSize/2+1];
smallFFTR = kiss_fftr_alloc(smallSize, 0, NULL, NULL);
smallIFFTR = kiss_fftr_alloc(smallSize, 1, NULL, NULL);
bigFFTR = kiss_fftr_alloc(bigSize, 0, NULL, NULL);
bigIFFTR = kiss_fftr_alloc(bigSize, 1, NULL, NULL);
}
double *getMin() {
return min;
}
double *getMax() {
return max;
}
// ___ __ 1
// | \ | | | | | | | / |
// | \ | | | | | | | / |
// | \_ | | | | | | | / |
// | \|__ | | | | | | /| |
// | | -----|_______|___ | | | | / | |
// | | | | ----| | | |/ | |
// --------------------------------x-----------------------x---|---- 0
// 0 1/8 2/8 3/8 4/8 5/8 6/8 7/8 15/16
// |-------------------------------| |-------|
// zeroStarts winStarts
//
// f(x) = 0 if 4/8 < x < 7/8
// f(-x)f(x) + f(-x+1/8)f(x-1/8) = 1 if 0 < x < 1/8
double costFunction(double *params, double compare, int print) {
double penalty = 0;
double accu = params[0]/2;
for (int i = 1; i < numParams; i += 2) {
accu += params[i];
}
if (print) {
printf("%.20f", params[0]/2/accu);
for (int i = 1; i < numParams; i += 2) {
printf("+%.20fcos(%d pi x)", params[i]/accu, (i+1)/2);
printf("+%.20fsin(%d pi x)", params[i+1]/accu, (i+1)/2);
}
printf("\n");
}
if (accu != 0) {
for (int i = 0; i < numParams; i++) {
params[i] /= accu;
}
}
const int zeroStarts = 4; // Normally 4
const int winStarts = 2; // Normally 1
int i = 0;
int j = 0;
freqData[j].r = params[i++];
freqData[j++].i = 0;
for (; i < numParams;) {
freqData[j].r = params[i++];
freqData[j++].i = params[i++];
}
for (; j <= smallSize/2;) {
freqData[j].r = 0;
freqData[j++].i = 0;
}
kiss_fftri(smallIFFTR, freqData, timeData);
double scale = 1.0/timeData[0];
double tilt = 0;
double tilt2 = 0;
for (int i = 2; i < numParams; i += 2) {
if ((i/2)%2) {
tilt2 += (i/2)*params[i]*scale;
} else {
tilt2 -= (i/2)*params[i]*scale;
}
tilt += (i/2)*params[i]*scale;
}
penalty += fabs(tilt);
penalty += fabs(tilt2);
double accu2 = 0;
for (int i = 0; i < smallSize; i++) {
timeData[i] *= scale;
}
penalty += fabs(timeData[zeroStarts*smallSize/8]);
penalty += fabs(timeData[winStarts*smallSize/16]*timeData[smallSize-winStarts*smallSize/16]-0.5);
for (int i = 1; i < winStarts*smallSize/16; i++) {
// Last 16th
timeData[bigSize-winStarts*smallSize/16+i] = timeData[smallSize-winStarts*smallSize/16+i];
accu2 += timeData[bigSize-winStarts*smallSize/16+i]*timeData[bigSize-winStarts*smallSize/16+i];
}
// f(-1/8+i)*f(1/8-i) + f(i)*f(-i) = 1
// => f(-1/8+i) = (1 - f(i)*f(-i))/f(1/8-i)
// => f(-1/16) = (1 - f(1/16)*f(-1/16))/f(1/16)
// = 1/(2 f(1/16))
for (int i = 1; i < winStarts*smallSize/16; i++) {
// 2nd last 16th
timeData[bigSize-winStarts*smallSize/8+i] = (1 - timeData[i]*timeData[bigSize-i])/timeData[winStarts*smallSize/8-i];
accu2 += timeData[bigSize-winStarts*smallSize/8+i]*timeData[bigSize-winStarts*smallSize/8+i];
}
// Between 2nd last and last 16th
timeData[bigSize-winStarts*smallSize/16] = 1/(2*timeData[winStarts*smallSize/16]);
accu2 += timeData[bigSize-winStarts*smallSize/16]*timeData[bigSize-winStarts*smallSize/16];
for (int i = zeroStarts*smallSize/8; i <= bigSize-winStarts*smallSize/8; i++) {
timeData[i] = 0;
}
for (int i = 0; i < zeroStarts*smallSize/8; i++) {
accu2 += timeData[i]*timeData[i];
}
if (print > 1) {
printf("\n");
for (int x = 0; x < bigSize; x++) {
printf("%d,%f\n", x, timeData[x]);
}
}
scale = 1/sqrt(accu2);
if (print) {
printf("sqrt(accu2) = %f\n", sqrt(accu2));
}
double tSpread = 0;
timeData[0] *= scale;
double tMean = 0;
for (int i = 1; i <= zeroStarts*smallSize/8; i++) {
timeData[i] *= scale;
// tSpread += ((double)i)*((double)i)*(timeData[i]*timeData[i]);
double x_0 = timeData[i-1]*timeData[i-1];
double x_1 = timeData[i]*timeData[i];
tSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
double slope = timeData[i]-timeData[i-1];
if (slope > 0) {
penalty += slope+1;
}
tMean += x_1*i;
if (timeData[i] < 0) {
penalty -= timeData[i];
}
}
double x_0 = timeData[0]*timeData[0];
for (int i = 1; i <= winStarts*smallSize/8; i++) {
timeData[bigSize-i] *= scale;
double x_1 = timeData[bigSize-i]*timeData[bigSize-i];
tSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
x_0 = x_1;
tMean += x_1*(-i);
}
tMean /= smallSize;
penalty += fabs(tMean);
if (tMean > 0) {
penalty += 1;
}
tSpread /= ((double)smallSize)*((double)smallSize);
if (print) {
printf("tSpread = %f\n", tSpread);
}
kiss_fftr(bigFFTR, timeData, freqData);
double fSpread = 0;
x_0 = freqData[0].r*freqData[0].r;
for (int i = 1; i <= bigSize/2; i++) {
double x_1 = freqData[i].r*freqData[i].r+freqData[i].i*freqData[i].i;
fSpread += ((double)i)*((double)i)*(x_0 + x_1)*0.5 - ((double)i)*(2.0/3*x_0 + 1.0/3*x_1) + 0.25*x_0 + 1.0/12*x_1;
x_0 = x_1;
}
if (print > 1) {
for (int i = 0; i <= bigSize/2; i++) {
printf("%d,%f,%f\n", i, freqData[i].r, freqData[i].i);
}
}
fSpread /= bigSize; // Includes kiss_fft scaling
if (print) {
printf("fSpread = %f\n", fSpread);
printf("%f,%f,%f\n", tSpread, fSpread, tSpread*fSpread);
}
return tSpread*fSpread + penalty;
}
double costFunction(double *params, double compare) {
return costFunction(params, compare, false);
}
int getNumDimensions() {
return numParams;
}
~WinProblem() {
delete[] min;
delete[] max;
delete[] timeData;
delete[] freqData;
KISS_FFT_FREE(smallFFTR);
KISS_FFT_FREE(smallIFFTR);
KISS_FFT_FREE(bigFFTR);
KISS_FFT_FREE(bigIFFTR);
}
};
ウィンドウのコンテキストに依存します。従来から開発されていたウィンドウイングは、推定のパワースペクトル密度のブラックマンチューキー法を目的としていました。これはコレログラム法の一般的な形式であり、離散時間ウィーナーキンチン定理が活用されます。これは、自己相関シーケンスを離散時間フーリエ変換によるパワースペクトル密度に関連付けていることを思い出してください。
したがって、ウィンドウはいくつかの基準を念頭に置いて設計されました。最初に、彼らは起源で統一ゲインを持たなければなりませんでした。これは、rxx [0]をサンプルパワーと見なすことができるため、信号の自己相関シーケンスのパワーを保持するためです。次に、ウィンドウは原点から先細になります。これにはいくつかの理由があります。最初に、有効な自己相関シーケンスであるためには、他のすべての遅延が原点以下でなければなりません。第二に、これにより、ほとんどのサンプルを使用して高い信頼性で計算された低遅延の高い重み付けと、利用可能なデータサンプルの量が減少するため分散が増加する高遅延の小さいまたはゼロの重み付けが可能になりました計算。これにより、最終的にメインローブが広くなり、PSD推定の解像度が低下します。
最後に、ウィンドウに非負のスペクトルがある場合も非常に望ましいです。これは、Blackman-Tukey法では、最終推定値のバイアスを、ウィンドウスペクトルと畳み込まれた真のパワースペクトル密度として考えることができるためです。このウィンドウスペクトルに負の領域がある場合、パワースペクトル密度推定で負の領域を持つ可能性があります。このコンテキストでは物理的な意味がほとんどないため、これは明らかに望ましくありません。さらに、Blackman-Tukey法には絶対値の2乗演算がないことに注意してください。これは、実数で偶数のウィンドウを乗算した実数で偶数の自己相関シーケンスを使用すると、離散フーリエ変換も実数で偶数になるためです。実際には、通常は量子化されている非常に小さな負の成分があります。
これらの理由から、有効な自己相関シーケンスもすべて同じであるため、ウィンドウの長さも奇数になります。さて、今でもできる(そして行われている)ことは、ピリオドグラムメソッドのコンテキストでのウィンドウ化です。つまり、データをウィンドウ化してから、ウィンドウ化されたデータの大きさの2乗を取ります。これは、Blackman-Tukey法と同等ではありません。いくつかの統計的導出から、それらは平均では同様に振る舞うことがわかりますが、一般的にはそうではありません。たとえば、ウェルチ法またはバートレット法の各セグメントにウィンドウ処理を使用して、推定値の分散を減らすことは非常に一般的です。本質的に、これらの方法では、モチベーションは部分的に同じですが、異なります。これらの方法では、ウィンドウラグを慎重に重み付けする代わりに、ウィンドウエネルギーを分割するなどして、電力が正規化されます。
したがって、ウィンドウとその起源、およびそれらが対称である理由をコンテキスト化することを願っています。非対称ウィンドウを選択する理由について知りたい場合は、フーリエ変換の双対性の意味と、アプリケーションのパワースペクトル密度推定の畳み込みの意味を考慮してください。乾杯。