簡體   English   中英

如何將兩個 2D RFFT arrays (FFTPACK) 相乘以兼容 NumPy 的 FFT?

[英]How to multiply two 2D RFFT arrays (FFTPACK) to be compatible with NumPy's FFT?

我正在嘗試將兩個 2D arrays 相乘,這些 2D arrays 用fftpack_rfft2d() (SciPy 的 FFTPACK RFFT)轉換,結果與我從scipy_rfft2d() (SciPy 的 FFT RFFT)得到的結果不兼容。

下圖共享腳本的output,顯示:

  • 兩個輸入arrays的初始化值;
  • arrays 在使用scipy_rfft2d()對 RFFT 進行 SciPy 的 FFT 實現進行轉換后,然后是 output 后用scipy_irfft2d()進行反向轉換后的乘法;
  • 使用帶有fftpack_rfft2d()fftpack_irfft2d()的 RFFT 的 SciPy 的 FFTPACK 實現同樣的事情;
  • 使用np.allclose()進行測試的結果,用於檢查兩個乘法的結果在它們被轉換回各自的 IRFFT 實現后是否相同。

需要說明的是,紅色矩形顯示的是逆變換IRFFT后的乘法結果:左邊的矩形使用了SciPy的FFT IRFFT; 右邊的矩形,SciPy 的 FFTPACK IRFFT。 當與 FFTPACK 版本的乘法固定時,它們應該呈現相同的數據。

我認為 FFTPACK 版本的乘法結果不正確,因為scipy.fftpack返回結果 RFFT 數組中的實部和虛部與來自scipy.fft的 RFFT 不同:

  • 我相信來自 scipy.fftpack 的RFFT返回一個數組,其中一個元素包含實部,下一個元素包含其虛部;
  • 在 scipy.fft 的RFFT中,每個元素都是復數,因此能夠同時包含實部和虛部;

如果我錯了,請糾正我! 我還想指出,由於scipy.fftpack不提供像rfft2()irfft2()這樣的二維 arrays 的函數,我在下面的代碼中提供了我自己的實現:

import numpy as np
from scipy import fftpack as scipy_fftpack
from scipy import fft as scipy_fft

# SCIPY RFFT 2D
def scipy_rfft2d(matrix):
    fftRows = [scipy_fft.rfft(row) for row in matrix]
    return np.transpose([scipy_fft.fft(row) for row in np.transpose(fftRows)])

# SCIPY IRFFT 2D
def scipy_irfft2d(matrix, s):
    fftRows = [scipy_fft.irfft(row) for row in matrix]
    return np.transpose([scipy_fft.ifft(row) for row in np.transpose(fftRows)])

# FFTPACK RFFT 2D
def fftpack_rfft2d(matrix):
    fftRows = [scipy_fftpack.rfft(row) for row in matrix]
    return np.transpose([scipy_fftpack.rfft(row) for row in np.transpose(fftRows)])

# FFTPACK IRFFT 2D
def fftpack_irfft2d(matrix):
    fftRows = [scipy_fftpack.irfft(row) for row in matrix]
    return np.transpose([scipy_fftpack.irfft(row) for row in np.transpose(fftRows)])


print('\n####################     INPUT DATA     ###################\n')

# initialize two 2D arrays with random data for testing
in1 = np.array([[0,   0,   0,   0], \
                [0, 255, 255,   0], \
                [0,   0, 255, 255], \
                [0,   0,   0,   0]])

print('\nin1 shape=', in1.shape, '\n', in1)

in2 = np.array([[0,   0,   0,   0], \
                [0,   0, 255,   0], \
                [0, 255, 255,   0], \
                [0, 255,   0,   0]])

print('\nin2 shape=', in2.shape, '\n', in2)

print('\n###############    SCIPY: 2D RFFT (MULT)    ###############\n')

# transform both inputs with SciPy RFFT for 2D
scipy_rfft1 = scipy_fft.rfftn(in1)
scipy_rfft2 = scipy_fft.rfftn(in2)

print('* Output from scipy_fft.rfftn():')
print('scipy_fft1 shape=', scipy_rfft1.shape, '\n', scipy_rfft1.real)
print('\nscipy_fft2 shape=', scipy_rfft2.shape, '\n', scipy_rfft2.real)

# perform multiplication between two 2D arrays from SciPy RFFT
scipy_rfft_mult = scipy_rfft1 * scipy_rfft2

# perform inverse RFFT for 2D arrays using SciPy
scipy_data = scipy_fft.irfftn(scipy_rfft_mult, in1.shape) # passing shape guarantees the output will have the original data size
print('\n* Output from scipy_fft.irfftn():')
print('scipy_data shape=', scipy_data.shape, '\n', scipy_data)

