【问题标题】:IFFT of FFT-image results in weird upside-down overlap of original imageFFT 图像的 IFFT 导致原始图像出现奇怪的上下重叠
【发布时间】:2020-06-14 01:43:51
【问题描述】:

在下面的代码中,我有一个函数,它返回裁剪到某个半径的中心圆的图像。然后我对图像进行傅立叶变换,然后再次进行逆傅立叶变换,以恢复图像,效果很好。

然后我计算出,半径为 43 的能谱(此处不包括)的中心圆将产生原始图像能量的 99%。但是当我尝试将我的函数“imgRadius”应用于幅度谱(傅立叶变换图像),然后对该图像执行逆傅立叶变换时,我得到了原始图像的这种奇怪的倒置重叠。

def imgRadius(img, radius):
    result = np.zeros(img.shape,np.float64)
    centerX = (img.shape[0])/2
    centerY = (img.shape[1])/2
    for m in range(img.shape[0]):
        for n in range(img.shape[1]):
            if math.sqrt((m-centerX)**2+(n-centerY)**2) < radius:
                result[m,n] = img[m,n]
    return result

imgpath = directory+"NorbertWiener.JPG"
img = cv.imread(imgpath, cv.IMREAD_GRAYSCALE)
norm_image = cv.normalize(img, None, alpha=0, beta=1, norm_type=cv.NORM_MINMAX, dtype=cv.CV_32F)
amp = (np.fft.fftshift(np.fft.fft2(norm_image)))
amp_log = np.log(np.abs(amp))
norm_amp = cv.normalize(amp_log, None, alpha=0, beta=1, norm_type=cv.NORM_MINMAX, dtype=cv.CV_32F)
restoredAMP = np.abs(np.fft.ifft2(np.fft.ifftshift(amp)))
radamp = imgRadius(norm_amp,43)
rest99 = np.abs(np.fft.ifft2((imgRadius(amp,43))))

plt.subplot(231),plt.imshow(norm_image, "gray", vmin=0, vmax=1),plt.title('Image')
plt.xticks([]), plt.yticks([])
plt.subplot(232),plt.imshow(norm_amp, "gray", vmin=0, vmax=1),plt.title('Amplitude')
plt.xticks([]), plt.yticks([])
plt.subplot(233),plt.imshow(restoredAMP, "gray", vmin=0, vmax=1),plt.title('Restored from amplitude')
plt.xticks([]), plt.yticks([])
plt.subplot(235),plt.imshow(rest99, "gray", vmin=0, vmax=1),plt.title('Restored from r=43')
plt.xticks([]), plt.yticks([])
plt.subplot(234),plt.imshow(radamp, "gray", vmin=0, vmax=1),plt.title('Amplitude r=43')
plt.xticks([]), plt.yticks([])
plt.show()

这与我使用绝对值这一事实有什么关系吗?我看不出我应该如何在不消除图像的虚部的情况下绘制图像,但我可以看到一些信息可能会在逆 fft 中丢失。

我只是不明白为什么在原始幅度谱上执行 IFFT 时没有遇到问题。

【问题讨论】:

  • 我认为您需要在恢复之前将 imgRadius() 应用于 amp 而不是 norm_amp。
  • @fmw42 这就是我试图用“rest99”做的,它在子图中显示为第 5 个图像,显示“后折叠”
  • 要在使用半径遮罩后恢复图像,您需要遮罩 FFT 的结果,而不是日志标准化版本。在那做IFT。我不明白您为什么要对日志标准化图像进行 IFT。
  • @fmw42 也许代码有点混乱,但我并不想恢复掩码日志规范化版本的 FFT。这仅用于绘图。我屏蔽并尝试恢复的版本只是amp,这只是FFT的结果。查看变量“rest99”
  • 尝试将rest99 = np.abs(np.fft.ifft2((imgRadius(amp,43))))更改为rest99 = np.abs(np.fft.ifft2((imgRadius(amp,43)))).clip(0,255).astype(np.uint8)

标签: python numpy opencv image-processing fft


【解决方案1】:

问题发生在这里:

def imgRadius(img, radius):
    result = np.zeros(img.shape,np.float64)

您正在创建一个实数值数组,并复制复数值。可能将实部或幅度写入数组。在任何情况下,复值频域数据都变为实值。逆变换是一个对称矩阵。

为了解决这个问题,将result初始化为一个复值数组。

在此之后,确保使用逆变换的实部,而不是幅度,如Gianluca already suggested in their answer

【讨论】:

  • 是的,我同意。我之前没有看到。但我不知道为什么 OP 会进行像素迭代处理以切出 dft 中的圆圈。我的方式,使用蒙版和乘法,更好,因为它允许软化边缘以避免振铃伪影。
  • 这解决了问题!谢谢您的帮助。通过将结果初始化为 img 的副本,然后将圆外的像素归零,我得到了相同的效果,但具有复杂的值。正如@fmw42 指出的那样,我本可以使用 cv2 的内置功能,并对蒙版应用某种类型的低通滤波器以避免模糊。但 Cris Luengo 在这里指出的绝对是我最搞砸的部分。
【解决方案2】:

丢弃 FFT 的虚部(以及实部的符号)正是导致倒置图像“反折叠”到自身中的问题。关于其原点对称的函数的 FFT 是实数(即虚部 0)。通过丢弃虚部,图像因此以某种方式“对称化”。

在傅里叶空间对复数FFT结果进行运算后,可以取逆FFT,然后只绘制它的实部np.fft.ifft2((imgRadius(amp,43))).real

【讨论】:

  • 好的,我看到绘制实部与将 np.abs 应用于图像时相同,对吧?我仍然对如何在没有任何问题的情况下从 amp 恢复图像感到困惑,但是当 amp 被裁剪成圆形时,它会显示“后折叠”特征。
  • @HamDerDolski:如果 IFFT 的虚部不接近于零,则过滤器的创建方式存在错误。它必须完全以 0 频率为中心,并且围绕该点完全对称。
  • @CrisLuengo 是的!我知道这可能会导致问题。但是,据我所见,应该改变的是 imgRadius 函数中的 centerX 和 centerY 的计算。在这里,我尝试在除以 2 之前和之后从每个计算中减去 1,但这似乎没有帮助。图像看起来仍然很奇怪。我在这里遗漏了一些明显的缺陷吗?
  • @HamDerDolski:我认为中心需要在img.shape[0] // 2(整数除法)。
【解决方案3】:

以下内容在 Python/OpenCV/Numpy 中对我有用,并显示了使用尖锐边界圆与已通过高斯模糊平滑以减轻振铃伪影的区别

输入:

import numpy as np
import cv2

# read input and convert to grayscale
img = cv2.imread('lena_gray.png', cv2.IMREAD_GRAYSCALE)

# do dft saving as complex output
dft = np.fft.fft2(img)

# apply shift of origin to center of image
dft_shift = np.fft.fftshift(dft)

# generate spectrum from magnitude image (for viewing only)
mag = np.abs(dft_shift)
spec = np.log(mag) / 20

# create circle mask
radius = 32
mask = np.zeros_like(img)
cy = mask.shape[0] // 2
cx = mask.shape[1] // 2
cv2.circle(mask, (cx,cy), radius, (255,255,255), -1)[0]

# blur the mask
mask2 = cv2.GaussianBlur(mask, (19,19), 0)

# apply mask to dft_shift
dft_shift_masked = np.multiply(dft_shift,mask) / 255
dft_shift_masked2 = np.multiply(dft_shift,mask2) / 255


# shift origin from center to upper left corner
back_ishift = np.fft.ifftshift(dft_shift)
back_ishift_masked = np.fft.ifftshift(dft_shift_masked)
back_ishift_masked2 = np.fft.ifftshift(dft_shift_masked2)


# do idft saving as complex output
img_back = np.fft.ifft2(back_ishift)
img_filtered = np.fft.ifft2(back_ishift_masked)
img_filtered2 = np.fft.ifft2(back_ishift_masked2)

# combine complex components to form original image again
img_back = np.abs(img_back).clip(0,255).astype(np.uint8)
img_filtered = np.abs(img_filtered).clip(0,255).astype(np.uint8)
img_filtered2 = np.abs(img_filtered2).clip(0,255).astype(np.uint8)


cv2.imshow("ORIGINAL", img)
cv2.imshow("SPECTRUM", spec)
cv2.imshow("MASK", mask)
cv2.imshow("MASK2", mask2)
cv2.imshow("ORIGINAL DFT/IFT ROUND TRIP", img_back)
cv2.imshow("FILTERED DFT/IFT ROUND TRIP", img_filtered)
cv2.imshow("FILTERED2 DFT/IFT ROUND TRIP", img_filtered2)
cv2.waitKey(0)
cv2.destroyAllWindows()

# write result to disk
cv2.imwrite("lena_dft_numpy_mask.png", mask)
cv2.imwrite("lena_dft_numpy_mask_blurred.png", mask2)
cv2.imwrite("lena_dft_numpy_roundtrip.png", img_back)
cv2.imwrite("lena_dft_numpy_lowpass_filtered1.png", img_filtered)
cv2.imwrite("lena_dft_numpy_lowpass_filtered2.png", img_filtered2)


锋利的面具:

模糊蒙版:

简单往返:

来自锐利蒙版的低通滤波结果(明显振铃):

模糊蒙版的低通滤波结果(振铃减轻):

【讨论】:

  • 感谢您的帮助。我对opencv还很陌生,所以我不知道它内置了圆形遮罩功能。我还看到了遮罩的低通滤波如何帮助减少振铃,感谢您指出这一点。正如@Cris Luengo 指出的那样,我在自己的圆形蒙版函数“imgRadius”中初始化结果图像时搞砸了,并修复了“后折叠”问题。
猜你喜欢
  • 1970-01-01
  • 2015-09-01
  • 1970-01-01
  • 2013-02-08
  • 2014-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-27
相关资源
最近更新 更多