写在前面的话,这个博文是我写的时间最长的,字数最多的了,从昨天开始就在看傅里叶变换了,看了很多大神的文章,也学到了很多知识,一度觉得有那么多前辈的文章,我还写它干嘛,反正也是抄一遍。不过想起以前的经历,看了那么多书,一两个月一过就全部还给作者了,所以还是要静心自己写一遍。纸上得来终觉浅,方知此事要躬行。
先推荐一下大佬的文章: 傅里叶变换详解,这篇文章原理讲的很详细,很有趣。 傅里叶变换代码,这篇文章对代码层面讲的很详细。所以如果大家觉得我的文章不好看,可以移步两位前辈的博客,肯定会满意的。

一、傅里叶变换

傅里叶变换可以将一副图片分解为正弦和余弦两个分量,换言之,它可以将一幅图像从空间域(spatial domain)转换为频域(frequency domain)。这种变换的思想是任何函数可以很精确的接近无穷个sin()函数和cos()函数的和。
我们来梳理一下概念:

空间域

一般情况下,空间域的图像是f(x, y) = 灰度级(0-255),形象一点就是一个二维矩阵,每一个坐标对应一个颜色值。

频率域

频率:对于图像来说就是指图像颜色值的梯度,即灰度级的变化速度
幅度:可以简单的理解为是频率的权,即该频率所占的比例
对于一个正弦信号,如果它的幅度变化很快,我们称之为高频信号,如果变化非常慢,我们称之为低频信号。迁移到图像中,图像哪里的幅度变化非常大呢?边界点或者噪声。所以我们说边界点和噪声是图像中的高频分量(这里的高频是指变化非常快,不是出现的次数多),图像的主要部分集中在低频分量。
由于图像变换的结果原点在边缘部分,不容易显示,所以将原点移动到中心部分。
那么结果便是中间一个亮点朝着周围发散开来,越远离中心位置的能量越低(越暗)。
对于二位图像其傅里叶变换公式如下:
式中 f(i, j)是图像空间域的值,F(k ,l)是频域的值。傅里叶转换的结果是复数,这也显示了傅里叶变换是一幅实数图像和虚数图像叠加或者是幅度图像和相位图像叠加的结果。

二、Numpy中的傅里叶变换