print('\n###############   FFTPACK: 2D RFFT (MULT)   ###############\n')

# transform both inputs with FFTPACK RFFT for 2D
fftpack_rfft1 = fftpack_rfft2d(in1)
fftpack_rfft2 = fftpack_rfft2d(in2)
print('* Output from fftpack_rfft2d():')
print('fftpack_rfft1 shape=', fftpack_rfft1.shape, '\n', fftpack_rfft1)
print('\nfftpack_rfft2 shape=', fftpack_rfft2.shape, '\n', fftpack_rfft2)

# TODO: perform multiplication between two 2D arrays from FFTPACK RFFT
fftpack_rfft_mult = fftpack_rfft1 * fftpack_rfft2 # this doesn't work

# perform inverse RFFT for 2D arrays using FFTPACK
fftpack_data = fftpack_irfft2d(fftpack_rfft_mult)
print('\n* Output from fftpack_irfft2d():')
print('fftpack_data shape=', fftpack_data.shape, '\n', fftpack_data)

print('\n#####################      RESULT     #####################\n')

# compare FFTPACK result with SCIPY
print('\nIs fftpack_data equivalent to scipy_data?', np.allclose(fftpack_data, scipy_data), '\n')

假設我的猜測是正確的,那么將由fftpack_rfft2d()生成的兩個二維 arrays 相乘的 function 的正確實現是什么? 請記住:生成的數組必須能夠使用fftpack_irfft2d()轉換回來。

僅邀請解決二維問題的答案。 那些對如何乘以一維 FFTPACK arrays 感興趣的人可以查看此線程

正確的功能:

import numpy as np
from scipy import fftpack as scipy_fftpack
from scipy import fft as scipy

# FFTPACK RFFT 2D
def fftpack_rfft2d(matrix):
    fftRows = scipy_fftpack.fft(matrix, axis=1)
    fftCols = scipy_fftpack.fft(fftRows, axis=0)

    return fftCols

# FFTPACK IRFFT 2D
def fftpack_irfft2d(matrix):
    ifftRows = scipy_fftpack.ifft(matrix, axis=1)
    ifftCols = scipy_fftpack.ifft(ifftRows, axis=0)

    return ifftCols.real

您以錯誤的方式計算了 2D FFT。 是的,可以使用rfft() 計算第一個 FFT(在您的情況下按列) ,但必須第一個 FFT(按列)復數output 上提供第二個 FFT 計算,因此rfft()的 output 必須被轉換成真正的復譜 此外,這意味着您必須使用fft()而不是rfft()進行第二次 FFT 逐行。 因此,在兩種計算中使用fft()會更方便。

此外,您輸入數據為numpy 2D arrays,為什么要使用列表理解 直接使用fftpack.fft()這樣會快很多

  • 如果您已經只有由錯誤函數計算的 2D arrays 並且需要將它們相乘:那么,我認為,嘗試使用相同的“錯誤”方式從錯誤的 2D FFT 重建輸入數據,然后計算正確的 2D FFT

==================================================== ===============

具有新功能版本的完整測試代碼:

import numpy as np
from scipy import fftpack as scipy_fftpack
from scipy import fft as scipy_fft


# FFTPACK RFFT 2D
def fftpack_rfft2d(matrix):
    fftRows = scipy_fftpack.fft(matrix, axis=1)
    fftCols = scipy_fftpack.fft(fftRows, axis=0)

    return fftCols

# FFTPACK IRFFT 2D
def fftpack_irfft2d(matrix):
    ifftRows = scipy_fftpack.ifft(matrix, axis=1)
    ifftCols = scipy_fftpack.ifft(ifftRows, axis=0)

    return ifftCols.real

print('\n####################     INPUT DATA     ###################\n')

# initialize two 2D arrays with random data for testing
in1 = np.array([[0,   0,   0,   0], \
                [0, 255, 255,   0], \
                [0,   0, 255, 255], \
                [0,   0,   0,   0]])

print('\nin1 shape=', in1.shape, '\n', in1)

in2 = np.array([[0,   0,   0,   0], \
                [0,   0, 255,   0], \
                [0, 255, 255,   0], \
                [0, 255,   0,   0]])

print('\nin2 shape=', in2.shape, '\n', in2)

print('\n###############    SCIPY: 2D RFFT (MULT)    ###############\n')

# transform both inputs with SciPy RFFT for 2D
scipy_rfft1 = scipy_fft.fftn(in1)
scipy_rfft2 = scipy_fft.fftn(in2)

