OpenCV(12): 傅里叶变换以及高通低通滤波器代码复现

引言:

上一篇博客简单介绍了一下傅里叶变换是什么,这篇博客主要讲傅里叶变换在图像处理中的具体应用,同时也会附上例子和代码实战。

知识引用:

我上一章讲的有些复杂。不了解傅里叶变换的同学,我在此可以简单介绍一下它的概念。

傅里叶变换认为,很多函数,都可以用正余弦函数组合而成。也就是说,所有的函数都能被分解为一系列正余弦函数。而傅里叶变换,主要就是求出这些函数,从而让时域转换为频域。

但是,并非所有函数都能被傅里叶变换表示为正弦和余弦函数的组合,只有满足一定条件的函数才能被傅里叶变换表示。这些条件包括:

  1. 函数绝对可积,即在整个实数轴上,函数的绝对值的积分存在;
  2. 函数平方可积,即在整个实数轴上,函数的平方的积分存在;
  3. 函数有限长且连续或者具有有限个极值点和有限个间断点且绝对可积。

对于时域,如果我们将一个函数的x轴视为时间,那么这个函数随着自变量的变化而变化,所形成的函数图像就是时域。而频域就是由这个函数分解成的一系列正余弦函数,以这些函数的频率为x轴,振幅为y轴组成的图像就是频域。

这边推荐一下这篇知乎文章,讲的非常好,反正比我好,有兴趣可以看一下。以下图片也转载自这篇文章。傅里叶分析之掐死教程(完整版)更新于2014.06.06 - 知乎 (zhihu.com)

 我们换个方向看过去,就能得到它的频域。

以下就是它的频域

 频域的x轴指的是频率,y轴代表振幅。那么根据分解出来的正余弦函数的频率有些时候,他们是有限个或者可列无限个,有些时候他们是无限且不可列个,就被分别称为离散傅里叶变换(DFT)和连续型傅里叶变换(CTFT)。上图就是离散型傅里叶变换的频域,连续型自行脑补,它的数据是连续的,不好展示,因为连续型分实部和虚部,实部就是上图转换成连续,虚部就是相位。

基于傅里叶变换的滤波器

对于一张图像,我们可以通过傅里叶变换的原理将其分解为无数个频率的图像。这些图像相互叠加便形成了我们所看到的图像。

那么根据傅里叶变换的原理,我们提出了两种滤波器,高通滤波器以及低通滤波器。

在图像处理中,我们可以将一幅图像看作是一个二维函数,即 f(x,y),其中 x 和 y 是图像中每个像素的水平和垂直位置(可以理解为之前我们说的时域中的x轴,此时的x轴不是时间而是空间坐标),而f(x,y) 表示该位置的亮度值。

而在频域上,我们可以将这个二维函数进行傅里叶变换,得到其频谱 F(u,v)。在频谱中,低频表示的是图像中亮度变化比较缓慢的部分高频表示的是图像中亮度变化比较剧烈的部分。因此,图像中的低频部分通常包含了图像的整体结构和纹理信息,而高频部分则包含了图像的细节信息和边缘信息。

低通滤波器

低通滤波器的工作原理是保留图像低频的部分,去除高频的部分,会使图像变得模糊。

代码复现:

测试图像为:

 

第一步:读取灰度图,这里imread方法中传入参数0就是读取灰度图

import numpy as np
import cv2 
import matplotlib.pyplot as plt

#展示图像的方法
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('bird.jpg',0)#读取灰度图
cv_show('img',img)#展示一下图像,按任意键继续

第二步:

将读取的图像数据格式转换为float32.我们都知道opencv读取图像的数据格式是uint8,在使用OpenCV中的cv2.dft()等函数进行傅里叶变换时,需要将输入图像的数据类型转换为浮点型(float)或双精度浮点型(double),以便进行复数类型的运算,获得更准确的频率域表述。

#处理前先把图像的dtype转换成float32,原本是uint8,傅里叶变换得用float32类型才能计算
img_float = np.float32(img)

第三步:

#傅里叶变换
dft = cv2.dft(img_float,flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

这段代码的作用是对输入图像img_float进行二维傅里叶变换,并将结果进行中心化处理。

首先,cv2.dft()函数将输入图像img_float从空间域转换到频率域,返回一个复数类型的数组,其中每个元素都包含一个实部和虚部。flags参数指定了变换的类型,cv2.DFT_COMPLEX_OUTPUT表示输出结果是一个复数数组,每个元素都包含一个实部和一个虚部。

接下来,使用numpy中的np.fft.fftshift()函数对傅里叶变换的结果进行中心化操作。np.fft.fftshift()函数可以将数组中的低频率分量移到数组中心,实现这一操作。

最终得到的dft_shift表示对输入图像img_float进行傅里叶变换并进行中心化处理后得到的结果,即移动后的频率域表述。

有些人在此有些疑惑,为什么要进行中心化。在此说明一下

经过傅里叶变换后的频率域数据分布特征主要包括以下几点:

  1. 直流分量:即频率为0的分量,表示图像中各个像素值的平均值。该分量通常位于图像的左上角,这一部分又称为零频分量,也可以被划为低频的部分。

  2. 高频分量:表示图像中像素值快速变化的部分,包括边缘、纹理等细节信息。该分量通常位于图像的右下角。

  3. 低频分量:表示图像中像素值变化缓慢的部分,包括背景、颜色块等整体信息。该分量通常位于图像的左下角和右上角。

在傅里叶变换后的频域数据中,低频信息通常位于频域的左下角,而高频信息通常位于频域的右上角。为了便于观察和处理这些频率成分,需要将频率域数据进行中心化,即将零频和低频信息移到频域中心位置。

np.fft.fftshift()函数实现了这一功能,将输入数组中的零频和低频分量移动到数组中心位置,之后我们通过数据切片或掩膜的方式就能将这些低频数据提取出来,然后再将他们还原成图像就行。在此不好展示频域数据。这段代码主要就是把频域数据中的低频部分移到中间,大家理解一下就行。

第四步:

创建掩膜

rows,cols = img.shape#分别得到图像的长和宽
crows, ccol =int(rows/2),int(cols/2)#分别得到图像中心点的位置

#低通滤波,创建低通滤波器掩码,中心区域是1,周围区域是0
mask = np.zeros((rows, cols, 2), dtype = np.uint8)#2是因为傅里叶变换结果是双通道的,分别为实部和虚部
mask[crows-30:crows+30,ccol-30:ccol+30] = 1

创建一个掩膜,首先得到图像的中心点的坐标,然后根据图像的长和宽生成一个双通道的零阵。为什么是二元,主要是因为傅里叶变换后生成的频域数据是双通道的,所以我们必须用双通道的矩阵才能进行掩膜。

把掩膜中心部分+-30像素点的值设置成1,这样掩膜就完成了。

 第五步:

利用掩膜去提取低频数据

#mask是中间是1,周围是0的掩膜,我们之前通过 np.fit.fftshift将低频部分移到图像中间了,乘以mask这样就能将低频部分保留(*1),高频部分抑制(*0)
fshift = dft_shift*mask

简单来说就是将之前通过中心化后的频域数据与掩膜进行相乘,这样的话中间+-30像素点的区域(之前中心化后把低频数据移到中间了)都被保留了下来(乘以1),而周围的区域(就是高频数据)都去除了(乘以0)

第六步:

通过傅里叶逆变换,将提取出来的低频频域数据重新变成图像数据。

f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)#这两步就是之前的傅里叶变换部分的逆变换
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

这段代码是对经过频域处理的图像进行逆变换,从而得到原图像。

首先,代码中的np.fft.ifftshift()函数是将傅里叶变换后的频域数据进行反移位操作,使得低频分量回到左上角,高频分量回到右下角(高频分量已经被掩膜处理设为0了),以便进行逆变换。这一步操作可以理解为与np.fft.fftshift()函数相反的操作。

然后,使用cv2.idft()函数对反移位后的频域数据进行逆变换,得到原图像的复数形式。注意,在进行逆变换时需要保留原始的实部和虚部信息,因此返回的结果是一个两通道的复数数组,其中第一个通道表示实部,第二个通道表示虚部。

最后,使用cv2.magnitude()函数计算复数数组的幅度,得到原始图像。有实部和虚部的图像没法展示,要通过这一操作才能将虚实图像转化成人能够看的图像。

cv2.magnitude()函数的作用就是将复数数组转换成实数数组,即根据下面的公式计算每个像素点的模长:

magnitude = sqrt(real^2 + imaginary^2)

对实部和虚部求平方和后再开方,就是这个函数的作用。

第七步:

展示图像

plt.subplot(1,2,1),plt.imshow(img,cmap = 'gray'),plt.title('origin')
plt.subplot(1,2,2),plt.imshow(img_back,cmap = 'gray'),plt.title('low-pass filter')

完整代码如下:

import numpy as np
import cv2 
import matplotlib.pyplot as plt

#展示图像的方法
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('bird.jpg',0)#读取灰度图
cv_show('img',img)#展示一下图像,按任意键继续

