第14章:傅里叶变换
- 一、理论基础:
- 二、Numpy实现傅里叶变换:
- 1. 实现傅里叶变换:
- 2. 逆傅里叶变换:
- 3. 高通滤波示例:
- 三、OpenCV实现傅里叶变换:
- 1. 实现傅里叶变换:
- 2. 实现逆傅里叶变换:
- 3. 低通滤波示例:
图像处理一般分为空间域处理和频率域处理。
空间域:
空间域处理是直接对图像内的像素点进行处理。空间域处理主要划分为灰度变换和空间滤波两种形式。灰度变换是对图像内的单个像素进行处理,比如调节对比度和处理域值等。空间滤波涉及图像的质量改变,比如图像平滑处理。空间域处理的计算简单方便,运算速度更快。
频率域:
频率域处理是先将图像变换的频率域,然后在频率域对图像进行处理,最后在通过反变换将图像从频率域变换到空间域。傅里叶变换是应用最广泛的一种频域变换,它能够将图像从空间域变换到频率域,而你傅里叶变换能够将频率域信息变换到空间域内。傅里叶变换在图像处理领域有着非常重要的作用。
下面从理论基础、基本实现、具体应用等角度对傅里叶变回进行简单的介绍。
一、理论基础:
傅里叶变换非常抽象,很多人在工程中用了很多年的傅里叶变换也没有彻底了解傅里叶变回到底是怎么回事。为了更好的说明傅里叶变换,我们先看生活中的一个例子。
下表所示是某饮料的配方,该配方是一个以时间形式表示的表格,表格很长,这里仅仅截取了其中一部分。该表中记录冲时刻“00:00”开始到某个特定时间“00:11”内的操作。
分析表格发现,该配方:
- 每隔1分钟放一块冰糖。
- 每隔2分钟放3粒红豆。
- 每隔3分钟放2粒绿豆。
- 每隔4分钟放4块西红柿。
- 每隔5分钟放1杯纯净水。
上述文字是从操作频率的角度对配方进行说明。
在数据处理过程中,经常使用图表的形式表述信息。如果从时域的角度,该配方表可以表示为如下图形式。
如果从频率(周期)的角度表示,这个配方表可以表示为如下图形式,图中横坐标是周期(频率的倒数),纵坐标是配料的份数。
对于函数,同样可以将其从时域变换到频域。下图是一个频率为5(1秒5个周期)、振幅为1的正弦曲线。
如果从频率的角度考虑,则可以将其绘制为如下图所示的频域图。图中横坐标是频率,纵坐标是振幅。
上述两图是等价的,他们是一个函数的两种不同表示方式。可以通过频域表示得到对应的时域表示,也可以通过时域表示得到对应的频域表示。
法国数学家傅里叶指出,任何周期函数都可以表示为不同频率的正弦函数和的形式。在今天看来,这个理论是理所当然的,但是这个理论难以理解,在但是遭受了很大的质疑。
下面我们来看傅里叶变换的具体过程。例如,周期函数的曲线如下图左上角所示。该周期函数可以表示为:
- y = 3 * np.sin(0.8 * x) + 7 * np.sin(0.5 * x) +2 * np.sin(0.2 * x)
因此,该函数可以看成是由下列三个函数的和构成的:
- y1 = 3 * np.sin(0.8 * x)
- y2 = 7 * np.sin(0.5 * x)
- y3 = 2 * np.sin(0.2 * x)
上述三个函数对应的函数曲线分别如下图右上角、左下角、右下角的图所示。
如果从频域的角度考虑,上述三个正弦函数可以分别表示为下图中的三根柱子,图中横坐标是频率,纵坐标是振幅。
通过以上分析可知,图中左上角的曲线可以表示为上图所示的频域图。
从左上角的时域函数图形,构造出上图所示频域图像的过程,就是傅里叶变换。
左上角的时域函数图形,与上图的频域图形表示的是完全相同的信息。傅里叶变换就是从频域的角度完整地表述时域信息。
除了上述的频率和振幅外,还要考虑时间的问题。例如,饮料配方为了控制风味,需要严格控制加入配料的时间。上表中“00:00”时刻的操作,在更精细的控制下,实际上如下表所示。
如果加入配料的时间发生了变换,饮料的风味就会发生变化。所以,在实际处理过程中,还要考虑时间差。这个时间差,在傅里叶变换中就是相位
。相位表述的时域时间差相关的信息。
例如,函数
- y = 3 * np.sin(0.8 * x) + 7 * np.sin(0.5 * 2 + 2) + 2 * np.sin(0.2 * x + 2)
可以看成下列三个函数的和的形式:
- y1 = 3 * np.sin(0.8 * x)
- y2 = 7 * np.sin(0.5 * x + 2)
- y3 = 2 * np.sin(0.2 * x + 3)
上述的四个函数分别对应的函数曲线为下图中的左上角、右上角、左下角、右下角。
在本例中,如果把横坐标看成开始时间,则构成函数y的三个正弦函数并不都是从0时刻开始的,他们之间存在时间差。如果直接使用没有时间差的函数,则无法构成上图左上角所示的函数,而是会构成前面我们所说到的那个周期函数y = 3 * np.sin(0.8 * x) + 7 * np.sin(0.5 * x) +2 * np.sin(0.2 * x)。所以,相差是傅里叶变换中一个非常重要的条件。
上面分别用饮料配方和函数的例子介绍了时域与频域转换的可行性,以帮助我们来理解傅里叶变换。
在图形处理过程中,傅里叶变换就是将图像分解成正弦分量和余弦分量两部分,即将图像从空间域转换到频率域。 数字图像经过傅里叶变换后,得到的频域是复数。因此,显示傅里叶变换的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magnitude image)加相位图像(phase image)的形式。
因为幅度图像中包含了原图我们所需要的大部分信息,所以图像处理过程中,通常仅使用幅度图像。当然,如果希望在频域内对图像进行处理,在通过逆傅里叶变换得到修改后的空域图像。就必须同时保留幅度图像和相位图像。
对图像进行傅里叶变换后,我们会得到图像中低频和高频的信息。低频信息对应图像中变化缓慢的灰度分量。高频信息对应图像内变换越来越快的灰度分量,是由灰度的尖锐过渡造成的。例如,在一幅大草原的图像中,低频信息就对应着广袤的颜色趋于一致的草原等细节信息,而高频信息则对应着狮子的轮廓等各种边缘及噪声信息。
傅里叶变换的目的,就是为了将图像从空域转换到频域,并在频域内实现对图像的特定处理,然后再对经过处理的频域图像进行逆傅里叶变换得到空域图像。 傅里叶变换在图像处理领域发挥着非常关键的作用,可以实现图像增强、图像去噪、边缘检测、特征提取、图像压缩和加密等。
二、Numpy实现傅里叶变换:
Numpy模块提供了傅里叶变换功能,Numpy模块中的fft2()函数可以实现图像的傅里叶变换。本节介绍如何用Numpy模块实现图像的傅里叶变换,以及在频域内过滤图像的低频信息,保留高频信息,实现高通滤波。
1. 实现傅里叶变换:
Numpy模块提供了实现傅里叶变换的函数numpy.fft.fft2(),它的语法格式是:
- 返回值=numpy.fft.fft2(原始图像)
这里需要注意的是,参数“原始图像”的类型是灰度图像,函数的返回值是一个复数数组(complex ndarray)。
经过该函数的处理,就能得到图像的频谱信息。此时,图像频谱中的零频率分量位于频谱图像(频域图像)的左上角,为了便于观察,通常会使用numpy.fft.fftshift()函数将零频率成分移动到频域图像的中心位置,如图所示。
函数numpy.fft.fftshift()的语法格式是:
- 返回值=numpy.fft.fftshift(原始频谱)
使用该函数处理后,图像频谱中的零频率分量会被移到频域图像的中心位置,对于观察傅里叶变换后频谱中的零频率部分非常有效。
对图像进行傅里叶变换后,得到的是一个复数数组。为了显示为图像,需要将它们的值调整到[0,255]的灰度空间内,使用的公式为:
- 像素新值=20*np.log(np.abs(频谱值))
示例1:实现傅里叶变换
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../lena.bmp', 0)f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))plt.subplot(1, 2, 1)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')plt.subplot(1, 2, 2)
plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('result')
plt.axis('off')
plt.show()
2. 逆傅里叶变换:
需要注意的是,如果在傅里叶变换过程中使用了numpy.fft.fftshift()函数移动零频率分量,那么在逆傅里叶变换过程中,需要先使用numpy.fft.ifftshift()函数将零频率分量移到原来的位置,再进行逆傅里叶变换。
函数numpy.fft.ifftshift()是numpy.fft.fftshift()的逆函数,其语法格式为:
- 调整后的频谱=numpy.fft.ifftshift(原始频谱)
numpy.fft.ifft2()函数是numpy.fft.fft2()的逆函数。用来实现逆傅里叶变换,返回空域的复数数组。该函数的语法格式为:
- 返回值=numpy.fft.ifft2(频域数据)
函数numpy.fft.ifft2()的返回值仍旧是一个复数数组(complex ndarray)。
逆傅里叶变换得到的空域信息是一个复数数组,需要将该信息调整至[0,255]灰度空间内,使用的公式为:
- iimg=np.abs(逆傅里叶变换结果)
示例1:实现逆傅里叶变换
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
magnitude_spectrum = 20 * np.log(np.abs(fshift))ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)plt.subplot(131)
plt.imshow(img, cmap='gray')
plt.title('img')
plt.axis('off')plt.subplot(132)
plt.imshow(magnitude_spectrum, cmap='gray')
plt.title('fft2')
plt.axis('off')plt.subplot(133)
plt.imshow(iimg, cmap='gray')
plt.title('iimg')
plt.axis('off')
plt.show()
3. 高通滤波示例:
在一幅图像内,同时存在着高频信号和低频信号。
- 低频信号对应图像内变化缓慢的灰度分量。例如,在一幅大草原的图像中,低频信号对应着颜色趋于一致的广袤草原。
- 高频信号对应图像内变化越来越快的灰度分量,是由灰度的尖锐过渡造成的。如果在上面的大草原图像中还有一头狮子,那么高频信号就对应着狮子的边缘等信息。
滤波器能够允许一定频率的分量通过或者拒绝其通过,按照其作用方式可以划分为低通滤波器和高通滤波器。
- 允许低频信号通过的滤波器称为低通滤波器。低通滤波器使高频信号衰减而对低频信号放行,会使图像变模糊。
- 允许高频信号通过的滤波器称为高通滤波器。高通滤波器使低频信号衰减而让高频信号通过,将增强图像中尖锐的细节,但是会导致图像的对比度降低。
傅里叶变换可以将图像的高频信号和低频信号分离。 例如,傅里叶变换可以将低频信号放置到傅里叶变换图像的中心位置,如前图所示,低频信号位于右图的中心位置。可以对傅里叶变换得到的高频信号和低频信号分别进行处理,例如高通滤波或者低通滤波。在对图像的高频或低频信号进行处理后,再进行逆傅里叶变换返回空域,就完成了对图像的频域处理。通过对图像的频域处理,可以实现图像增强、图像去噪、边缘检测、特征提取、压缩和加密等操作。
例如,在下图所示,左图original是原始图像,中间的图像result是对左图original进行傅里叶变化后得到的结果,右图则是对result进行高通滤波后的结果。将傅里叶变换结果图像result中的低频分量值都替换为0(处理为黑色),就屏蔽了低频信号,只保留高频信号,实现高通滤波。
要将上图中右图中间的像素值都置零,需要先计算其中心位置的坐标,然后选取以该坐标为中心,上下左右各30个像素大小的区域,将这个区域内的像素值置零。该滤波器的实现方法为:
-
rows,cols=img.shape
crow,ccol=int(rows/2),int(cols/2)
fshift[crow-30:crow+30,ccol-30:ccol+30]=0
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)
fshift[crow - 30: crow + 30, ccol - 30: ccol + 30] = 0ishift = np.fft.ifftshift(fshift)
iimg = np.fft.ifft2(ishift)
iimg = np.abs(iimg)plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')plt.subplot(122)
plt.imshow(iimg, cmap='gray')
plt.title('iimg')
plt.axis('off')plt.show()
三、OpenCV实现傅里叶变换:
OpenCV提供了函数cv2.dft()和cv2.idft()来实现傅里叶变换和逆傅里叶变换。
1. 实现傅里叶变换:
OpenCV中使用函数cv2.dft()进行傅里叶变换,语法格式为:
- 返回结果=cv2.dft(原始图像,转换标识)
在使用该函数时,需要注意参数的使用规范:
- 对于参数“原始图像”,要首先使用np.float32()函数将图像转换成np.float32格式。
- “转换标识”的值通常为“cv2.DFT_COMPLEX_OUTPUT”,用来输出一个复数阵列。
函数cv2.dft()返回的结果与使用Numpy进行傅里叶变换得到的结果是一致的,但是它返回的值是双通道的,第1个通道是结果的实数部分,第2个通道是结果的虚数部分。
经过函数 cv2.dft()的变换后,我们得到了原始图像的频谱信息。此时,零频率分量并不在中心位置,为了处理方便需要将其移至中心位置,可以用函数numpy.fft.fftshift()实现。例如,如下语句将频谱图像 dft 中的零频率分量移到频谱中心,得到了零频率分量位于中心的频谱图像dftshift。
- dftShift=np.fft.fftshift(dft)
经过上述处理后,频谱图像还只是一个由实部和虚部构成的值。要将其显示出来,还要做进一步的处理才行。
函数cv2.magnitude()可以计算频谱信息的幅度。该函数的语法格式为:
- 返回值=cv2.magnitude(参数1,参数2)
- 参数1:浮点型x坐标值,也就是实部。
- 参数2:浮点型y坐标值,也就是虚部,它必须和参数1具有相同的大小(size值的大小,不是value值的大小)。
函数cv2.magnitude()的返回值是参数1和参数2的平方和的平方根,公式为:
I表示原始图像,dst表示目标图像。
得到频谱信息的幅度后,通常还要对幅度值做进一步的转换,以便将频谱信息以图像的形式展示出来。简单来说,就是需要将幅度值映射到灰度图像的灰度空间[0,255]内,使其以灰度图像的形式显示出来。
这里使用的公式为:
- result=20*np.log(cv2.magnitude(实部,虚部))
示例1:
import cv2
import numpy as npimg = cv2.imread('../lena.bmp', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)result = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))print(dft)
print(dft_shift)
print(result)
示例2:用OpenCV函数对图像进行傅里叶变换,并展示其频谱信息。
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../lena.bmp', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
result = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))
plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')plt.subplot(122)
plt.imshow(result, cmap='gray')
plt.title('result')
plt.axis('off')plt.show()
2. 实现逆傅里叶变换:
在OpenCV中,使用函数cv2.idft()实现逆傅里叶变换,该函数是傅里叶变换函数cv2.dft()的逆函数。其语法格式为:
- 返回结果=cv2.idft(原始数据)
对图像进行傅里叶变换后,通常会将零频率分量移至频谱图像的中心位置。如果使用函数numpy.fft.fftshift()移动了零频率分量,那么在进行逆傅里叶变换前,要使用函数numpy.fft.ifftshift()将零频率分量恢复到原来位置。
注意,在进行逆傅里叶变换后,得到的值仍旧是复数,需要使用函数cv2.magnitude()计算其幅度。
示例:
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)rst = 20 * np.log(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1]))ishift = np.fft.ifftshift(dft_shift)
iimg = cv2.idft(ishift)
iimg = cv2.magnitude(iimg[:, :, 0], iimg[:, :, 1])plt.subplot(131)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')plt.subplot(132)
plt.imshow(rst, cmap='gray')
plt.title('rst')
plt.axis('off')plt.subplot(133)
plt.imshow(iimg, cmap='gray')
plt.title('iimg')
plt.axis('off')plt.show()
3. 低通滤波示例:
前面讲过,在一幅图像内,低频信号对应图像内变化缓慢的灰度分量。例如,在一幅大草原的图像中,低频信号对应着颜色趋于一致的广袤草原。低通滤波器让高频信号衰减而让低频信号通过,图像进行低通滤波后会变模糊。
例如,在下图中,左图original是原始图像,中间的图像result是对original进行傅里叶变换后得到的结果,右图是低通滤波后的图像。将傅里叶变换结果图像result中的高频信号值都替换为0(处理为黑色),就屏蔽了高频信号,只保留低频信号,从而实现了低通滤波。
在实现低通滤波时,可以专门构造一个如下图左图所示的掩码图像,用它与原图的傅里叶变换频谱图像进行与运算,就能将频谱图像中的高频信号过滤掉。
import cv2
import numpy as np
import matplotlib.pyplot as pltimg = cv2.imread('../boat.512.tiff', 0)
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)rows, cols = img.shape
crow, ccol = int(rows / 2), int(cols / 2)
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow - 30: crow + 30, ccol - 30: ccol + 30] = 1f_shift = dft_shift * mask
print(f_shift)# mask = np.zeros((rows, cols), np.uint8)
# mask[crow - 30: crow + 30, ccol - 30: ccol + 30] = 1
# f_shift = cv2.bitwise_and(dft_shift, dft_shift, mask=mask)
# print(f_shift)ishift = np.fft.ifftshift(f_shift)
iimg = cv2.idft(ishift)
iimg = cv2.magnitude(iimg[:, :, 0], iimg[:, :, 1])plt.subplot(121)
plt.imshow(img, cmap='gray')
plt.title('original')
plt.axis('off')plt.subplot(122)
plt.imshow(iimg, cmap='gray')
plt.title('iimg')
plt.axis('off')plt.show()