print('* Output from scipy_fft.rfftn():')
print('scipy_fft1 shape=', scipy_rfft1.shape, '\n', scipy_rfft1)
print('\nscipy_fft2 shape=', scipy_rfft2.shape, '\n', scipy_rfft2)

# perform multiplication between two 2D arrays from SciPy RFFT
scipy_rfft_mult = scipy_rfft1 * scipy_rfft2

# perform inverse RFFT for 2D arrays using SciPy
scipy_data = scipy_fft.irfftn(scipy_rfft_mult, in1.shape) # passing shape guarantees the output will
                                                          # have the original data size
print('\n* Output from scipy_fft.irfftn():')
print('scipy_data shape=', scipy_data.shape, '\n', scipy_data)

print('\n###############   FFTPACK: 2D RFFT (MULT)   ###############\n')

# transform both inputs with FFTPACK RFFT for 2D
fftpack_rfft1 = fftpack_rfft2d(in1)
fftpack_rfft2 = fftpack_rfft2d(in2)
print('* Output from fftpack_rfft2d():')
print('fftpack_rfft1 shape=', fftpack_rfft1.shape, '\n', fftpack_rfft1)
print('\nfftpack_rfft2 shape=', fftpack_rfft2.shape, '\n', fftpack_rfft2)

# TODO: perform multiplication between two 2D arrays from FFTPACK RFFT
fftpack_rfft_mult = fftpack_rfft1 * fftpack_rfft2 # this doesn't work

# perform inverse RFFT for 2D arrays using FFTPACK
fftpack_data = fftpack_irfft2d(fftpack_rfft_mult)
print('\n* Output from fftpack_irfft2d():')
print('fftpack_data shape=', fftpack_data.shape, '\n', fftpack_data)

print('\n#####################      RESULT     #####################\n')

# compare FFTPACK result with SCIPY
print('\nIs fftpack_data equivalent to scipy_data?', np.allclose(fftpack_data, scipy_data), '\n')

output 是:

####################     INPUT DATA     ###################


in1 shape= (4, 4) 
 [[  0   0   0   0]
 [  0 255 255   0]
 [  0   0 255 255]
 [  0   0   0   0]]

in2 shape= (4, 4) 
 [[  0   0   0   0]
 [  0   0 255   0]
 [  0 255 255   0]
 [  0 255   0   0]]

###############    SCIPY: 2D RFFT (MULT)    ###############

* Output from scipy_fft.rfftn():
scipy_fft1 shape= (4, 4) 
 [[1020.  -0.j -510.  +0.j    0.  -0.j -510.  -0.j]
 [-510.-510.j    0.  +0.j    0.  +0.j  510.+510.j]
 [   0.  -0.j    0.+510.j    0.  -0.j    0.-510.j]
 [-510.+510.j  510.-510.j    0.  -0.j    0.  -0.j]]

scipy_fft2 shape= (4, 4) 
 [[1020.  -0.j -510.-510.j    0.  -0.j -510.+510.j]
 [-510.  +0.j  510.+510.j    0.-510.j    0.  -0.j]
 [   0.  -0.j    0.  +0.j    0.  -0.j    0.  -0.j]
 [-510.  -0.j    0.  +0.j    0.+510.j  510.-510.j]]

* Output from scipy_fft.irfftn():
scipy_data shape= (4, 4) 
 [[130050.  65025.  65025. 130050.]
 [ 65025.      0.      0.  65025.]
 [ 65025.      0.      0.  65025.]
 [130050.  65025.  65025. 130050.]]

###############   FFTPACK: 2D RFFT (MULT)   ###############

* Output from fftpack_rfft2d():
fftpack_rfft1 shape= (4, 4) 
 [[1020.  -0.j -510.  +0.j    0.  -0.j -510.  +0.j]
 [-510.-510.j    0.  +0.j    0.  +0.j  510.+510.j]
 [   0.  +0.j    0.+510.j    0.  +0.j    0.-510.j]
 [-510.+510.j  510.-510.j    0.  +0.j    0.  +0.j]]

fftpack_rfft2 shape= (4, 4) 
 [[1020.  -0.j -510.-510.j    0.  -0.j -510.+510.j]
 [-510.  +0.j  510.+510.j    0.-510.j    0.  +0.j]
 [   0.  +0.j    0.  +0.j    0.  +0.j    0.  +0.j]
 [-510.  +0.j    0.  +0.j    0.+510.j  510.-510.j]]

