坐看云起时|

一枚码农

园龄:7年6个月粉丝:5关注:1

opencv-python 4.11.1 傅里叶变换

理论

傅立叶变换用于分析各种滤波器的频率特性。对于图像,2D离散傅里叶变换(DFT)用于找到频域。称为快速傅里叶变换(FFT)的快速算法用于计算DFT。有关这些的详细信息可以在任何图像处理或信号处理教科书中找到。

对于正弦信号,x(t)= Asin(2πft),我们可以说f是信号的频率,如果采用其频域,我们可以看到f处的尖峰。如果对信号进行采样以形成离散信号,则我们得到相同的频域,但在[-π,π]或[0,2π](或对于N点DFT的[0,N])范围内是周期性的。你可以将图像视为在两个方向上采样的信号。因此,在X和Y方向上进行傅里叶变换可以得到图像的频率表示。

更直观地说,对于正弦信号,如果幅度在短时间内变化如此之快,则可以说它是高频信号。如果变化缓慢,则为低频信号。你可以将相同的想法扩展到图像。幅度在图像中的幅度变化很大?在边缘点,或噪音。我们可以说,边缘和噪声是图像中的高频内容。如果幅度没有太大变化,则它是低频分量。 (一些链接被添加到Additional Resources_,它通过示例直观地解释了频率变换)。

现在我们将看到如何找到傅立叶变换。

Numpy中的傅里叶变换

首先,我们将看到如何使用Numpy找到傅立叶变换。Numpy有一个FFT包来做到这一点。np.fft.fft2()为我们提供了一个复杂数组的频率变换。它的第一个参数是输入图像,它是灰度。第二个参数是可选的,它决定了输出数组的大小。如果它大于输入图像的大小,则在计算FFT之前用零填充输入图像。如果小于输入图像,则将裁剪输入图像。如果没有传递参数,则输出数组大小将与输入相同。

现在,一旦得到结果,零频率分量(DC分量)将位于左上角。如果要将其置于中心位置,则需要在两个方向上将结果移动$$\frac{N}{2}$$。这只是通过函数np.fft.fftshift()完成的。找到频率变换后,你可以找到幅度谱。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\opera.jpg', 0)

# 傅里叶变换 需要是灰度图像噢
f = np.fft.fft2(img)

# 零频率分量 移动到中间
fshift = np.fft.fftshift(f)

magnitude_spectrum = 20*np.log(np.abs(fshift))
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])

plt.subplot(122),plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

image

请注意,你可以在中心看到更多更白的区域,显示低频内容更多。

所以你找到了频率变换现在你可以在频域做一些操作,比如高通滤波和重建图像,即找到逆DFT。 为此,你只需通过使用尺寸为60x60的矩形窗口进行遮罩来移除低频。 然后使用np.fft.ifftshift()应用反向移位,以便DC组件再次出现在左上角。 然后使用np.ifft2()函数找到逆FFT。 结果再次是一个复杂的数字。 你可以采取它的绝对价值。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\opera.jpg', 0)

# 快速傅里叶变换 需要是灰度图像噢
f = np.fft.fft2(img)

# 零频率分量 移动到中间
fshift = np.fft.fftshift(f)

# magnitude_spectrum = 20 * np.log(np.abs(fshift))
# plt.subplot(121), plt.imshow(img, cmap='gray')
# plt.title('Input Image'), plt.xticks([]), plt.yticks([])
#
# plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
# plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
# plt.show()

rows, cols = img.shape
print(1111, rows/2, cols/2)
# 使用60 * 60的块遮盖低频区域
crow, ccol = int(rows / 2), int(cols / 2)
fshift[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

# 反向位移
f_ishift = np.fft.ifftshift(fshift)

# 逆傅里叶变换
img_back = np.fft.ifft2(f_ishift)

# 取绝对值
img_back = np.abs(img_back)

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])

plt.subplot(122), plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
plt.show()

image
结果显示高通滤波是边缘检测操作。这是我们在4.7. Canny边缘检测章节中看到的。这也表明大多数图像数据存在于光谱的低频区域。无论如何,我们已经看到如何在Numpy中找到DFT,IDFT等。现在让我们看看如何在OpenCV中完成它。

如果你仔细观察结果,特别是JET颜色的最后一个图像,你可以看到一些纹物(我用红色箭头标记的一个实例)。它在那里显示出一些类似波纹的结构,它被称为振铃效应。它是由我们用于遮蔽的矩形窗口引起的。此掩膜转换为sinc形状,这会导致此问题。因此矩形窗口不用于过滤。更好的选择是高斯窗口。

OpenCV中的傅里叶变换

OpenCV为此提供了cv.dft()和cv.idft()函数。它返回与之前相同的结果,但有两个通道。第一个通道将具有结果的实部,第二个通道将具有结果的虚部。输入图像应首先转换为np.float32。我们将看到如何做到这一点。
cv2.dft(src, dst=None, flag=None, nonzeroRows=0)

  • src:输入图像,要求 np.float32 格式;
  • dst:输出图像,双通道(实部+虚部),大小和类型取决于第三个参数 flags;
  • flags:表示转换标记,默认为 0,存在多种取值,参见后文;
  • nonzeroRows:默认为 0,暂时不考虑。