函数np.fft.fft2()可以对信号进行频率转换,输出结果是一个复杂的数组。函数的第一个参数是输入图像,要求是灰度格式。第二个参数是可选的,决定输出数组的大小。 输出数组的大小和输入大小一样。如果输出结果比输入图像大,输入图像就需要在进行FFT之前补0.如果输出结果比输入图像小,输入图像就会被切割。
#快速傅里叶变换算法得到频率分布
f = np.fft.fft2(img)
#默认结果中心点位置在左上角,转移到中间
fshift = np.fft.fftshift(f)
#fshift是复数,求绝对值结果才是振幅
magnitude_spectrum = 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()
注意的是,上图其实并没有什么含义,显示出来的可以看成是频域后图像的振幅信息,并没有相位信息,图像的相位
ϕ = a t a n ( 实部虚部 ),numpy包自带一个angle函数可以直接根据复数的实部和虚部求出角度(默认出来的角度是弧度。)像上述的程序中的f和fshift都是复数,就可以直接用angle直接求解相位,我们和振幅图对比一下:
可以直接
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
#取绝对值:将复数变成实数
#取对数的目的为了将数据变化到0-255
s1 = np.log(np.abs(fshift))#求相位
s1_angle = np.angle(fshift)plt.subplot(221),plt.imshow(img, cmap='gray'),plt.title('original')
plt.subplot(222),plt.imshow(s1, 'gray'),plt.title('center')
plt.subplot(223),plt.imshow(s1_angle, 'gray'),plt.title('angel')
#逆变换
f1shift = np.fft.ifftshift(fshift)
img_back = np.fft.ifft2(f1shift)
#出来的复数,无法显示,转成实数
img_back = np.abs(img_back)
plt.subplot(224),plt.imshow(img_back, 'gray'),plt.title('img back')
plt.show()
图三就是图像上每个像素点对应的相位图,其实是毫无规律的,理解就是偏移的角度。
图像变换到频域后,我们怎么转回时域呢?这就是傅里叶逆变换,先反中心化,再逆变换,上面我们一并提到了,并做了一个对比,和原图像一样。
我们说恢复一个频域图像需要图像的振幅和相位,而一个复数也正好包含这些,振幅就是复数的实数部分的绝对值,相位就是
a t a n ( 实部虚部 ),现在假设我们只用一幅图像的振幅或者相位来将频域内图像恢复到时域会怎么样?
下面给出只用振幅、只用相位、和两者结合的程序:
f = np.fft.fft2(img)
fshift = np.fft.fftshift(f)
# 取绝对值: 将复数变成实数
# 取对数是为了将数据变换到0-255
s1 = np.log(np.abs(fshift))
plt.subplot(221),plt.imshow(img, 'gray'),plt.title('original')
plt.xticks([]), plt.yticks([])
#-------------------------------------
#逆变换 -- 取绝对值就是振幅
f1shift = np.fft.ifftshift(np.abs(fshift))
img_back = np.fft.ifft2(f1shift)img_back = np.abs(img_back)
plt.subplot(222),plt.imshow(img_back, 'gray'),plt.title('only Amplitude')
plt.xticks([]),plt.yticks([])
#------------------------------------
#逆变换 -- 取相位
f2shift = np.fft.ifftshift(np.angle(fshift))
img_back = np.fft.ifft2(f2shift)
#结果是复数,无法显示
img_back = np.abs(img_back)
plt.subplot(223), plt.imshow(img_back, 'gray'), plt.title('only phase')
plt.xticks([]), plt.yticks([])
#------------------------------------
#逆变换--将两者结合看看
s1 = np.abs(fshift) # 取振幅
s1_angle = np.angle(fshift) # 取相位
s1_real = s1*np.cos(s1_angle) # 取实部
s1_imag = s1*np.sin(s1_angle) # 去虚部
s2 = np.zeros(img.shape, dtype=complex)
s2.real = np.array(s1_real) # 重新赋值给s2
s2.imag = np.array(s1_imag)#对新的进行逆变换
f3shift = np.fft.ifftshift(s2)
img_back = np.fft.ifft2(f3shift)img_back = np.abs(img_back)
plt.subplot(224), plt.imshow(img_back, 'gray'), plt.title('another way')
plt.xticks([]), plt.yticks([])
plt.show()
可以看到,仅仅振幅的恢复图什么都看不来,仅仅的相位图虽然有东西,但是也看不出来什么,最后是把振幅和相位分别作为频域内复数的实部和虚部,得到的恢复图才和原来一样。
基于此,我们来做一个实验,假设有两幅图像,将这两幅图像进行傅里叶变换到频域,然后用一幅图像的振幅和另一幅图像的相位结合生成一幅新的图像,然后我们再转回时域,那么生成的图像更像取振幅的图像,还是取相位的图像呢?我们两张大小一样的图片:
img_1 = cv.imread('img/WindowsLogo.jpg', 0)
img_2 = cv.imread('img/LinuxLogo.jpg', 0)
plt.subplot(221), plt.imshow(img_1, 'gray'), plt.title('original_1')
plt.xticks([]), plt.yticks([])
plt.subplot(222), plt.imshow(img_2, 'gray'), plt.title('original_2')
plt.xticks([]), plt.yticks([])
#----------------------------
f1 = np.fft.fft2(img_1)
f1shift = np.fft.fftshift(f1)
f1_A = np.abs(f1shift) # 取振幅
f1_P = np.angle(f1shift) # 取相位
#-----------------------------
f2 = np.fft.fft2(img_2)
f2shift = np.fft.fftshift(f2)
f2_A = np.abs(f2shift) # 取振幅
f2_P = np.angle(f2shift) # 取相位
#--------图1的振幅--图2的相位-----
img_new_1 = np.zeros(img_1.shape, dtype=complex)
img_new_1_real = f1_A * np.cos(f2_P) # 取实部
img_new_1_imag = f1_A * np.sin(f2_P) # 取虚部
img_new_1.real = img_new_1_real
img_new_1.imag = img_new_1_imag # 对新图像赋值
f3shift = np.fft.ifftshift(img_new_1) # 对新的图像进行逆变换
img_new_1_back = np.fft.ifft2(f3shift)
#结果是复数,无法显示
img_new_1_back = np.abs(img_new_1_back)plt.subplot(223), plt.imshow(img_new_1_back, 'gray'), plt.title('img1_A img2_P')
plt.xticks([]), plt.yticks([])
#-------图1的相位--图2的振幅---------
img_new_2 = np.zeros(img_2.shape, dtype=complex)
img_new_2_real = f2_A * np.cos(f1_P) # 取实部
img_new_2_imag = f2_A * np.sin(f1_P) # 取虚部
img_new_2.real = img_new_2_real
img_new_2.imag = img_new_2_imag #对新图像赋值
f4shift = np.fft.ifftshift(img_new_2) # 对新的图像进行逆变换
img_new_2_back = np.fft.ifft2(f4shift)
#结果是复数,无法显示
img_new_2_back = np.abs(img_new_2_back)plt.subplot(224), plt.imshow(img_new_2_back, 'gray'), plt.title('img1_P img2_A')
plt.xticks([]), plt.yticks([])
plt.show()
图三是图一的振幅加图二的相位,图四是图一的相位加图二的振幅。很明显的可以看出,新图像由相位决定。为什么会这样呢?可以理解为振幅不过是描述图像灰度的亮度,占用谁的振幅不过使得结果哪部分偏亮或者哪部分偏暗而已。而图像最终是什么样子,由相位来决定。相位描述的是一个方向,只要方向正确了,那么最终的结果离你的目的也不会远。
现在我们可以进行频域变换了,例如高通滤波和重建图像(DFT的逆变换)。比如我们使用一个60 x 60的矩形窗口对图像进行掩模操作去除低频分量。然后再使用函数np.fft.ifftshift()进行逆平移操作,现在直流分量又回到了左上角,然后使用函数np.ifft2()进行FFT逆变换。同样又得到一堆复杂的数字,我们取绝对值:
rows, cols = img.shape
#宽高的1/2 取中心坐标
crow, ccol = int(rows / 2), int(cols / 2)
#60 * 60 的矩形窗口,去除低频分量
fshift[crow-30:crow+30,ccol-30:ccol+30] = 0
#进行逆平移操作,直流分量又回到左上角
f_shift = np.fft.ifftshift(fshift)
#进行FFT逆变换
img_back = np.fft.ifft2(f_shift)
#取绝对值
img_back = np.abs(img_back)plt.subplot(131),plt.imshow(img, cmap='gray')
plt.title('Input Image'),plt.xticks([]),plt.yticks([])
plt.subplot(132), plt.imshow(img_back, cmap='gray')
plt.title('Image after HPF'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img_back)
plt.title('Result in JET'), plt.xticks([]), plt.yticks([])
plt.show()
效果如下:
上图的结果显示高通滤波其实是一种边界检测操作。我们把中心区域的低频分量全部去除了,只留下了一些高频分量,高频分量是什么?边界和噪声啊,所以剩下的就是边界了。
下面再来试试低通滤波器什么效果,构造个低通滤波器也很简单,把上述模板中的1变成0,0变成1,其实就是低通了:
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('original')
plt.xticks([]), plt.yticks([])rows, cols = img.shape
crow, ccol = int(rows/2), int(cols/2)
# -------高通滤波---------
mask = np.ones(img.shape, np.uint8)
mask[crow - 30:crow + 30, ccol - 30:ccol + 30] = 0