* Output from fftpack_irfft2d():
fftpack_data shape= (4, 4) 
 [[130050.+0.j  65025.+0.j  65025.+0.j 130050.+0.j]
 [ 65025.+0.j      0.+0.j      0.+0.j  65025.+0.j]
 [ 65025.+0.j      0.+0.j      0.+0.j  65025.+0.j]
 [130050.+0.j  65025.+0.j  65025.-0.j 130050.+0.j]]

#####################      RESULT     #####################


Is fftpack_data equivalent to scipy_data? True 

你的假設是正確的。 FFTPACK 以格式返回單個實向量中的所有系數

[y(0),Re(y(1)),Im(y(1)),...,Re(y(n/2))]              if n is even
[y(0),Re(y(1)),Im(y(1)),...,Re(y(n/2)),Im(y(n/2))]   if n is odd

其中 scipy.rfft 返回一個復數向量

[y(0),Re(y(1)) + 1.0j*Im(y(1)),...,Re(y(n/2) + 1.0j*Im(y(n/2)))]

所以你需要使用適當的步幅形成一個向量,如下所示:

y_fft = np.cat([y_fftpack[0], y_fftpack[1:2:] + 1.0j*y_fftpack[2:2:]])

@Andrei是對的:僅使用復值 FFT 會簡單得多(盡管他的實現不必要地復雜,只需使用scipy.fftpack.fft2 )。 正如我在評論中所說,最好的選擇是切換到scipy.fft ,它更易於使用; fftpack已被棄用,取而代之的是它。

但是,如果您確實需要使用fftpack ,並且確實希望通過使用rfft function 來節省一些計算時間,那么這是正確的方法。 它需要將 rfft function 的實值rfft轉換為復值數組,然后再計算沿另一個維度的fft 使用此解決方案,以下fftpack_rfft2d輸出其輸入的 2D FFT 的一半,另一半是冗余的。

import numpy as np
from scipy import fftpack

# FFTPACK RFFT 2D
def fftpack_rfft1d(matrix):
    assert not (matrix.shape[1] & 0x1)
    tmp = fftpack.rfft(matrix, axis=1)
    assert  tmp.dtype == np.dtype('float64')
    return np.hstack((tmp[:, [0]], np.ascontiguousarray(tmp[:, 1:-1]).view(np.complex128), tmp[:, [-1]]))

def fftpack_rfft2d(matrix):
    return fftpack.fft(fftpack_rfft1d(matrix), axis=0)

# FFTPACK IRFFT 2D
def fftpack_irfft1d(matrix):
    assert  matrix.dtype == np.dtype('complex128')
    tmp = np.hstack((matrix[:, [0]].real, np.ascontiguousarray(matrix[:, 1:-1]).view(np.float64), matrix[:, [-1]].real))
    return fftpack.irfft(tmp, axis=1)

def fftpack_irfft2d(matrix):
    return fftpack_irfft1d(fftpack.ifft(matrix, axis=0))

######

# test data
in1 = np.random.randn(256,256)
in2 = np.random.randn(256,256)

# fftpack.fft2
gt_result = fftpack.ifft2(fftpack.fft2(in1) * fftpack.fft2(in2)).real

# fftpack_rfft2d
our_result = fftpack_irfft2d(fftpack_rfft2d(in1) * fftpack_rfft2d(in2) )

# compare
print('\nIs our result equivalent to the ground truth?', np.allclose(gt_result, our_result), '\n')

[此代碼僅適用於大小均勻的圖像,我沒有費心將其設為通用,請參閱此處了解如何操作)。

盡管如此,由於此解決方案需要數據副本,因此它實際上比僅使用普通的復數值 FFT ( fftpack.fft2 ) 慢,即使它執行的計算更少:

import time

tic = time.perf_counter()
for i in range(100):
   fftpack.fft(in1)
toc = time.perf_counter()
print(f"fftpack.fft() takes {toc - tic:0.4f} seconds")

tic = time.perf_counter()
for i in range(100):
   fftpack_rfft2d(in1)
toc = time.perf_counter()
print(f"fftpack_rfft2d() takes {toc - tic:0.4f} seconds")

輸出:

fftpack.fft() takes 0.0442 seconds
fftpack_rfft2d() takes 0.0664 seconds

所以,確實,堅持fftpack.fft (或者更確切地說scipy.fft.fft如果可以的話)。

要將復數系數的 2 個 arrays 相乘,您必須進行復數乘法。

請參閱https://en.m.wikipedia.org/wiki/Complex_number的操作部分中的乘法

您不能只將實數分量相乘,然后將虛數分量分開或拆分元素,這可能就是您的 fftpack 矩陣 mul 產生垃圾的原因。