img_float = np.float32(img)#处理前先把图像的dtype转换成float32,原本是uint8,傅里叶变换得用float32类型才能计算

#傅里叶变换
dft = cv2.dft(img_float,flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows,cols = img.shape#分别得到图像的长和宽
crows, ccol =int(rows/2),int(cols/2)#分别得到图像中心点的位置

#低通滤波,创建低通滤波器掩码,中心区域是1,周围区域是0
mask = np.zeros((rows, cols, 2), dtype = np.uint8)#2是因为傅里叶变换结果是双通道的,分别为实部和虚部
mask[crows-30:crows+30,ccol-30:ccol+30] = 1

#IDFT,进行一个逆变换
fshift = dft_shift*mask#mask是中间是1,周围是0的掩膜,我们之前通过 np.fit.fftshift将低频部分移到图像中间了,乘以mask这样就能将低频部分保留(*1),高频部分抑制(*0)
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)#这两步就是之前的傅里叶变换部分的逆变换
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

plt.subplot(1,2,1),plt.imshow(img,cmap = 'gray'),plt.title('origin')
plt.subplot(1,2,2),plt.imshow(img_back,cmap = 'gray'),plt.title('low-pass filter')

结果:

可以发现低通滤波器将图像变得模糊了,咱们就是把图像中低频率的部分提取了出来。其实很多美图软件磨皮就是用低通滤波器

高通滤波器

做法和低通滤波器几乎一摸一样,就是掩膜改一改。咱们之前的掩膜是中间为1,而周围为0的掩膜。这是因为中心化后低频数据都集中在中间的原因。因此,如果需要提取高频数据,我们只要将掩膜反过来,周围设为1,而中心设置为0即可。

代码展示:

import numpy as np
import cv2 
import matplotlib.pyplot as plt

#展示图像的方法
def cv_show(title,img):
    cv2.imshow(title,img)
    cv2.waitKey(0) 
    cv2.destroyAllWindows()
    return

img = cv2.imread('bird.jpg',0)#读取灰度图
cv_show('img',img)#展示一下图像,按任意键继续

img_float = np.float32(img)#处理前先把图像的dtype转换成float32,原本是uint8,傅里叶变换得用float32类型才能计算

#傅里叶变换
dft = cv2.dft(img_float,flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)

rows,cols = img.shape#分别得到图像的长和宽
crows, ccol =int(rows/2),int(cols/2)#分别得到图像中心点的位置

#创建高通滤波器掩膜
mask = np.ones((rows, cols, 2), dtype = np.uint8)
mask[crows-30:crows+30,ccol-30:ccol+30] = 0

#IDFT,进行一个逆变换
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)#这两步就是之前的傅里叶变换部分的逆变换
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])

plt.subplot(1,2,1),plt.imshow(img,cmap = 'gray'),plt.title('origin')
plt.subplot(1,2,2),plt.imshow(img_back,cmap = 'gray'),plt.title('high-pass filter')

tips:大部分和低通滤波器一样,直接ctrl cv,注意我们创建掩膜的方法

mask = np.ones((rows, cols, 2), dtype = np.uint8)
mask[crows-30:crows+30,ccol-30:ccol+30] = 0

这边先创建了一个全是1的双通道矩阵,然后再把中间设置为0就行

看一下结果:

总结与后话:

傅里叶变换是一种将信号从时域转换到频域的方法,对于图像处理而言,傅里叶变换可以将图像从空间域转换到频域,这样有助于发现和分析图像中的周期性结构。除了滤波器之外,傅里叶变换在图像处理中的作用主要有以下几个方面:

  1. 压缩。在傅里叶变换后,图像会被分解为若干个频率分量,而这些频率分量并不一定都是重要的。因此,在压缩图像时,可以只保留一部分最重要的频率分量,忽略其余的频率成分,从而实现图像压缩的目的。

  2. 特征提取。傅里叶变换可以用于检测和识别出图像中的周期性结构。例如,可以获取图像中存在的特定频率的信息,用于物体的检测和识别。

  3. 图像增强。傅里叶变换可以用于增强图像的特定频率成分,从而提高图像的质量和清晰度。例如,在傅里叶变换后,可以加强低频成分,使图像整体看起来更加平滑,或者增强高频成分,突出图像的细节。

总的来说,傅里叶变换是图像处理中非常重要的工具,可以用于改善图像的质量、实现特定的处理目标等。

 

猜你喜欢

转载自blog.csdn.net/m0_50317149/article/details/130252741