f1 = np.fft.fft2(img)
f1shift = np.fft.fftshift(f1)
f1shift = f1shift * mask
f2shift = np.fft.ifftshift(f1shift) # 对新得到图像进行逆变换
img_new = np.fft.ifft2(f2shift)img_new = np.abs(img_new)plt.subplot(132), plt.imshow(img_new, 'gray'), plt.title('Highpass')
plt.xticks([]), plt.yticks([])# --------低通滤波---------
mask2 = np.zeros(img.shape, np.uint8)
mask2[crow - 30:crow + 30, ccol - 30:ccol + 30] = 1

f1 = np.fft.fft2(img)
f1shift = np.fft.fftshift(f1)
f1shift = f1shift * mask2
f2shift = np.fft.ifftshift(f1shift)  # 对新得到图像进行逆变换
img_new = np.fft.ifft2(f2shift)img_new = np.abs(img_new)plt.subplot(133), plt.imshow(img_new, 'gray'), plt.title('Lowpass')
plt.xticks([]), plt.yticks([])
plt.show()
可以看出低通滤波后图像的轮廓模糊了,从原理上看,图像的主要信息都集中在低频上,所以低通滤波器相当于执行了迷糊操作。
除了高通、低通滤波器,还有其他滤波器,如高斯滤波器,butterworth滤波器等。还有就是把高通低通都结合一部分到一个模板上形成的带通滤波器,这在一些场合会很有用,还是以理想的带通滤波器试验下:
plt.subplot(121), plt.imshow(img, 'gray'), plt.title('original')
plt.xticks([]), plt.yticks([])
#-----------------------------
rows, cols = img.shape
crow, ccol = int(rows/2), int(cols/2)
#高通滤波器
mask1 = np.ones(img.shape, np.uint8)
mask1[crow-8:crow+8, ccol-8:ccol+8] = 0
#低通滤波器
mask2 = np.zeros(img.shape, np.uint8)
mask2[crow-80:crow+80, ccol-80:ccol+80] = 1
#带通滤波器
mask = mask1 * mask2
#----------------------------------
f1 = np.fft.fft2(img)
f1shift = np.fft.fftshift(f1)
f1shift = f1shift * mask
#对新得到的图像进行逆变换
f2shift = np.fft.ifftshift(f1shift)
img_new = np.fft.ifft2(f2shift)img_new = np.abs(img_new)
plt.subplot(122), plt.imshow(img_new, 'gray'), plt.title('new image')
plt.xticks([]), plt.yticks([])
plt.show()
这就是带通的效果,即可以保留一部分低频,也可以保留一部分高频,至于保留多少,就要视情况而定。
在文章的最后,我们对一些算子做一下傅里叶变换,看看他们属于HPF还是LPH
#对一些算子进行比较
def demo5():mean_filter = np.ones((3, 3))x = cv.getGaussianKernel(5, 10)guassian = x * x.Tscharr = np.array([[-3, 0, -3],[-10, 0, -10],[-3, 0, 3]])sobel_x = np.array([[-1, 0, 1],[-2, 0, -2],[-1, 0, 1]])sobel_y = np.array([[-1, -2, -1],[0, 0, 0],[1, 2, 1]])laplacian = np.array([[0, 1, 0],[1, -4, 1],[0, 1, 0]])#我看有两个版本的laplacian算子,这个博客给了解释https://www.cnblogs.com/german-iris/p/4840647.html

    filters = [mean_filter, guassian, laplacian, sobel_x, sobel_y, scharr]filter_name = ['mean_filter', 'guassian', 'laplacian', 'sobel_x', 'sobel_y', 'scharr']fft_filters = [np.fft.fft2(x) for x in filters]fft_shift = [np.fft.fftshift(x) for x in fft_filters]mag_spectrum = [np.log(np.abs(x)+1) for z in fft_shift]for i in range(6):plt.subplot(2, 3, i+1),plt.imshow(mag_spectrum[i], 'gray')plt.title(filter_name[i]),plt.xticks([]),plt.yticks([])plt.show()

从结果我们可以看出:mean_filter、guassian算子是LPH,laplacian、sobel、scharr算子是HPF

OpenCV学习笔记-傅里叶变换相关推荐

  1. opencv学习笔记22:傅里叶变换,高通滤波,低通滤波

    傅里叶变换原理 任何连续的周期信号,都可以由一组适当的正弦曲线组合而成. 下列左上图由其他三图构成. 左图经过傅里叶变换,由时域图转换到频域图.相互可逆 相位:不是同时开始的一组余弦函数,在叠加时要体 ...

  2. 36篇博文带你学完opencv :python3+opencv学习笔记汇总目录(基础版)

    经过几天的学习,opencv基础部分学习完啦.整理出来. OpenCV opencv学习笔记1:图片读入,显示与保存(有代码) opencv学习笔记2:图像处理基础 opencv学习笔记3:像素处理 ...

  3. OpenCV学习笔记5

    OpenCV学习笔记5 图像变换 傅里叶变换 这里可以先学习一下卷积分,了解清除卷积的过程和实际意义,在看这一章节的内容. 原理: 傅里叶变换经常被用来分析不同滤波器的频率特性.我们可以使用 2D 离 ...

  4. OpenCV 学习笔记03 boundingRect、minAreaRect、minEnclosingCircle、boxPoints、int0、circle、rectangle函数的用法...

    函数中的代码是部分代码,详细代码在最后 1 cv2.boundingRect 作用:矩形边框(boundingRect),用于计算图像一系列点的外部矩形边界. cv2.boundingRect(arr ...

  5. opencv学习笔记(二):基于肤色的人手检测

    opencv学习笔记(二):基于肤色的人手检测 原文:http://blog.csdn.net/wzmsltw/article/details/50849810 先写了人手的检测程序,下一步基于检测程 ...

  6. python做直方图-python OpenCV学习笔记实现二维直方图

    本文介绍了python OpenCV学习笔记实现二维直方图,分享给大家,具体如下: 官方文档 – https://docs.opencv.org/3.4.0/dd/d0d/tutorial_py_2d ...

  7. OpenCV学习笔记大集锦

    转载自: OpenCV学习笔记大集锦 – 视觉机器人 http://www.cvrobot.net/collect-opencv-resource-learn-study-note-chinese/ ...

  8. OpenCV学习笔记(五十六)——InputArray和OutputArray的那些事core OpenCV学习笔记(五十七)——在同一窗口显示多幅图片 OpenCV学习笔记(五十八)——读《Mast

    OpenCV学习笔记(五十六)--InputArray和OutputArray的那些事core 看过OpenCV源代码的朋友,肯定都知道很多函数的接口都是InputArray或者OutputArray ...

  9. OpenCV学习笔记(五十一)——imge stitching图像拼接stitching OpenCV学习笔记(五十二)——号外:OpenCV 2.4.1 又出来了。。。。。 OpenCV学习笔记(五

    OpenCV学习笔记(五十一)--imge stitching图像拼接stitching stitching是OpenCV2.4.0一个新模块,功能是实现图像拼接,所有的相关函数都被封装在Stitch ...

最新文章

  1. 查看Windows系统里的进程已运行的时间
  2. Sharepoint学习笔记—ECMAScript对象模型系列-- 7、获取和修改List的Lookup字段
  3. css列表格式属性,css list-style-type属性笔记
  4. pwm 正弦波_CC6420单相正弦波直流无刷马达驱动应用指南
  5. 大数据组件的各种协议与作用(持续更新中)
  6. 扑克牌图片一张一张_培养孩子的数学力,不妨试试这五个扑克牌游戏
  7. 四种依恋类型_“我值得被爱吗?”| 如何在亲密关系中培养安全型依恋
  8. 认识队列技术中的硬件队列和软件队列及如何改变硬件队列长度
  9. 【VMware vSAN 6.6】5.4.vSAN 配置提示:vSAN硬件服务器解决方案
  10. 基于SSM的NBA篮球球队运营系统
  11. 使用VMWARE安装Mac OSX 雪豹操作系统并配置iphone开发环境
  12. 云原生小课堂 | Envoy请求流程源码解析(一):流量劫持
  13. Unity中下载图片、音频和视频
  14. django基础入门之搭建博客系统
  15. 查看linux服务器存储空间状况
  16. 数字通信学习笔记——基带信号解调
  17. html页面打包为小程序
  18. 5个网站让你成为主宰网络世界的神秘黑客
  19. 线下空间体验如何承载商业策略——从宜家的冰淇淋说起
  20. 将json对象转换为数组,获取json对象的属性值

热门文章

  1. 如何承接软件外包项目
  2. 3D美术人员Technical Artist(TA技术美术)的学习之旅(3)
  3. html语言需要dw吗,DW(HTML-基础知识点1)
  4. 【应用案例】AGV小车的运动控制方案
  5. python随机库函数_python标准库中的随机分布函数
  6. 数据报和字节流的区别
  7. android+开机+无命令,红米手机怎么刷机
  8. 精确率/召回率/准确率
  9. Python 时间序列异常点检测 | 详解 S-ESD 和 S-H-ESD
  10. Mangos模拟器综合资源贴