除了@CrisLuengo 回答( https://stackoverflow.com/a/61873672/501852 )。

性能測試

測試fftpack.FFTfftpack.RFFT - 1D

# test data
sz =50000
sz = fftpack.next_fast_len(sz)
in1 = np.random.randn(sz)

print(f"Input (len = {len(in1)}):", sep='\n')

rep = 1000

tic = time.perf_counter()
for i in range(rep):
    spec1 = fftpack.fft(in1,axis=0)
toc = time.perf_counter()
print("", f"Spectrum FFT (len = {len(spec1)}):",
      f"spec1 takes {10**6*((toc - tic)/rep):0.4f} us", sep="\n")

sz2 = sz//2 + 1
spec2 = np.empty(sz2, dtype=np.complex128)

tic = time.perf_counter()
for i in range(rep):
    tmp = fftpack.rfft(in1)

    assert  tmp.dtype == np.dtype('float64')

    if not sz & 0x1:
        end = -1 
        spec2[end] = tmp[end]
    else:
        end = None

    spec2[0] = tmp[0]
    spec2[1:end] = tmp[1:end].view(np.complex128)

toc = time.perf_counter()
print("", f"Spectrum RFFT (len = {len(spec2)}):",
      f"spec2 takes {10**6*((toc - tic)/rep):0.4f} us", sep="\n")

結果是

Input (len = 50000):

Spectrum FFT (len = 50000):
spec1 takes 583.5880 us

Spectrum RFFT (len = 25001):
spec2 takes 476.0843 us
  • 因此,使用fftpack.rfft()並進一步將其 output 轉換為complex視圖fftpack.fft()大 arrays 快約 15-20%

測試fftpack.FFTfftpack.FFT2 - 2D

2D 案例的類似測試:

# test data
sz = 5000
in1 = np.random.randn(sz, sz)

print(f"Input (len = {len(in1)}):", sep='\n')

rep = 1

tic = time.perf_counter()
for i in range(rep):
    spec1 = np.apply_along_axis(fftpack.fft, 0, in1)
    spec1 = np.apply_along_axis(fftpack.fft, 1, spec1)
toc = time.perf_counter()
print("", f"2D Spectrum FFT with np.apply_along_axis (len = {len(spec1)}):",
      f"spec1 takes {10**0*((toc - tic)/rep):0.4f} s", sep="\n")


tic = time.perf_counter()
for i in range(rep):
    spec2 = fftpack.fft(in1,axis=0)
    spec2 = fftpack.fft(spec2,axis=1)
toc = time.perf_counter()
print("", f"2D Spectrum 2xFFT (len = {len(spec2)}):",
      f"spec2 takes {10**0*((toc - tic)/rep):0.4f} s", sep="\n")

tic = time.perf_counter()
for i in range(rep):
    spec3 = fftpack.fft2(in1)
toc = time.perf_counter()
print("", f"2D Spectrum FFT2 (len = {len(spec3)}):",
      f"spec3 takes {10**0*((toc - tic)/rep):0.4f} s", sep="\n")

# compare
print('\nIs spec1 equivalent to the spec2?', np.allclose(spec1, spec2))
print('\nIs spec2 equivalent to the spec3?', np.allclose(spec2, spec3), '\n')

大小矩陣的結果 = 5x5

Input (len = 5):

2D Spectrum FFT with np.apply_along_axis (len = 5):
spec1 takes 0.000183 s

2D Spectrum 2xFFT (len = 5):
spec2 takes 0.000010 s

2D Spectrum FFT2 (len = 5):
spec3 takes 0.000012 s

Is spec1 equivalent to the spec2? True

Is spec2 equivalent to the spec3? True

大小矩陣的結果 = 500x500

Input (len = 500):

2D Spectrum FFT with np.apply_along_axis (len = 500):
spec1 takes 0.017626 s

2D Spectrum 2xFFT (len = 500):
spec2 takes 0.005324 s

2D Spectrum FFT2 (len = 500):
spec3 takes 0.003528 s

Is spec1 equivalent to the spec2? True

Is spec2 equivalent to the spec3? True 

大小矩陣的結果 = 5000x5000

Input (len = 5000):

2D Spectrum FFT with np.apply_along_axis (len = 5000):
spec1 takes 2.538471 s

2D Spectrum 2xFFT (len = 5000):
spec2 takes 0.846661 s

2D Spectrum FFT2 (len = 5000):
spec3 takes 0.574397 s

Is spec1 equivalent to the spec2? True

Is spec2 equivalent to the spec3? True

結論

從上面的測試看來,使用fftpack.fft2()對於更大的矩陣更有效。

使用np.apply_along_axis()是最慢的方法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM