【问题标题】:How to use the FFT for a 1D deconvolution?如何使用 FFT 进行一维反卷积?
【发布时间】:2020-02-21 15:45:11
【问题描述】:

问题
我正在尝试使用卷积定理对两个测量数据 AB 进行反卷积。我知道对于卷积,您应该对数据进行零填充以防止循环卷积。但是,如果零填充对于反卷积也是必不可少的,我会感到困惑。

问题
1.如何根据卷积定理正确进行反卷积?
2. 为什么下面的例子不起作用?

接近
因为 AB 是测量的,所以我创建了一个示例以供进一步调查。这个想法是通过在模式same 中使用scipy.signal.convolve 创建B

import numpy as np 
import matplotlib.pyplot as plt 
from scipy.signal import convolve
from scipy.fftpack import next_fast_len

# A, in the description above
A = np.array([1, 1, 1, 2, 1, 1])
# The result, I want to get from the deconvolution
kernel = np.array([0, 1, 2, 1, 0, 0]) 
#B, in the description above
B = convolve(kernel, data, mode='same') 

# Using the deconvolution theorem
f_A = np.fft.fft(A)
f_B = np.fft.fft(B)
# I know that you should use a regularization here 
r = f_B / f_A

# dk should be equal to kernel
dk = np.fft.ifft(r)

dk 的结果是:

dk = array([ 2.28571429-9.25185854e-18j,  1.28571429+9.25185854e-18j,
       -0.71428571-9.25185854e-18j, -0.71428571+9.25185854e-18j,
        0.28571429-9.25185854e-18j,  1.28571429+9.25185854e-18j])

预期是:

dk = array([0, 1, 2, 1, 0, 0]) 

【问题讨论】:

  • datacon 是什么?你的意思是AB
  • @meowgoesthedog,是的!对于传输错误,我深表歉意。

标签: python fft deconvolution


【解决方案1】:

确实,由于内核是 [1.0 2.0 1.0] 以 2.0 为中心(模糊和膨胀),因此内核宽度为 3。由于数组 A 在 [0..5] 上不为空,因此完全卷积数组 paddedB 在 [-1..6] 上不为空。尽管如此,函数scipy.signal.convolve(...,'same') 返回一个主干卷积数组B(0..5)=paddedB(0..5)。因此,paddedB(-1)paddedB(6) 相关的信息会丢失,并且如果使用np.convolve() 的选项same,则很难恢复内核。。 p>

为避免信息丢失,输出paddedB 将被填充以包含卷积信号的support,计算为函数A 支持和内核支持的Minkowski sumnp.convolve() 的选项full 直接计算paddedB 而不会丢失信息。

kernel=[1,2,1]
paddedB = convolve(kernel, A, mode='full')

为了使用卷积定理检索内核,输入信号A要被填充以匹配函数paddedB的支持

paddedA=np.zeros(paddedB.shape[0])
paddedA[kernel.shape[0]/2: kernel.shape[0]/2+A.shape[0]]=A[:]

# Using the deconvolution theorem
f_A = np.fft.fft(paddedA)
f_B = np.fft.fft(paddedB)
# I know that you should use a regularization here 
r = f_B / f_A

# dk should be equal to kernel
dk = np.fft.ifft(r)
# shift to get zero frequency in the middle:
dk=np.fft.fftshift(dk)

注意使用函数np.fft.fftshift()获取中间的零频。

import numpy as np 
import matplotlib.pyplot as plt 
from scipy.signal import convolve
from scipy.fftpack import next_fast_len

# A, in the description above
A = np.array([1, 1, 1, 2, 1, 1])

kernel=np.asarray([1,2,1])
paddedB = convolve(kernel, A, mode='full')
print paddedB

paddedA=np.zeros(paddedB.shape[0])
paddedA[kernel.shape[0]/2: kernel.shape[0]/2+A.shape[0]]=A[:]
#pad both signal and kernel. Requires the size of the kernel

# Using the deconvolution theorem
f_A = np.fft.fft(paddedA)
f_B = np.fft.fft(paddedB)
# I know that you should use a regularization here 
r = f_B / f_A

# dk should be equal to kernel
dk = np.fft.ifft(r)
# shift to get zero abscissa in the middle:
dk=np.fft.fftshift(dk)

print dk

如果无法获得paddedB 并且B 是唯一可用的数据,您可以尝试通过用零填充B 或平滑B 的最后值来重建填充B。它需要一些估计内核的大小。

B = convolve(A,kernel, mode='same')
paddedB=np.zeros(A.shape[0]+kernel.shape[0]-1)
paddedB[kernel.shape[0]/2: kernel.shape[0]/2+B.shape[0]]=B[:]
print paddedB

最后,window 可以同时应用于 paddedA 和 paddedB,这意味着中间的值更重要,因为要估计内核。例如 Parzen / de la Vallée Poussin 窗口:

import numpy as np 
import matplotlib.pyplot as plt 
from scipy.signal import convolve
from scipy.fftpack import next_fast_len
from scipy.signal import tukey
from scipy.signal import parzen

