[英]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,顯示:
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不提供像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()
,這樣會快很多。
==================================================== ===============
具有新功能版本的完整測試代碼:
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 )。
# 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% 。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.