内容来自OpenCV-Python Tutorials 自己翻译整理
傅里叶变换原理的文章
目标
用opencv对图像进行傅里叶变换
numpy进行快速傅里叶变换
傅里叶变换的用处
学习函数函数cv2.dft(),cv2.idft()
原理
傅里叶变换常用来分析各种滤波器的特性。可以是用2D离散傅里叶变换分析图像的频域特性。
(个人理解,在图像问题当中,频域是指图像的灰度变化,也就是灰度图像的梯度值,这个和轮廓的原理差不多,灰度值变化剧烈的叫做高频分量,例如边界和噪声。灰度值变化缓慢的称谓低频分量)
实现2D离散傅里叶变换(DFT)的的算法叫做快速傅里叶变换(FFT)。
对图像进行X方向和Y方向的傅里叶变换,会得到图像的频域表示图。
直观理解,一个正弦信号,如果幅度变换很快,可以称之为高频信号,如果变换慢,可以称之为低频信号。在图像中,灰度值变化快的位置,可以称之为高频分量(只变化快而不是次数多),灰度值变化慢的称之为低频分量。
图像使用使用二维离散傅里叶变换后得到一个复数矩阵,叫做图像的频谱图。
矩阵中的每个值对应F(u,v),由于是复数,所以一般在数学计算上都用极坐标表示。
F(u)=|F(u)|e−jϕ(u)
其中
|F(u)|=[R2(u)+I2(u)]12 叫做变换的振幅或者频率谱,实数和复数分别表示振幅和相位
其中
ϕ(u)=arctan[I(u)R(u)] 叫做相角或者相位谱
傅里叶变换值的平方,叫做普功率
numpy中的傅里叶变换
np.fft.fft2()函数可对信号进行频率转换,输出一个复数数组(有人给翻译成复杂数组,明显错误!)。
第一个参数要求为灰度图像,第二个参数为输出数组大小(可选),输出数组大小和输入图像大小一样。若输出结果比输入大,输入图像要在变换前补0,输出结果比输入小,输入图像被切割。
代码
频率为0 的部分(直流分量)在输出图像的左上角。(2D傅里叶变换F(x,y)的F(0,0)位置在图像的左上角,F(0,0)表示的是图像灰度的均值)
如果想让直流分量在输出图像的重心,那么需要将结果沿着两个方向平移分别平移N/2和M/2(原文是两个方向平移N/2,我感觉写错了-_-),其中M和N代表图像的长宽。
使用np.fft.fftshift()函数来实现平移,参数输入使用2维傅里叶变换得到的复杂数组即可。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('3.jpg',0)
f = np.fft.fft2(img)
print(f)
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()
对频域图像进行操作,例如使用高通滤波和重建图像(DFT的逆变换)。
(高通滤波的意思就是增强边缘,非边缘部分被过滤,同理,低通滤波就是将图像边的平滑)
使用一个60×60的矩形窗口进行蒙板操作,去除低频分量,使用函数np.fft.ifftshift将图像中心平移回左上角,然后使用函数 np.ifft2()进行FFT逆变换,将得到的复数结果取绝对值。
import cv2
import numpy as np
from matplotlib import pyplot as plt
img = cv2.imread('3.jpg',0)
f = np.fft.fft2(img)#得到结果为复数矩阵
fshift = np.fft.fftshift(f)#直接取中心
rows, cols = img.shape
crow,ccol = rows/2 , cols/2
fshift[int(crow-30):int(crow+30), int(ccol-30):int(ccol+30)] = 0#蒙板大小60×60
f_ishift = np.fft.ifftshift(fshift)#回到左上角
img_back = np.fft.ifft2(f_ishift)#使用FFT逆变换,此时结果仍然是复数
img_back = np.abs(img_back)#还原成JET图像,变成可以看的图像
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()
上面例子可以看到高通滤波和边缘检测差不多,同时可以看到上面图片当中除了裂痕的部分,都被过滤过去了,说明非裂痕部分是低频区域,都被过滤了
我选的图片是人工画出来的图片,没有明暗效果,而且相同颜色的灰度值部分相同。如果是照的图片,使用傅里叶变换会有振铃效应,因为是用矩形窗口进行蒙板操作,蒙板被转换成正弦形状就会有上面的问题,最好是用高斯窗口。
OpenCV 中的傅里叶变换
函数为 cv2.dft() 和 cv2.idft(),分别为傅里叶变换和逆变换
输出结果和上面一样,但是,分别是双通道,第一个通道是实数部分,第二个通道是虚数
输入图像要转换成小数
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('33.jpg',0)#读图
dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT)#傅里叶变换
dft_shift = np.fft.fftshift(dft)#平移到中心
magnitude_spectrum = 20*np.log(cv2.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()
使用函数 cv2.cartToPolar()会同时得到幅度和相位,此函数也是直角坐标转换为极坐标的函数。
之前做了一个高通滤波器,现在实现一个低通滤波器,将高频部分去除。
首先实现一个蒙板,低频去对应的部分设置为1,高频设置为0。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('3.jpg',0)#读图
dft = cv2.dft(np.float32(img),flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow,ccol = rows/2 , cols/2
# create a mask first, center square is 1, remaining all zeros
mask = np.zeros((rows,cols,2),np.uint8)
mask[int(crow)-30:int(crow)+30, int(ccol)-30:int(ccol)+30] = 1
# apply mask and inverse DFT
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(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()
opencv中的函数比numpy的速度要快,但是numpy的操作更加友好
DFT性能优化
数组大小是2的指数DFT效率最高,数组大小是2,3,5的倍数时,性能也挺高。所以可以通过修改输入图像的大小(补0)来提高效率。opencv需要手动补0,numpy自动。
在opencv中有一个函数,cv2.getOptimalDFTSize(),可以被cv2.dft和np.fff.fff2使用。
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('33.jpg',0)#读图
rows,cols = img.shape
print("%s %s"%(rows,cols))
nrows = cv2.getOptimalDFTSize(rows)
ncols = cv2.getOptimalDFTSize(cols)
print("%s %s"%(nrows,ncols))
输出结果
67 72
72 72
把数据拷贝过去或者使用函数cv2.copyMakeBoder()
nimg = np.zeros((nrows,ncols))
nimg[:rows,:cols] = img
或者
right = ncols - cols
bottom = nrows - rows
#just to avoid line breakup in PDF file
bordertype = cv2.BORDER_CONSTANT
nimg = cv2.copyMakeBorder(img,0,bottom,0,right,bordertype, value = 0)