# A, in the description above
A = np.array([1, 1, 1, 2, 1, 1])

kernel=np.asarray([1,2,1])
paddedB = convolve(kernel, A, mode='full')
print paddedB


B = convolve(A,kernel, mode='same')
estimatedkernelsize=3
paddedB=np.zeros(A.shape[0]+estimatedkernelsize-1)
paddedB[estimatedkernelsize/2: estimatedkernelsize/2+B.shape[0]]=B[:]
print paddedB

paddedA=np.zeros(paddedB.shape[0])
paddedA[estimatedkernelsize/2: estimatedkernelsize/2+A.shape[0]]=A[:]

#applying window
#window=tukey(paddedB.shape[0],alpha=0.1,sym=True) #if longer signals, should be enough.
window=parzen(paddedB.shape[0],sym=True)
windA=np.multiply(paddedA,window)
windB=np.multiply(paddedB,window)


# Using the deconvolution theorem
f_A = np.fft.fft(windA)
f_B = np.fft.fft(windB)
# I know that you should use a regularization here 
r = f_B / f_A

# dk should be equal to kernel
dk = np.fft.ifft(r)
# shift to get the zero abscissa in the middle:
dk=np.fft.fftshift(dk)

print dk

尽管如此,估计的内核远非完美,因为 A 的大小很小:

[ 0.08341737-6.93889390e-17j -0.2077029 +0.00000000e+00j
 -0.17500324+0.00000000e+00j  1.18941919-2.77555756e-17j
  2.40994395+6.93889390e-17j  0.66720653+0.00000000e+00j
 -0.15972098+0.00000000e+00j  0.02460791+2.77555756e-17j]

【讨论】:

  • 感谢您的详细回答,但是为什么在ifft 之后使用fftshift?尽管我们不在频域中,但您确实提到了“转移以在中间获得零频率”。此外,dk 的长度在您的示例中比A 的长度更大。是否有可能在大小上相等?
  • 我可以问你一个关于这篇文章答案的问题:stackoverflow.com/a/54875879/10364180?很遗憾,我不能对这个帖子发表评论,我不知道如何在别处联系你。
  • ifft 的结果接近[2 1 0 0 ... 1],因为内核的中间在 x=0 处,而 x=0 在 np.fft 的索引 0 处。函数np.fftshift() 用于检索帧中间的内核,确实,你是对的,我们不在频域,所以不是“偏移到中间的中心频率”,而是“在框架中间获得横坐标 x=0 的转变”。
  • 为什么内核中间对应x=0?
  • 这不是强制性的,但非居中内核会导致卷积信号与原始信号相比向左或向右移动。它在integral defining the convolution 中可见。因此,旨在模糊信号的内核以 x=0 为中心。
【解决方案2】:
# I had to modify the listed code for it to work under Python3. 
# I needed to upgrade to the scipy-1.4.1 and numpy-1.18.2
# and to avoid a TypeError: slice indices must be integers
# I needed to change / to // in the line marked below
import numpy as np 
import matplotlib.pyplot as plt 
from scipy.signal import convolve 
from scipy.fftpack import next_fast_len 
# A, in the description above 
A = np.array([1, 1, 1, 2, 1, 1]) 
kernel=np.asarray([1,2,1]) 
paddedB = convolve(kernel, A, mode='full') 
print(paddedB)
paddedA=np.zeros(paddedB.shape[0]) 
# note // instead of / below
paddedA[kernel.shape[0]//2: kernel.shape[0]//2+A.shape[0]]=A[:] 
#pad both signal and kernel. Requires the size of the kernel 
# Using the deconvolution theorem 
f_A = np.fft.fft(paddedA) 
f_B = np.fft.fft(paddedB) # I know that you should use a regularization here 
r = f_B / f_A 
# dk should be equal to kernel 
dk = np.fft.ifft(r) 
# shift to get zero abscissa in the middle: 
dk=np.fft.fftshift(dk) 
print(dk)
# this gives:
#(py36) bash-3.2$ python decon.py
#[1 3 4 5 6 5 3 1]
#[ 1.11022302e-16+0.j -1.11022302e-16+0.j -9.62291355e-17+0.j
# 1.00000000e+00+0.j  2.00000000e+00+0.j  1.00000000e+00+0.j
# 9.62291355e-17+0.j -1.11022302e-16+0.j]

【讨论】:

  • 不只是将答案粘贴到堆栈溢出中,您应该尝试回答您是如何得出该特定答案的问题并解释每个部分等。而不仅仅是一堵无法阅读的代码
  • 嗨,马克 - 谢谢你的建议 - 我不得不说我什至很难让这个帖子被接受 - 界面一直告诉我我的语法是非法的,所以我没有成功提供您正确建议的那种细节。我提供的代码是上一位受访者提供的代码的编辑版本。我尝试使用该代码,但如果不进行我在介绍和 cmets 中强调的更改,就无法在 Python 3 中成功运行它。我认为其他人可能会觉得它有用
猜你喜欢
  • 2013-10-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-18
  • 1970-01-01
  • 2019-10-20
  • 2013-06-08
相关资源
最近更新 更多