通过 FFT 进行快速余弦变换

信息处理 fft dct
2022-01-01 00:39:19

我想实现快速余弦变换。我在wikipedia上读到,有一个快速版本的 DCT,它的计算方式与 FFT 类似。我尝试阅读引用的Makhoul * 论文,以了解Scipy中也使用的 FTPACK 和 FFTW 实现,但我无法提取实际算法。这是我到目前为止所拥有的:

FFT代码:

def fft(x):
    if x.size ==1:
        return x
    N = x.size
    x0 = my_fft(x[0:N:2])
    x1 = my_fft(x[0+1:N:2])
    k = numpy.arange(N/2)
    e = numpy.exp(-2j*numpy.pi*k/N)
    l = x0 + x1 * e
    r = x0 - x1 * e  
    return numpy.hstack([l,r])

DCT代码:

def dct(x):
    k = 0
    N = x.size
    xk = numpy.zeros(N)
    for k in range(N):     
        for n in range(N):
            xn = x[n]
            xk[k] += xn*numpy.cos(numpy.pi/N*(n+1/2.0)*k)
    return xk 

FCT试验:

def my_fct(x):
    if x.size ==1:
        return x
    N = x.size
    x0 = my_fct(x[0:N:2]) # have to be set to zero?
    x1 = my_fct(x[0+1:N:2])
    k = numpy.arange(N/2)
    n = # ???
    c = numpy.cos(numpy.pi/N*(n+1/2.0)*k)
    l = x0 #???
    r = x0 #???
    return numpy.hstack([l,r])

*J。Makhoul,“一维和二维的快速余弦变换”,IEEE Trans。声学。语音信号。过程。28 (1), 27-34 (1980)。

4个回答

我一直在阅读这方面的内容,并且有多种方法可以使用不同大小的 N。我的 Matlab 生锈了,所以它们在 Python 中(N是输入信号的长度xkarange(N)= ):[0,1,2,...,N1]

类型 2 DCT 使用 4N FFT 且无移位

信号[a, b, c, d]变成

[0, a, 0, b, 0, c, 0, d, 0, d, 0, c, 0, b, 0, a].

然后取 FFT 得到频谱

[A, B, C, D, 0, -D, -C, -B, -A, -B, -C, -D, 0, D, C, B]

然后扔掉除了第一个之外的所有东西[A, B, C, D],你就完成了:

u = zeros(4 * N)
u[1:2*N:2] = x
u[2*N+1::2] = x[::-1]

U = fft(u)[:N]
return U.real

使用 2N FFT 镜像的 2 型 DCT (Makhoul)

[a, b, c, d]变成[a, b, c, d, d, c, b, a]. 取它的 FFT 得到 [A, B, C, D, 0, D*, C*, B*],然后扔掉所有东西,但[A, B, C, D]乘以半样本移位)得到 DCT:ejπk2N

y = empty(2*N)
y[:N] = x
y[N:] = x[::-1]

Y = fft(y)[:N]

Y *= exp(-1j*pi*k/(2*N))
return Y.real

使用 2N FFT 填充的 2 型 DCT (Makhoul)

[a, b, c, d]变成[a, b, c, d, 0, 0, 0, 0]. 取 FFT 得到 [A, B, C, D, E, D*, C*, B*],然后扔掉所有东西,但[A, B, C, D]乘以得到 DCT:2ejπk2N

y = zeros(2*N)
y[:N] = x

Y = fft(y)[:N]

Y *= 2 * exp(-1j*pi*k/(2*N))
return Y.real

使用 N FFT (Makhoul) 的 2 型 DCT

信号[a, b, c, d, e, f]变为[a, c, e, f, d, b],然后用FFT得到[A, B, C, D, C*, B*]乘以然后取实部:2ejπk2N