flags 取值如下:

  • DFT_INVERSE:用一维或二维逆变换取代默认的正向变换;
  • DFT_SCALE: 缩放比例标识符,根据数据元素个数平均求出其缩放结果,如有 N 个元素,则输出结果以 1/N 缩放输出,常与 DFT_INVERSE 搭配使用;
  • DFT_ROWS:对输入矩阵的每行进行正向或反向的傅里叶变换;此标识符可在处理多种适量的的时候用于减小资源的开销,这些处理常常是三维或高维变换等复杂操作;
  • DFT_COMPLEX_OUTPUT:对一维或二维的实数数组进行正向变换,这样的结果虽然是复数阵列,但拥有复数的共轭对称性(CCS),可以以一个和原数组尺寸大小相同的实数数组进行填充,这是最快的选择也是函数默认的方法。你可能想要得到一个全尺寸的复数数组(像简单光谱分析等等),通过设置标志位可以使函数生成一个全尺寸的复数输出数组;
  • DFT_REAL_OUTPUT:对一维二维复数数组进行逆向变换,这样的结果通常是一个尺寸相同的复数矩阵,但是如果输入矩阵有复数的共轭对称性(比如是一个带有 DFT_COMPLEX_OUTPUT标识符的正变换结果),便会输出实数矩阵。

总结下来就是:

  • DFT_COMPLEX_OUTPUT:得到一个复数形式的矩阵;
  • DFT_REAL_OUTPUT:只输出复数的实部;
  • DFT_INVERSE:进行傅里叶逆变换;
  • DFT_SCALE:是否除以 MxN (M 行 N 列的图片,共有有 MxN 个像素点);
  • DFT_ROWS:输入矩阵的每一行进行傅里叶变换或者逆变换。

最后需要注意的是,输出的频谱结果是一个复数,需要调用 cv2.magnitude() 函数将傅里叶变换的双通道结果转换为 0 到 255 的范围。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\opera.jpg', 0)

# 傅里叶变换
dft = cv.dft(np.float32(img), flags=cv.DFT_COMPLEX_OUTPUT)

# 零频率分量 移动到中间
dft_shift = np.fft.fftshift(dft)

magnitude_spectrum = 20 * np.log(cv.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

image
所以,现在我们必须进行逆DFT。 在之前的会话中,我们创建了一个HPF,这次我们将看到如何去除图像中的高频内容,即我们将LPF应用于图像。 它实际上模糊了图像。 为此,我们首先在低频处创建具有高值(1)的掩模,即我们传递LF内容,并且在HF区域传递0。

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread(r'C:\Users\yuyalong\Pictures\Saved Pictures\opera.jpg', 0)

# 傅里叶变换
dft = cv.dft(np.float32(img), flags=cv.DFT_COMPLEX_OUTPUT)

# 零频率分量 移动到中间
dft_shift = np.fft.fftshift(dft)

# magnitude_spectrum = 20 * np.log(cv.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
#
# plt.subplot(121), plt.imshow(img, cmap='gray')
# plt.title('Input Image'), plt.xticks([]), plt.yticks([])
# plt.subplot(122), plt.imshow(magnitude_spectrum, cmap='gray')
# plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
# plt.show()

rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)
# create a mask first, center square is 1, remaining all zeros
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1

# apply mask and inverse DFT
fshift = dft_shift * mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv.idft(f_ishift)
img_back = cv.magnitude(img_back[:, :, 0], img_back[:, :, 1])

plt.subplot(121), plt.imshow(img, cmap='gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122), plt.imshow(img_back, cmap='gray')
plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.show()

image

注意:像往常一样,OpenCV函数cv.dft()和cv.idft()比Numpy函数更快。 但是Numpy功能更加用户友好。

DFT的性能优化

对于某些阵列大小,DFT计算的性能更好。 当阵列大小为2的幂时,它是最快的。 尺寸为2,3和5的乘积的阵列也可以非常有效地处理。 因此,如果你担心代码的性能,可以在找到DFT之前将数组的大小修改为任何最佳大小(通过填充零)。 对于OpenCV,你必须手动填充零。 但对于Numpy,你可以指定FFT计算的新大小,它会自动为你填充零。

那么我们如何找到这个最佳尺寸? OpenCV为此提供了一个函数cv.getOptimalDFTSize()。 它适用于cv.dft()和np.fft.fft2()。 让我们使用IPython magic命令timeit检查它们的性能。

In [16]: img = cv.imread('messi5.jpg',0)
In [17]: rows,cols = img.shape
In [18]: print("{} {}".format(rows,cols))
342 548
In [19]: nrows = cv.getOptimalDFTSize(rows)
In [20]: ncols = cv.getOptimalDFTSize(cols)
In [21]: print("{} {}".format(nrows,ncols))
360 576

看,大小(342,548)被修改为(360,576)。 现在让我们用零填充它(对于OpenCV)并找到它们的DFT计算性能。 你可以通过创建一个新的大零数组并将数据复制到它,或使用cv.copyMakeBorder()来实现。

nimg = np.zeros((nrows,ncols))
nimg[:rows,:cols] = img

或者

right = ncols - cols
bottom = nrows - rows
bordertype = cv.BORDER_CONSTANT #just to avoid line breakup in PDF file
nimg = cv.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)