v = empty_like(x)
v[:(N-1)//2+1] = x[::2]

if N % 2: # odd length
    v[(N-1)//2+1:] = x[-2::-2]
else: # even length
    v[(N-1)//2+1:] = x[::-2]

V = fft(v)

V *= 2 * exp(-1j*pi*k/(2*N))
return V.real

在我的机器上,这些速度都大致相同,因为生成exp(-1j*pi*k/(2*N))比 FFT 需要更长的时间。:D

In [99]: timeit dct2_4nfft(a)
10 loops, best of 3: 23.6 ms per loop

In [100]: timeit dct2_2nfft_1(a)
10 loops, best of 3: 20.1 ms per loop

In [101]: timeit dct2_2nfft_2(a)
10 loops, best of 3: 20.8 ms per loop

In [102]: timeit dct2_nfft(a)
100 loops, best of 3: 16.4 ms per loop

In [103]: timeit scipy.fftpack.dct(a, 2)
100 loops, best of 3: 3 ms per loop

我不确定您引用的论文使用什么方法,但是我之前已经得出了这个,这就是我最终得到的结果:(假设是输入)x(n)

y(n)={x(n),n=0,1,...,N1x(2N1n),n=N,N+1,...,2N1

然后 DCT 由下式给出

C(k)=Re{ejπk2NFFT{y(n)}}

因此,您基本上创建了长度序列,其中前半部分是,后半部分是反转。然后只需采用 FFT 并将该向量乘以相位斜坡。最后只取实部,你就有了 DCT。2Ny(n)x(n)x(n)

这是MATLAB中的代码。

function C = fdct(x)
    N = length(x);
    y = zeros(1,2*N);
    y(1:N) = x;
    y(N+1:2*N) = fliplr(x);
    Y = fft(y);
    k=0:N-1;
    C = real(exp(-j.* pi.*k./(2*N)).*Y(1:N));

编辑:

注意:这里使用的 DCT 公式是:

C(k)=2n=0N1x(n)cos(πk2N(2n+1))

有几种缩放总和的方法,因此它可能与其他实现不完全匹配。例如,MATLAB 使用:

C(k)=w(k)n=0N1x(n)cos(πk2N(2n+1))

其中w(0)=1Nw(1...N1)=2N

您可以通过适当缩放输出来解决此问题。

对于真正的科学计算,内存使用量也很重要。因此 N 点 FFT 对我来说更有吸引力。这仅是由于信号的 Hermitian 对称性才可能实现的。这里给出了参考 Makhoul。并且实际上有计算DCT和IDCT或DCT10和DCT01的算法。
http://ieeexplore.ieee.org/abstract/document/1163351/

我为 DCT 和 DST 编写了 python numpy/scipy 函数,如果你想将它与具有可微 FFT 但还没有 DCT 和 DST 的 autodiff 库(如 pytorch)一起使用,这将很有用。

import numpy as np
import scipy.fftpack

def dct_with_fft(x):
    N = x.size
    v = np.empty_like(x)
    v[:(N-1)//2+1] = x[::2]

    if N % 2: # odd length
        v[(N-1)//2+1:] = x[-2::-2]
    else: # even length
        v[(N-1)//2+1:] = x[::-2]

    V = scipy.fftpack.fft(v)

    k = np.arange(N)
    V *= 2 * np.exp(-1j*np.pi*k/(2*N))
    return V.real


def dst_with_fft(x):
    N = x.size
    v = np.empty_like(x)
    v[:(N-1)//2+1] = x[::2]

    if N % 2: # odd length
        v[(N-1)//2+1:] = -x[-2::-2]
    else: # even length
        v[(N-1)//2+1:] = -x[::-2]

    V = scipy.fftpack.fft(1j*v)

    k = np.arange(N)
    V *= 2 * np.exp(-1j*np.pi*k/(2*N))
    return V.imag[::-1]

N = 5
x = np.random.rand(N)

print(dct_with_fft(x))
print(scipy.fft.dct(x))

print(dst_with_fft(x))
print(scipy.fft.dst(x))