现在我们计算Numpy函数的DFT性能比较:

In [22]: %timeit fft1 = np.fft.fft2(img)
10 loops, best of 3: 40.9 ms per loop
In [23]: %timeit fft2 = np.fft.fft2(img,[nrows,ncols])
100 loops, best of 3: 10.4 ms per loop

它显示了4倍的加速。现在我们将尝试使用OpenCV函数。

In [24]: %timeit dft1= cv.dft(np.float32(img),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 13.5 ms per loop
In [27]: %timeit dft2= cv.dft(np.float32(nimg),flags=cv.DFT_COMPLEX_OUTPUT)
100 loops, best of 3: 3.11 ms per loop

它还显示了4倍的加速。 你还可以看到OpenCV函数比Numpy函数快3倍。这也可以进行逆FFT测试,这可以作为练习。

为什么拉普拉斯算子是高通滤波器?

在论坛中提出了类似的问题。 问题是,为什么拉普拉斯算子是高通滤波器? 为什么Sobel是HPF? 第一个答案就是傅立叶变换。 只需将拉普拉斯算子的傅里叶变换用于更高尺寸的FFT。 分析一下:

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
# simple averaging filter without scaling parameter
mean_filter = np.ones((3,3))
# creating a gaussian filter
x = cv.getGaussianKernel(5,10)
gaussian = x*x.T
# different edge detecting filters
# scharr in x-direction
scharr = np.array([[-3, 0, 3],
                   [-10,0,10],
                   [-3, 0, 3]])
# sobel in x direction
sobel_x= np.array([[-1, 0, 1],
                   [-2, 0, 2],
                   [-1, 0, 1]])
# sobel in y direction
sobel_y= np.array([[-1,-2,-1],
                   [0, 0, 0],
                   [1, 2, 1]])
# laplacian
laplacian=np.array([[0, 1, 0],
                    [1,-4, 1],
                    [0, 1, 0]])
filters = [mean_filter, gaussian, laplacian, sobel_x, sobel_y, scharr]
filter_name = ['mean_filter', 'gaussian','laplacian', 'sobel_x', \
                'sobel_y', 'scharr_x']
fft_filters = [np.fft.fft2(x) for x in filters]
fft_shift = [np.fft.fftshift(y) for y in fft_filters]
mag_spectrum = [np.log(np.abs(z)+1) for z in fft_shift]
for i in xrange(6):
    plt.subplot(2,3,i+1),plt.imshow(mag_spectrum[i],cmap = 'gray')
    plt.title(filter_name[i]), plt.xticks([]), plt.yticks([])
plt.show()

窗口将如下图显示:
image

本文作者:一枚码农

本文链接:https://www.cnblogs.com/yimeimanong/p/17292195.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   一枚码农  阅读(162)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
 
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起
  1. 1 Sold Out Hawk
  2. 2 光辉岁月 Beyond
光辉岁月 - Beyond
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

作词 : 黄家驹

作曲 : 黄家驹

编曲 : Beyond

制作人 : Beyond/Gordon O'Yang

Synth Programming : Gordon O'Yang / 叶世荣

Mixed by Philip Kwok

钟声响起归家的讯号

钟声响起归家的讯号

在他生命里

仿佛带点唏嘘

黑色肌肤给他的意义

是一生奉献 肤色斗争中

年月把拥有变做失去

疲倦的双眼带着期望

今天只有残留的躯壳

迎接光辉岁月

风雨中抱紧自由

一生经过彷徨的挣扎

自信可改变未来

问谁又能做到

可否不分肤色的界限

可否不分肤色的界限

愿这土地里

不分你我高低

缤纷色彩闪出的美丽

是因它没有

分开每种色彩

年月把拥有变做失去

疲倦的双眼带着期望

今天只有残留的躯壳

迎接光辉岁月

风雨中抱紧自由

一生经过彷徨的挣扎

自信可改变未来

问谁又能做到

今天只有残留的躯壳

今天只有残留的躯壳

迎接光辉岁月

风雨中抱紧自由

一生经过彷徨的挣扎

自信可改变未来

问谁又能做到

Woo

Ah

Ah

今天只有残留的躯壳

今天只有残留的躯壳

迎接光辉岁月

风雨中抱紧自由

一生经过彷徨的挣扎

自信可改变未来

问谁又能做到

Woo

Ah

Ah

今天只有残留的躯壳

今天只有残留的躯壳

迎接光辉岁月

风雨中抱紧自由

一生经过彷徨的挣扎

自信可改变未来