14、频率域滤波基础——傅里叶变换计算及应用基础

1、理解傅里叶变换

  如果是理工科的学生 ,在高等数学和信号处理的课程中应该就已经学习过Fourier变换 ,但是这里还是进行一个简单的基本学习和理解,为时域转频域提供一个基础理论概念。

1、什么是傅里叶级数

  周期函数的fourier级数是由正弦函数和余弦函数组成的三角级数。这里首先说结论周期为T的任意周期性函数f(t),若满足以下迪利克雷条件:

  • 在一个周期内只有有限个不连续点;
  • 爱一个周期内只有有限个极大和极小值
  • 积分$\int_{-\frac{-T}{2}}^{\frac{T}{2}}|f(t)| d t$存在,则f(t)可展开为如下傅里叶级数

$$
f(x)=\frac{a_{0}}{2}+\sum_{k=1}^{\infty}\left(a_{k} \cos k x+b_{k} \sin k x\right)
$$

其中

$$
\begin{aligned} a_{n} &=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \cos n x \mathrm{d} x \quad(n=0,1,2,3, \cdots) \\ b_{n} &=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin n x \mathrm{d} x \quad(n=1,2,3, \cdots) \end{aligned}
$$

式中的$w=\frac{2 \pi}{T}$称为角频率。

为了便于理解,这里先给出几个傅里叶变换拟合周期函数的示例图(关于这个图如何绘制,不是本文重点,可以查看参考资料):

2、傅里叶级数的提出及理论依据

  在早先,拉格朗日等数学家发现某些周期函数可以由三角函数的和来表示,比如下图中,黑色的斜线就是周期为的函数,而红色的曲线是三角函数之和,可以看出两者确实近似:

基于此,傅里叶提出了任意周期函数都可以写成三角函数之和的猜测,其公式提出思想基本如下:

  • 首先,根据周期函数的定义,常数函数是周期函数,周期为任意实数。所以,分解里面得有一个常数项。
  • 任意函数可以分解和奇偶函数之和,$f(x)=\frac{f(x)+f(-x)}{2}+\frac{f(x)-f(-x)}{2}=f_{\text { even }}+f_{\text { odd }}$,由正余弦函数分别为奇偶函数,且其为周期性函数,而且还具有正交的特性,所以分解里面需要由正余弦函数sin和cos
  • 如下图所示,对于$\sin (n x), n \in \mathbb{N}$,虽然最小周期是$\frac{\pi}{n}$,但是其周期中都有一个周期为2$\pi$,则对于周期为T的函数,可以知道角频率$w=\frac{2 \pi}{T}$,将这些函数进行加减(这里用级数表示),就保证了得到的函数的周期也为 T。
  • 又因为正余弦函数的值域只有[-1,1],而需要表示的函数的至于变化显然不止[-1,1],为了适应函数的值域,正余弦级数必然会有各自不同的系数an和bn

至此,我们就得到了傅里叶级数的猜想式,即

$$
f(x)=\frac{a_{0}}{2}+\sum_{n=1}^{\infty}\left(a_{n} \cos \left(\frac{2 \pi n}{T} x\right)+b_{n} \sin \left(\frac{2 \pi n}{T} x\right)\right), \frac{a_{0}}{2} \in \mathbb{R}
$$

3、傅里叶级数的系数计算

   在前面我们已经提出了傅里叶级数的猜想式

$$
f(x)=\frac{a_{0}}{2}+\sum_{n=1}^{\infty}\left(a_{n} \cos \left(\frac{2 \pi n}{T} x\right)+b_{n} \sin \left(\frac{2 \pi n}{T} x\right)\right), \frac{a_{0}}{2} \in \mathbb{R}
$$

但是我们还不知道系数C,an,bn和f(x)之间的关系。

  为了求解系数域函数之间的关系,这里我们进一步对假设级数进行逐步积分

  • 首先,对猜想式两端在$[-\pi, \pi]$上进行积分,并在等式的右端逐项积分,利用三角函数的正交性有

$$
\int_{-\pi}^{\pi} f(x) d x=\int_{-\pi}^{\pi} \frac{a_{0}}{2} d x+\sum_{n=1}^{\infty}\left(a_{n} \int_{-\pi}^{\pi} \cos n x d x+b_{n} \int_{-\pi}^{\pi} \sin n x d x\right)=a_{0} \pi
$$

于是有:

$$
a_{0}=\frac{1}{ \pi} \int_{-\pi}^{\pi} f(x) d x
$$

  • 将猜想式两端同乘cos nx,再用同样的方法求积分,利用三角函数系的正交性有:

$$
\int_{-\pi}^{\pi} f(x) \cos m x d x=\int_{-\pi}^{\pi} \frac{a_{0}}{2} \cos m x d x+\sum_{n=1}^{\infty}\left(a_{n} \int_{-\pi}^{\pi} \cos n x \cos n x d x+b_{n} \int_{-\pi}^{\pi} \cos m x \sin n x d x\right)=\int_{-\pi}^{\pi} a_{n} \cos ^{2} n x=a_{n} \pi
$$

所以我们可以得出

$$
a_{n}=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \cos n x d x
$$

  • 同理将猜想式两端同乘sin nx,再用同样的方法求积分,可以得出

$$
b_{n}=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin n x d x
$$

3、傅里叶级数的指数形式

结合欧拉公式

$$
e^{i t}=\cos (t)+i \sin (t)
$$

我们有

$$
\begin{aligned} \sin \theta &=\frac{e^{i \theta}-e^{-i \theta}}{2 i} \\ \cos \theta &=\frac{e^{i \theta}+e^{-i \theta}}{2} \end{aligned}
$$

根据上式,我们可以写出傅立叶级数的另外一种形式:

$$
f(x)=\sum_{n=-\infty}^{\infty} c_{n} \cdot e^{i \frac{2 \pi n x}{T}}
$$

其中

$$
c_{n}=\frac{1}{T} \int_{x_{0}}^{x_{0}+T} f(x) \cdot e^{-i \frac{2 \pi n x}{T}} d x
$$

4、由傅里叶级数拓展到傅里叶变换

  我们已经知道了周期性函数只要满足迪利克雷条件就可以转换成傅里叶级数。对于非周期函数,因为其周期T趋于无穷大,我们不能将其用傅里叶级数展开,而需要做某些修改。

  若f(t)为非周期函数,则可视它为周期无限大,角频率趋于0的周期函数。此时,各个相邻的谐波频率之差$\Delta \omega=(n+1) \omega_{0}-n \omega_{0}=\omega_{0}$很小,谐波频率$n \omega_{0}$需用一个变量\(\omega\)代替(此时的\(\omega\)不同于之前的角频率)。这样傅里叶级数的指数形式即可改写为

$$
\begin{array}{l}{f(t)=\sum_{\omega=-\infty}^{\infty} a_{\omega} e^{-j w t} d t} \\ {a_{w}=\frac{\Delta \omega}{2 \pi} \int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t}\end{array}
$$

两式合并有:

$$
f(t)=\sum_{\omega=-\infty}^{\infty}\left[\frac{\Delta \omega}{2 \pi} \int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t\right] e^{j w t}=\frac{1}{2 \pi} \sum_{\omega=-\infty}^{\infty}\left[\int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t\right] e^{j w t} \Delta \omega
$$

当T趋近于无穷时,$\Delta \omega$趋近于$d \omega$,求和式变为积分式,上面的公式可以写成

$$
f(t)=\frac{1}{2 \pi} \int_{-\infty}^{\infty}\left[\int_{-\infty}^{\infty} f(t) e^{-j \omega t} d t\right] e^{j \omega t} d t
$$

 则此公式即为非周期函数的傅里叶积分形式之一。

若令

$$
F(\omega)=\int_{-\infty}^{\infty} f(t) e^{-j \omega t} d t
$$

$$
f(t)=\frac{1}{2 \pi} \int_{-\infty}^{\infty} F(\omega) e^{j \omega t} d t
$$

则我们称这两个公司为傅里叶变换对,F(w)称为f(t)的傅里叶变换,而f(t)称为傅里叶反变换。

5、傅里叶变换的离散化——单变量的离散傅里叶变换(DFT)

  由于计算机处理的都是离散的变量,所以连续函数必须转换为离散值序列。这是用取样和量化来完成的。对于一个连续函数f(t),我们希望以自变量t的均匀间隔取样,由信号与系统可知,我们可以用一个单位间隔的脉冲响应作为取样函数乘以f(t),即

$$
\tilde{f}(t)=f(t) s_{\Delta T}(t)=\sum_{n=-\infty}^{\infty} f(t) \sigma\left(t-n_{\Delta} T\right)
$$

  用图像表示为:

  由图可知,序列中的任意取样值$f_{k}$可用如下式子表示:

$$
f_{k}=\int_{-\infty}^{\infty} f(t) \delta(t-k \Delta T) \mathrm{d} t=f(k \Delta T)
$$

  取样后的函数的傅里叶变换$\tilde{F}(\mu)$是

$$
\tilde{F}(\mu)=\mathfrak{J}\{\tilde{f}(t)\}=\mathcal{J}\left\{f(t) S_{\Delta T}(t)\right\}=\mathrm{F}(\mu) \otimes S(\mu)
$$

对于脉冲串$S_{\Delta T}(t)$是周期为$\Delta T$的周期函数,则由傅里叶级数定理有

$$
s_{\Delta T}(t)=\sum_{n=-\infty}^{\infty} c_{n} \mathrm{e}^{\mathrm{j} \frac{2 \pi n}{\Delta T} t}
$$

式中:

$$
c_{n}=\frac{1}{\Delta T} \int_{-\Delta T / 2}^{\Delta T / 2} s_{\Delta T}(t) \mathrm{e}^{-\mathrm{j} \frac{2 \pi n}{\Delta T} t} \mathrm{d} t
$$

由冲激串图可以看出

在区间$[-\Delta T / 2, \Delta T / 2]$的积分只包含位于原点的冲激,即

$$
c_{n}=\frac{1}{\Delta T} \int_{-\Delta T / 2}^{\Delta T / 2} \delta(t) \mathrm{e}^{-\mathrm{j} \frac{2 \pi n}{\Delta T}} \mathrm{d} t
$$

由冲激的采样特性

$$
\int_{-\infty}^{\infty} f(t) \delta(t) \mathrm{d} t=f(0)
$$

$$
\mathrm{c}_{n}=\frac{1}{\Delta T} e^{-j \frac{2 \pi n}{\Delta T} 0}=\frac{1}{\Delta T} e^{0}=\frac{1}{\Delta T}
$$

因此,傅里叶级数可展开为:

$$
s_{\Delta T}(t)=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}
$$

由傅里叶变换的指数形式可以知道冲激$\delta\left(t-t_{0}\right)$的傅里叶变换为$e^{-j 2 \pi \mu t_{0}}$,所以$S(\mu)$可以化简为:

$$
S(\mu)=\Im\left\{s_{\Delta T}(t)\right\}=\Im\left\{\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}\right\}=\frac{1}{\Delta T} \mathfrak{S}\left\{\sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}\right\}=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \delta\left(\mu-\frac{n}{\Delta T}\right)
$$

所以这里我们可以直接得到

$$
\begin{aligned} \tilde{F}(\mu) &=F(\mu) \star S(\mu)=\int_{-\infty}^{\infty} F(\tau) S(\mu-\tau) \mathrm{d} \tau \\ &=\frac{1}{\Delta T} \int_{-\infty}^{\infty} F(\tau) \sum_{n=-\infty}^{\infty} \delta\left(\mu-\tau-\frac{n}{\Delta T}\right) \mathrm{d} \tau=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \int_{-\infty}^{\infty} F(\tau) \delta\left(\mu-\tau-\frac{n}{\Delta T}\right) \mathrm{d} \tau \\ &=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} F\left(\mu-\frac{n}{\Delta T}\right) \end{aligned}
$$

  至此,我们就得到了关于原始函数变换的取样过的数据的变换$\tilde{F}(\mu)$,但是这里最初我们给定的还是连续函数。

  

  接下来我们思考如果给定的是离散的信号的时候,则$\tilde{f}(t)$的傅里叶变换为:

$$
\tilde{F}(\mu)=\int_{-\infty}^{\infty} \tilde{f}(t) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t
$$

将最开始我们给出的$\tilde{f}(t)$函数带入有

$$
\begin{aligned} \tilde{F}(\mu) &=\int_{-\infty}^{\infty} \tilde{f}(t) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t=\int_{-\infty}^{\infty} \sum_{n=-\infty}^{\infty} f(t) \delta(t-n \Delta T) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t=\sum_{n=-\infty}^{\infty} \int_{-\infty}^{\infty} f(t) \delta(t-n \Delta T) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t \\ &=\sum_{n=-\infty}^{\infty} f_{n} \mathrm{e}^{-\mathrm{j} 2 \pi \mu n \Delta T} \end{aligned}
$$

·  由给定连续函数求解的结果我们知道,其傅里叶变换$\tilde{F}(\mu)$是周期为1$/ \Delta T$的无限连续函数,因此,我们需要在一个周期内表示函数$\tilde{F}(\mu)$,这也是DFT的基础

  假设我们想在周期$\mu=0$和$\mu=1 / \Delta T$得到$\tilde{F}(\mu)$的M个等间距样本。即:

$$
\mu=\frac{m}{M \Delta T}, \quad m=0,1,2, \cdots, M-1
$$

  将其带入$\tilde{F}(\mu)$,则有:

$$
F_{m}=\sum_{m}^{M-1} f_{n} \mathrm{e}^{-\mathrm{j} 2 \pi m n / M}, \quad m=0,1,2, \cdots, M-1
$$

  这个表达式即为我们寻找的离散傅里叶变换。此时,给定一个由$f(t)$的M个样本组成的集合$\left\{f_{n}\right\}$,我们可以通过上式来得出一个与输入样本集合离散傅里叶变换相对应的M个复离散值的样本集合$\left\{F_{m}\right\}$。

  反之,给定$\left\{F_{m}\right\}$,我们可以用傅里叶反变换复原样本集$\left\{f_{n}\right\}$

$$
f_{n}=\frac{1}{M} \sum_{m=0}^{M-1} F_{m} \mathrm{e}^{\mathrm{j} 2 \pi m n / M}, \quad n=0,1,2, \cdots, M-1
$$

6、二维傅里叶变换

  有了之前的基础,我们可以将傅里叶变换拓展到二维空间,二维连续傅立叶变换为:

$$
F(u, v)=\int_{-\infty-\infty}^{\infty} \int_{-\infty}^{\infty} f(x, y) e^{-j 2 \pi(\mu x+v y)} d x d y
$$

反变换为:

$$
f(x, y)=\int_{-\infty}^{\infty} \int_{-\infty}^{\infty} F(u, v) e^{j 2 \pi(\mu x+v y)} d u d v
$$

对于一个图像尺寸为M×N的 函数的离散傅里叶变换由以下等式给出:

$$
F(u, v)=\sum_{x=0}^{M-1} \sum_{y=0}^{N-1} f(x, y) e^{-\mathrm{j} 2 \pi(u x / M+v y / N)}
$$

给出了变换函数,我们可以得到傅里叶反变换公式为:

$$
f(x, y)=\frac{1}{M N} \sum_{u=0}^{M-1} \sum_{v=0}^{N-1} F(u, v) \mathrm{e}^{\mathrm{j} 2 \pi(u x / M+v y / N)}
$$

因为离散傅里叶变换通常是复函数,因此可用极坐标形式来表示

$$
F(u, v)=|F(u, v)| \mathrm{e}^{\mathrm{j} \phi(u, v)}
$$

其中,幅度

$$
|F(u, v)|=\left[R^{2}(u, v)+I^{2}(u, v)\right]^{1 / 2}
$$

称为傅里叶变换谱(频谱)

$$
\phi(u, v)=\arctan \left[\frac{I(u, v)}{R(u, v)}\right]
$$

称为相角,这里的actan必须用四象限反正切来计算。

最后,功率谱定义为:

$$
F(u, v)=|F(u, v)|^{2}=R^{2}(u, v)+I^{2}(u, v)
$$

R和I分别是F(u,v)的实部和虚部。

2、基于傅里叶变换的数字信号处理

  傅里叶的意义就是给出了一个在频域解决问题的方法,本质就是解释了任意波都是由简单的正弦波组成的。

利用傅里叶变换的这种特性,我们可以将时域的信号转到频域去处理,例如一个声音谱,我们可以将其转到频率谱如下:

   相信大家听歌的时候都看到过相关的动态频谱波形。这里只是将声音信号转换成了频谱给大家观察,其实在日常生活中,我们从app听歌的声音都是无噪声干扰的,如果你做过从声音采集到功放输出的声音信号处理的过程,你就会知道,在声音采集的过程中,我们噪声是难以避免的,如果你需要做人声采集,你则需要将声音的300~3.4Khz的声音频率以外的噪声滤除掉。在模电中,你可以设计带通滤波器来完成相关的滤波工作。而当我们通过AD转换直接将声音采集后,我们同样可以通过DFT变换得到声音的频谱图来进行滤波操作。这个不是我们这个系列的重点,我就不做详细讲解了。

3、图像的傅里叶变换基础

  在声学领域,我们可以把傅里叶变换的结果不同频段的能量的强度。在图像领域,我们可以将傅里叶变换可以看作是数学上的棱镜,将图像基于频率分解为不同的成分。当我们考虑光时,讨论它的光谱或频率谱。然而一般来说,将一幅图像的某些特定成分与其傅里叶变换直接联系联系起来通常是不可能的,但是关于傅里叶变换的频率成分和一幅图像的空间特性间的关系还是可以做出某些一般的叙述。例如,因为频率直接关系到空间变化率,因此直观的将傅里叶变换中的频率与图像中的亮度变换模式联系起来并不困难。二且图像与数字信号不同,其傅里叶变换结果并非是一维的单一曲线,而是一个二维的平面,类比关系如下:

  同理,二维的傅里叶变换还可拓展至三维:

  在光学方法做傅立叶变换一书中,给出了一个相关实现实验,如图,左侧是字符3的镂空平板,将其放透镜前面,用平行光照明,透镜焦平面上将显示其二维傅立叶变换。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部        // 计算谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

  程序运行结果如下(原图可以通过对上图进行截取获得):

 

下面来一步步看看,各个步骤的作用。如果不进行归一化处理的结果如下图所示,整个画面发白,根本看不到任何信息。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	//normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

如果不进行象限调整,结果如下,低频出现在图片的四个角(四角发亮),而通过调整之后将低频调整到了原点附近。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	//Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	//Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	//Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	//Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	////变换左上角和右下角象限
	//Mat tmp;
	//q0.copyTo(tmp);
	//q3.copyTo(q0);
	//tmp.copyTo(q3);

	////变换右上角和左下角象限
	//q1.copyTo(tmp);
	//q2.copyTo(q1);
	//tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

如果不进行尺度调整,可以发现对比度不高,仅仅能看到中心一个亮点,说明尺度调整后,能显示更多细节。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部        // 计算功率谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	//log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

4、傅里叶反变换复原图像

   通过傅里叶变换将图像转换到频率域之后,我们可以通过滤波器操作对图像进行噪声滤波操作,关于滤波器的操作将在下一篇文章中讲到,这里不再赘述,经过滤波操作后的图像需要通过傅里叶反变换来得到傅里叶反变换的结果,OpenCV提供了傅里叶反变换的程序,下面是一个详细示例,可以帮助你理解相关API接口及其使用方法:

#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
	//Mat input = imread(p[1], CV_LOAD_IMAGE_GRAYSCALE);//以灰度图像的方式读入图片
		//如果不知到怎么传入p[1]。可以改为
	Mat input = imread("2222.jpg", 0);
	imshow("input", input);//显示原图
	int w = getOptimalDFTSize(input.cols);
	int h = getOptimalDFTSize(input.rows);//获取最佳尺寸,快速傅立叶变换要求尺寸为2的n次方
	Mat padded;
	copyMakeBorder(input, padded, 0, h - input.rows, 0, w - input.cols, BORDER_CONSTANT, Scalar::all(0));//填充图像保存到padded中
	Mat plane[] = { Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F) };//创建通道
	Mat complexIm;
	merge(plane, 2, complexIm);//合并通道
	dft(complexIm, complexIm);//进行傅立叶变换,结果保存在自身
	split(complexIm, plane);//分离通道     // 计算谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(plane[0], plane[1], plane[0]);//获取幅度图像,0通道为实数通道,1为虚数,因为二维傅立叶变换结果是复数
	int cx = padded.cols / 2; int cy = padded.rows / 2;//一下的操作是移动图像,左上与右下交换位置,右上与左下交换位置
	Mat temp;
	Mat part1(plane[0], Rect(0, 0, cx, cy));
	Mat part2(plane[0], Rect(cx, 0, cx, cy));
	Mat part3(plane[0], Rect(0, cy, cx, cy));
	Mat part4(plane[0], Rect(cx, cy, cx, cy));

	part1.copyTo(temp);
	part4.copyTo(part1);
	temp.copyTo(part4);

	part2.copyTo(temp);
	part3.copyTo(part2);
	temp.copyTo(part3);
	//*******************************************************************

		//Mat _complexim(complexIm,Rect(padded.cols/4,padded.rows/4,padded.cols/2,padded.rows/2));
		//opyMakeBorder(_complexim,_complexim,padded.rows/4,padded.rows/4,padded.cols/4,padded.cols/4,BORDER_CONSTANT,Scalar::all(0.75));
	Mat _complexim;
	complexIm.copyTo(_complexim);//把变换结果复制一份,进行逆变换,也就是恢复原图
	Mat iDft[] = { Mat::zeros(plane[0].size(),CV_32F),Mat::zeros(plane[0].size(),CV_32F) };//创建两个通道,类型为float,大小为填充后的尺寸
	idft(_complexim, _complexim);//傅立叶逆变换
	split(_complexim, iDft);//结果貌似也是复数
	magnitude(iDft[0], iDft[1], iDft[0]);//分离通道,主要获取0通道
	normalize(iDft[0], iDft[0], 1, 0, CV_MINMAX);//归一化处理,float类型的显示范围为0-1,大于1为白色,小于0为黑色
	imshow("idft", iDft[0]);//显示逆变换
//*******************************************************************
	plane[0] += Scalar::all(1);//傅立叶变换后的图片不好分析,进行对数处理,结果比较好看
	log(plane[0], plane[0]);
	normalize(plane[0], plane[0], 1, 0, CV_MINMAX);

	imshow("dft", plane[0]);
	waitKey(0);
	return 0;
}

  

5、傅里叶功率谱和相位谱对图像

  我们之前都是谈到的傅里叶谱及对傅里叶谱进行操作,前面我们也讲到了,傅里叶谱可以分解为功率谱和相位谱,这里通过实验讲解一下功率谱和相位谱对应整个图像的信息。

#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

//将幅度归一,相角保持不变
void one_amplitude(Mat &complex_r, Mat &complex_i, Mat &dst)
{

	Mat temp[] = { Mat::zeros(complex_r.size(),CV_32FC1), Mat::zeros(complex_i.size(),CV_32FC1) };
	float realv=0.0, imaginv=0.0;
	for (int i = 0;  i < complex_r.rows;  i++) {
		for (int j = 0;  j< complex_r.cols; j++) {
			//cout << complex_r.at<float>(i, j) << endl;
			realv = complex_r.at<float>(i, j);
			imaginv = complex_i.at<float>(i, j);
			float distance = sqrt(realv*realv + imaginv * imaginv);
			float a = realv / distance;
			float b = imaginv / distance;
			temp[0].at<float>(i, j) = a;
			temp[1].at<float>(i, j) = b;
		}
	}
	merge(temp, 2, dst);//多通道合成一个通道
}

//将相角归一,幅值保持不变
void one_angel(Mat &complex_r, Mat &complex_i, Mat &dst)
{

	Mat temp[] = { Mat::zeros(complex_r.size(),CV_32FC1), Mat::zeros(complex_i.size(),CV_32FC1) };
	float realv = 0.0, imaginv = 0.0;
	for (int i = 0; i < complex_r.rows; i++) {
		for (int j = 0; j < complex_r.cols; j++) {
			realv = complex_r.at<float>(i, j);
			imaginv = complex_i.at<float>(i, j);
			float distance = sqrt(realv*realv + imaginv * imaginv);
			temp[0].at<float>(i, j) = distance / sqrt(2);
			temp[1].at<float>(i, j) = distance / sqrt(2);
		}
	}
	merge(temp, 2, dst);
}

//使用1的幅值和2的相位合并
void mixed_amplitude_with_phase(Mat &real1, Mat &imag1, Mat &real2, Mat &imag2, Mat &dst)
{
	if (real1.size() != real2.size()) {
		std::cerr << "image don‘t ==" << std::endl;
		return;
	}
	Mat temp[] = { Mat::zeros(real1.size(),CV_32FC1), Mat::zeros(real1.size(),CV_32FC1) };
	float realv1 = 0.0, imaginv1 = 0.0, realv2 = 0.0, imaginv2 = 0.0;
	for (int i = 0; i < real1.rows; i++) {
		for (int j = 0; j < real1.cols; j++) {
			realv1 = real1.at<float>(i, j);
			imaginv1 = imag1.at<float>(i, j);
			realv2 = real2.at<float>(i, j);
			imaginv2 = imag2.at<float>(i, j);
			float distance1 = sqrt(realv1*realv1 + imaginv1 * imaginv1);
			float distance2 = sqrt(realv2*realv2 + imaginv2 * imaginv2);
			temp[0].at<float>(i, j) = (realv2*distance1) / distance2;
			temp[1].at<float>(i, j) = (imaginv2*distance1) / distance2;
		}
	}
	merge(temp, 2, dst);
}

//使用1的相位和2的幅值合并
void mixed_phase_with_amplitude(Mat &real1, Mat &imag1, Mat &real2, Mat &imag2, Mat &dst)
{
	if (real1.size() != real2.size()) {
		std::cerr << "image don‘t ==" << std::endl;
		return;
	}
	Mat temp[] = { Mat::zeros(real1.size(),CV_32FC1), Mat::zeros(real1.size(),CV_32FC1) };
	float realv1 = 0.0, imaginv1 = 0.0, realv2 = 0.0, imaginv2 = 0.0;
	for (int i = 0; i < real1.rows; i++) {
		for (int j = 0; j < real1.cols; j++) {
			realv1 = real1.at<float>(i, j);
			imaginv1 = imag1.at<float>(i, j);
			realv2 = real2.at<float>(i, j);
			imaginv2 = imag2.at<float>(i, j);
			float distance1 = sqrt(realv1*realv1 + imaginv1 * imaginv1);
			float distance2 = sqrt(realv2*realv2 + imaginv2 * imaginv2);
			temp[0].at<float>(i, j) = (realv1*distance2) / distance1;
			temp[1].at<float>(i, j) = (imaginv1*distance2) / distance1;
		}
	}
	merge(temp, 2, dst);
}

cv::Mat fourior_inverser(Mat &_complexim)
{
	Mat dst;
	Mat iDft[] = { Mat::zeros(_complexim.size(),CV_32F),Mat::zeros(_complexim.size(),CV_32F) };//创建两个通道,类型为float,大小为填充后的尺寸
	idft(_complexim, _complexim);//傅立叶逆变换
	split(_complexim, iDft);//结果貌似也是复数
	magnitude(iDft[0], iDft[1], dst);//分离通道,主要获取0通道
//    dst += Scalar::all(1);                    // switch to logarithmic scale
//    log(dst, dst);
	//归一化处理,float类型的显示范围为0-255,255为白色,0为黑色
	normalize(dst, dst, 0, 255, NORM_MINMAX);
	dst.convertTo(dst, CV_8U);
	return dst;
}

void move_to_center(Mat &center_img)
{
	int cx = center_img.cols / 2;
	int cy = center_img.rows / 2;
	Mat q0(center_img, Rect(0, 0, cx, cy));
	Mat q1(center_img, Rect(cx, 0, cx, cy));
	Mat q2(center_img, Rect(0, cy, cx, cy));
	Mat q3(center_img, Rect(cx, cy, cx, cy));

	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);
}

void fast_dft(cv::Mat &src_img, cv::Mat &real_img, cv::Mat &ima_img)
{
	src_img.convertTo(src_img, CV_32FC1);

	///////////////////////////////////////快速傅里叶变换/////////////////////////////////////////////////////
	int oph = getOptimalDFTSize(src_img.rows);
	int opw = getOptimalDFTSize(src_img.cols);
	Mat padded;
	copyMakeBorder(src_img, padded, 0, oph - src_img.rows, 0, opw - src_img.cols,
		BORDER_CONSTANT, Scalar::all(0));

	Mat temp[] = { padded, Mat::zeros(padded.size(),CV_32FC1) };
	Mat complexI;
	merge(temp, 2, complexI);
	dft(complexI, complexI);//傅里叶变换
	split(complexI, temp); //将傅里叶变换结果分为实部和虚部
	temp[0].copyTo(real_img);
	temp[1].copyTo(ima_img);
}

int main()
{

	Mat image = imread("2222.jpg", IMREAD_GRAYSCALE);

	imshow("woman_src", image);

	Mat woman_real, woman_imag;
	Mat sqrt_real, sqrt_imag;

	fast_dft(image, woman_real, woman_imag);//woman_real实部,woman_imag虚部
	fast_dft(image, sqrt_real, sqrt_imag);
	//
	Mat img_range, img_angle;
	one_amplitude(woman_real, woman_imag, img_range);//原图像的幅值归一化
	one_angel(woman_real, woman_imag, img_angle);//原图像的相位归一化
	//
	Mat woman_amp2sqrt_angle, sqrt_amp2woman_angle;
	mixed_amplitude_with_phase(woman_real, woman_imag,
		sqrt_real, sqrt_imag, woman_amp2sqrt_angle);
	mixed_phase_with_amplitude(woman_real, woman_imag,
		sqrt_real, sqrt_imag, sqrt_amp2woman_angle);
	Mat amplitude, angle, amplitude_src;
	magnitude(woman_real, woman_imag, amplitude); //计算原图像幅值
	phase(woman_real, woman_imag, angle);  //计算原图像相位
    //cartToPolar(temp[0], temp[1],amplitude, angle);

	move_to_center(amplitude);//幅值亮度集合到中心

	divide(amplitude, amplitude.cols*amplitude.rows, amplitude_src);
	imshow("amplitude_src", amplitude_src);

	amplitude += Scalar::all(1);// switch to logarithmic scale
	log(amplitude, amplitude);
	normalize(amplitude, amplitude, 0, 255, NORM_MINMAX); //归一化 方便显示,和实际数据没有关系
	amplitude.convertTo(amplitude, CV_8U);
	imshow("amplitude", amplitude);

	normalize(angle, angle, 0, 255, NORM_MINMAX); //归一化 方便显示,和实际数据没有关系
	angle.convertTo(angle, CV_8U);
	imshow("angle", angle);

	//*******************************************************************

	Mat inverse_amp = fourior_inverser(img_range);//傅里叶逆变换
	Mat inverse_angle = fourior_inverser(img_angle);
	Mat inverse_dst1 = fourior_inverser(woman_amp2sqrt_angle);
	move_to_center(inverse_angle);
	imshow("相位谱复原结果", inverse_amp);
	imshow("功率谱复原结果", inverse_angle);
	imshow("傅里叶谱图复原结果", inverse_dst1);
	waitKey(0);

	return 1;
}

  结果如下:从左到右依次是原图、傅里叶谱、功率谱、相位谱、相位谱傅里叶反变换结果、功率谱傅里叶反变换结果、傅里叶谱反变换结果

  

通过结果可以发现,相位谱决定着图像结构的位置信息,功率谱决定着图像结构的整体灰度分布的特性,如明暗、灰度变化趋势等,反应的是图像整体上各个方向上的频率分量的相对强度。

参考资料:

下面这个方波分解示意图用matlab怎么画?

如何理解傅里叶变换公式?

傅里叶频域,复数域,冲激函数,香农采样(不介绍公式-只介绍是啥)另一种思维

DFT

opencv学习(十五)之图像傅里叶变换dft

[数字图像处理]频域滤波(1)--基础与低通滤波器

CS425 Lab: Frequency Domain Processing

傅里叶分析与图像处理

二维傅里叶变换是怎么进行的?

  如果是理工科的学生 ,在高等数学和信号处理的课程中应该就已经学习过Fourier变换 ,但是这里还是进行一个简单的基本学习和理解,为时域转频域提供一个基础理论概念。

1、什么是傅里叶级数

  周期函数的fourier级数是由正弦函数和余弦函数组成的三角级数。这里首先说结论周期为T的任意周期性函数f(t),若满足以下迪利克雷条件:

  • 在一个周期内只有有限个不连续点;
  • 爱一个周期内只有有限个极大和极小值
  • 积分$\int_{-\frac{-T}{2}}^{\frac{T}{2}}|f(t)| d t$存在,则f(t)可展开为如下傅里叶级数

$$
f(x)=\frac{a_{0}}{2}+\sum_{k=1}^{\infty}\left(a_{k} \cos k x+b_{k} \sin k x\right)
$$

其中

$$
\begin{aligned} a_{n} &=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \cos n x \mathrm{d} x \quad(n=0,1,2,3, \cdots) \\ b_{n} &=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin n x \mathrm{d} x \quad(n=1,2,3, \cdots) \end{aligned}
$$

式中的$w=\frac{2 \pi}{T}$称为角频率。

为了便于理解,这里先给出几个傅里叶变换拟合周期函数的示例图(关于这个图如何绘制,不是本文重点,可以查看参考资料):

2、傅里叶级数的提出及理论依据

  在早先,拉格朗日等数学家发现某些周期函数可以由三角函数的和来表示,比如下图中,黑色的斜线就是周期为的函数,而红色的曲线是三角函数之和,可以看出两者确实近似:

基于此,傅里叶提出了任意周期函数都可以写成三角函数之和的猜测,其公式提出思想基本如下:

  • 首先,根据周期函数的定义,常数函数是周期函数,周期为任意实数。所以,分解里面得有一个常数项。
  • 任意函数可以分解和奇偶函数之和,$f(x)=\frac{f(x)+f(-x)}{2}+\frac{f(x)-f(-x)}{2}=f_{\text { even }}+f_{\text { odd }}$,由正余弦函数分别为奇偶函数,且其为周期性函数,而且还具有正交的特性,所以分解里面需要由正余弦函数sin和cos
  • 如下图所示,对于$\sin (n x), n \in \mathbb{N}$,虽然最小周期是$\frac{\pi}{n}$,但是其周期中都有一个周期为2$\pi$,则对于周期为T的函数,可以知道角频率$w=\frac{2 \pi}{T}$,将这些函数进行加减(这里用级数表示),就保证了得到的函数的周期也为 T。
  • 又因为正余弦函数的值域只有[-1,1],而需要表示的函数的至于变化显然不止[-1,1],为了适应函数的值域,正余弦级数必然会有各自不同的系数an和bn

至此,我们就得到了傅里叶级数的猜想式,即

$$
f(x)=\frac{a_{0}}{2}+\sum_{n=1}^{\infty}\left(a_{n} \cos \left(\frac{2 \pi n}{T} x\right)+b_{n} \sin \left(\frac{2 \pi n}{T} x\right)\right), \frac{a_{0}}{2} \in \mathbb{R}
$$

3、傅里叶级数的系数计算

   在前面我们已经提出了傅里叶级数的猜想式

$$
f(x)=\frac{a_{0}}{2}+\sum_{n=1}^{\infty}\left(a_{n} \cos \left(\frac{2 \pi n}{T} x\right)+b_{n} \sin \left(\frac{2 \pi n}{T} x\right)\right), \frac{a_{0}}{2} \in \mathbb{R}
$$

但是我们还不知道系数C,an,bn和f(x)之间的关系。

  为了求解系数域函数之间的关系,这里我们进一步对假设级数进行逐步积分

  • 首先,对猜想式两端在$[-\pi, \pi]$上进行积分,并在等式的右端逐项积分,利用三角函数的正交性有

$$
\int_{-\pi}^{\pi} f(x) d x=\int_{-\pi}^{\pi} \frac{a_{0}}{2} d x+\sum_{n=1}^{\infty}\left(a_{n} \int_{-\pi}^{\pi} \cos n x d x+b_{n} \int_{-\pi}^{\pi} \sin n x d x\right)=a_{0} \pi
$$

于是有:

$$
a_{0}=\frac{1}{ \pi} \int_{-\pi}^{\pi} f(x) d x
$$

  • 将猜想式两端同乘cos nx,再用同样的方法求积分,利用三角函数系的正交性有:

$$
\int_{-\pi}^{\pi} f(x) \cos m x d x=\int_{-\pi}^{\pi} \frac{a_{0}}{2} \cos m x d x+\sum_{n=1}^{\infty}\left(a_{n} \int_{-\pi}^{\pi} \cos n x \cos n x d x+b_{n} \int_{-\pi}^{\pi} \cos m x \sin n x d x\right)=\int_{-\pi}^{\pi} a_{n} \cos ^{2} n x=a_{n} \pi
$$

所以我们可以得出

$$
a_{n}=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \cos n x d x
$$

  • 同理将猜想式两端同乘sin nx,再用同样的方法求积分,可以得出

$$
b_{n}=\frac{1}{\pi} \int_{-\pi}^{\pi} f(x) \sin n x d x
$$

3、傅里叶级数的指数形式

结合欧拉公式

$$
e^{i t}=\cos (t)+i \sin (t)
$$

我们有

$$
\begin{aligned} \sin \theta &=\frac{e^{i \theta}-e^{-i \theta}}{2 i} \\ \cos \theta &=\frac{e^{i \theta}+e^{-i \theta}}{2} \end{aligned}
$$

根据上式,我们可以写出傅立叶级数的另外一种形式:

$$
f(x)=\sum_{n=-\infty}^{\infty} c_{n} \cdot e^{i \frac{2 \pi n x}{T}}
$$

其中

$$
c_{n}=\frac{1}{T} \int_{x_{0}}^{x_{0}+T} f(x) \cdot e^{-i \frac{2 \pi n x}{T}} d x
$$

4、由傅里叶级数拓展到傅里叶变换

  我们已经知道了周期性函数只要满足迪利克雷条件就可以转换成傅里叶级数。对于非周期函数,因为其周期T趋于无穷大,我们不能将其用傅里叶级数展开,而需要做某些修改。

  若f(t)为非周期函数,则可视它为周期无限大,角频率趋于0的周期函数。此时,各个相邻的谐波频率之差$\Delta \omega=(n+1) \omega_{0}-n \omega_{0}=\omega_{0}$很小,谐波频率$n \omega_{0}$需用一个变量\(\omega\)代替(此时的\(\omega\)不同于之前的角频率)。这样傅里叶级数的指数形式即可改写为

$$
\begin{array}{l}{f(t)=\sum_{\omega=-\infty}^{\infty} a_{\omega} e^{-j w t} d t} \\ {a_{w}=\frac{\Delta \omega}{2 \pi} \int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t}\end{array}
$$

两式合并有:

$$
f(t)=\sum_{\omega=-\infty}^{\infty}\left[\frac{\Delta \omega}{2 \pi} \int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t\right] e^{j w t}=\frac{1}{2 \pi} \sum_{\omega=-\infty}^{\infty}\left[\int_{-\frac{T}{2}}^{\frac{T}{2}} f(t) e^{-j \omega t} d t\right] e^{j w t} \Delta \omega
$$

当T趋近于无穷时,$\Delta \omega$趋近于$d \omega$,求和式变为积分式,上面的公式可以写成

$$
f(t)=\frac{1}{2 \pi} \int_{-\infty}^{\infty}\left[\int_{-\infty}^{\infty} f(t) e^{-j \omega t} d t\right] e^{j \omega t} d t
$$

 则此公式即为非周期函数的傅里叶积分形式之一。

若令

$$
F(\omega)=\int_{-\infty}^{\infty} f(t) e^{-j \omega t} d t
$$

$$
f(t)=\frac{1}{2 \pi} \int_{-\infty}^{\infty} F(\omega) e^{j \omega t} d t
$$

则我们称这两个公司为傅里叶变换对,F(w)称为f(t)的傅里叶变换,而f(t)称为傅里叶反变换。

5、傅里叶变换的离散化——单变量的离散傅里叶变换(DFT)

  由于计算机处理的都是离散的变量,所以连续函数必须转换为离散值序列。这是用取样和量化来完成的。对于一个连续函数f(t),我们希望以自变量t的均匀间隔取样,由信号与系统可知,我们可以用一个单位间隔的脉冲响应作为取样函数乘以f(t),即

$$
\tilde{f}(t)=f(t) s_{\Delta T}(t)=\sum_{n=-\infty}^{\infty} f(t) \sigma\left(t-n_{\Delta} T\right)
$$

  用图像表示为:

  由图可知,序列中的任意取样值$f_{k}$可用如下式子表示:

$$
f_{k}=\int_{-\infty}^{\infty} f(t) \delta(t-k \Delta T) \mathrm{d} t=f(k \Delta T)
$$

  取样后的函数的傅里叶变换$\tilde{F}(\mu)$是

$$
\tilde{F}(\mu)=\mathfrak{J}\{\tilde{f}(t)\}=\mathcal{J}\left\{f(t) S_{\Delta T}(t)\right\}=\mathrm{F}(\mu) \otimes S(\mu)
$$

对于脉冲串$S_{\Delta T}(t)$是周期为$\Delta T$的周期函数,则由傅里叶级数定理有

$$
s_{\Delta T}(t)=\sum_{n=-\infty}^{\infty} c_{n} \mathrm{e}^{\mathrm{j} \frac{2 \pi n}{\Delta T} t}
$$

式中:

$$
c_{n}=\frac{1}{\Delta T} \int_{-\Delta T / 2}^{\Delta T / 2} s_{\Delta T}(t) \mathrm{e}^{-\mathrm{j} \frac{2 \pi n}{\Delta T} t} \mathrm{d} t
$$

由冲激串图可以看出

在区间$[-\Delta T / 2, \Delta T / 2]$的积分只包含位于原点的冲激,即

$$
c_{n}=\frac{1}{\Delta T} \int_{-\Delta T / 2}^{\Delta T / 2} \delta(t) \mathrm{e}^{-\mathrm{j} \frac{2 \pi n}{\Delta T}} \mathrm{d} t
$$

由冲激的采样特性

$$
\int_{-\infty}^{\infty} f(t) \delta(t) \mathrm{d} t=f(0)
$$

$$
\mathrm{c}_{n}=\frac{1}{\Delta T} e^{-j \frac{2 \pi n}{\Delta T} 0}=\frac{1}{\Delta T} e^{0}=\frac{1}{\Delta T}
$$

因此,傅里叶级数可展开为:

$$
s_{\Delta T}(t)=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}
$$

由傅里叶变换的指数形式可以知道冲激$\delta\left(t-t_{0}\right)$的傅里叶变换为$e^{-j 2 \pi \mu t_{0}}$,所以$S(\mu)$可以化简为:

$$
S(\mu)=\Im\left\{s_{\Delta T}(t)\right\}=\Im\left\{\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}\right\}=\frac{1}{\Delta T} \mathfrak{S}\left\{\sum_{n=-\infty}^{\infty} \mathrm{e}^{j \frac{2 \pi n}{\Delta T} t}\right\}=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \delta\left(\mu-\frac{n}{\Delta T}\right)
$$

所以这里我们可以直接得到

$$
\begin{aligned} \tilde{F}(\mu) &=F(\mu) \star S(\mu)=\int_{-\infty}^{\infty} F(\tau) S(\mu-\tau) \mathrm{d} \tau \\ &=\frac{1}{\Delta T} \int_{-\infty}^{\infty} F(\tau) \sum_{n=-\infty}^{\infty} \delta\left(\mu-\tau-\frac{n}{\Delta T}\right) \mathrm{d} \tau=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} \int_{-\infty}^{\infty} F(\tau) \delta\left(\mu-\tau-\frac{n}{\Delta T}\right) \mathrm{d} \tau \\ &=\frac{1}{\Delta T} \sum_{n=-\infty}^{\infty} F\left(\mu-\frac{n}{\Delta T}\right) \end{aligned}
$$

  至此,我们就得到了关于原始函数变换的取样过的数据的变换$\tilde{F}(\mu)$,但是这里最初我们给定的还是连续函数。

  

  接下来我们思考如果给定的是离散的信号的时候,则$\tilde{f}(t)$的傅里叶变换为:

$$
\tilde{F}(\mu)=\int_{-\infty}^{\infty} \tilde{f}(t) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t
$$

将最开始我们给出的$\tilde{f}(t)$函数带入有

$$
\begin{aligned} \tilde{F}(\mu) &=\int_{-\infty}^{\infty} \tilde{f}(t) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t=\int_{-\infty}^{\infty} \sum_{n=-\infty}^{\infty} f(t) \delta(t-n \Delta T) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t=\sum_{n=-\infty}^{\infty} \int_{-\infty}^{\infty} f(t) \delta(t-n \Delta T) \mathrm{e}^{-\mathrm{j} 2 \pi \mu t} \mathrm{d} t \\ &=\sum_{n=-\infty}^{\infty} f_{n} \mathrm{e}^{-\mathrm{j} 2 \pi \mu n \Delta T} \end{aligned}
$$

·  由给定连续函数求解的结果我们知道,其傅里叶变换$\tilde{F}(\mu)$是周期为1$/ \Delta T$的无限连续函数,因此,我们需要在一个周期内表示函数$\tilde{F}(\mu)$,这也是DFT的基础

  假设我们想在周期$\mu=0$和$\mu=1 / \Delta T$得到$\tilde{F}(\mu)$的M个等间距样本。即:

$$
\mu=\frac{m}{M \Delta T}, \quad m=0,1,2, \cdots, M-1
$$

  将其带入$\tilde{F}(\mu)$,则有:

$$
F_{m}=\sum_{m}^{M-1} f_{n} \mathrm{e}^{-\mathrm{j} 2 \pi m n / M}, \quad m=0,1,2, \cdots, M-1
$$

  这个表达式即为我们寻找的离散傅里叶变换。此时,给定一个由$f(t)$的M个样本组成的集合$\left\{f_{n}\right\}$,我们可以通过上式来得出一个与输入样本集合离散傅里叶变换相对应的M个复离散值的样本集合$\left\{F_{m}\right\}$。

  反之,给定$\left\{F_{m}\right\}$,我们可以用傅里叶反变换复原样本集$\left\{f_{n}\right\}$

$$
f_{n}=\frac{1}{M} \sum_{m=0}^{M-1} F_{m} \mathrm{e}^{\mathrm{j} 2 \pi m n / M}, \quad n=0,1,2, \cdots, M-1
$$

6、二维傅里叶变换

  有了之前的基础,我们可以将傅里叶变换拓展到二维空间,二维连续傅立叶变换为:

$$
F(u, v)=\int_{-\infty-\infty}^{\infty} \int_{-\infty}^{\infty} f(x, y) e^{-j 2 \pi(\mu x+v y)} d x d y
$$

反变换为:

$$
f(x, y)=\int_{-\infty}^{\infty} \int_{-\infty}^{\infty} F(u, v) e^{j 2 \pi(\mu x+v y)} d u d v
$$

对于一个图像尺寸为M×N的 函数的离散傅里叶变换由以下等式给出:

$$
F(u, v)=\sum_{x=0}^{M-1} \sum_{y=0}^{N-1} f(x, y) e^{-\mathrm{j} 2 \pi(u x / M+v y / N)}
$$

给出了变换函数,我们可以得到傅里叶反变换公式为:

$$
f(x, y)=\frac{1}{M N} \sum_{u=0}^{M-1} \sum_{v=0}^{N-1} F(u, v) \mathrm{e}^{\mathrm{j} 2 \pi(u x / M+v y / N)}
$$

因为离散傅里叶变换通常是复函数,因此可用极坐标形式来表示

$$
F(u, v)=|F(u, v)| \mathrm{e}^{\mathrm{j} \phi(u, v)}
$$

其中,幅度

$$
|F(u, v)|=\left[R^{2}(u, v)+I^{2}(u, v)\right]^{1 / 2}
$$

称为傅里叶变换谱(频谱)

$$
\phi(u, v)=\arctan \left[\frac{I(u, v)}{R(u, v)}\right]
$$

称为相角,这里的actan必须用四象限反正切来计算。

最后,功率谱定义为:

$$
F(u, v)=|F(u, v)|^{2}=R^{2}(u, v)+I^{2}(u, v)
$$

R和I分别是F(u,v)的实部和虚部。

2、基于傅里叶变换的数字信号处理

  傅里叶的意义就是给出了一个在频域解决问题的方法,本质就是解释了任意波都是由简单的正弦波组成的。

利用傅里叶变换的这种特性,我们可以将时域的信号转到频域去处理,例如一个声音谱,我们可以将其转到频率谱如下:

   相信大家听歌的时候都看到过相关的动态频谱波形。这里只是将声音信号转换成了频谱给大家观察,其实在日常生活中,我们从app听歌的声音都是无噪声干扰的,如果你做过从声音采集到功放输出的声音信号处理的过程,你就会知道,在声音采集的过程中,我们噪声是难以避免的,如果你需要做人声采集,你则需要将声音的300~3.4Khz的声音频率以外的噪声滤除掉。在模电中,你可以设计带通滤波器来完成相关的滤波工作。而当我们通过AD转换直接将声音采集后,我们同样可以通过DFT变换得到声音的频谱图来进行滤波操作。这个不是我们这个系列的重点,我就不做详细讲解了。

3、图像的傅里叶变换基础

  在声学领域,我们可以把傅里叶变换的结果不同频段的能量的强度。在图像领域,我们可以将傅里叶变换可以看作是数学上的棱镜,将图像基于频率分解为不同的成分。当我们考虑光时,讨论它的光谱或频率谱。然而一般来说,将一幅图像的某些特定成分与其傅里叶变换直接联系联系起来通常是不可能的,但是关于傅里叶变换的频率成分和一幅图像的空间特性间的关系还是可以做出某些一般的叙述。例如,因为频率直接关系到空间变化率,因此直观的将傅里叶变换中的频率与图像中的亮度变换模式联系起来并不困难。二且图像与数字信号不同,其傅里叶变换结果并非是一维的单一曲线,而是一个二维的平面,类比关系如下:

  同理,二维的傅里叶变换还可拓展至三维:

  在光学方法做傅立叶变换一书中,给出了一个相关实现实验,如图,左侧是字符3的镂空平板,将其放透镜前面,用平行光照明,透镜焦平面上将显示其二维傅立叶变换。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部        // 计算谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

  程序运行结果如下(原图可以通过对上图进行截取获得):

 

下面来一步步看看,各个步骤的作用。如果不进行归一化处理的结果如下图所示,整个画面发白,根本看不到任何信息。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	//normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

如果不进行象限调整,结果如下,低频出现在图片的四个角(四角发亮),而通过调整之后将低频调整到了原点附近。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	//Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	//Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	//Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	//Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	////变换左上角和右下角象限
	//Mat tmp;
	//q0.copyTo(tmp);
	//q3.copyTo(q0);
	//tmp.copyTo(q3);

	////变换右上角和左下角象限
	//q1.copyTo(tmp);
	//q2.copyTo(q1);
	//tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

如果不进行尺度调整,可以发现对比度不高,仅仅能看到中心一个亮点,说明尺度调整后,能显示更多细节。

#include "stdafx.h"
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace std;
using namespace cv;

int main()
{
	Mat I = imread("2222.jpg", IMREAD_GRAYSCALE);       //读入图像灰度图

	//判断图像是否加载成功
	if (I.empty())
	{
		cout << "图像加载失败!" << endl;
		return -1;
	}
	else
		cout << "图像加载成功!" << endl << endl;

	Mat padded;                 //以0填充输入图像矩阵
	int m = getOptimalDFTSize(I.rows);
	int n = getOptimalDFTSize(I.cols);

	//填充输入图像I,输入矩阵为padded,上方和左方不做填充处理
	copyMakeBorder(I, padded, 0, m - I.rows, 0, n - I.cols, BORDER_CONSTANT, Scalar::all(0));

	Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);     //将planes融合合并成一个多通道数组complexI

	dft(complexI, complexI);        //进行傅里叶变换

	//计算幅值,转换到对数尺度(logarithmic scale)
	//=> log(1 + sqrt(Re(DFT(I))^2 + Im(DFT(I))^2))
	split(complexI, planes);        //planes[0] = Re(DFT(I),planes[1] = Im(DFT(I))
									//即planes[0]为实部,planes[1]为虚部        // 计算功率谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(planes[0], planes[1], planes[0]);     //planes[0] = magnitude
	Mat magI = planes[0];

	magI += Scalar::all(1);
	//log(magI, magI);                //转换到对数尺度(logarithmic scale)

	//如果有奇数行或列,则对频谱进行裁剪
	magI = magI(Rect(0, 0, magI.cols&-2, magI.rows&-2));

	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magI.cols / 2;
	int cy = magI.rows / 2;

	Mat q0(magI, Rect(0, 0, cx, cy));       //左上角图像划定ROI区域
	Mat q1(magI, Rect(cx, 0, cx, cy));      //右上角图像
	Mat q2(magI, Rect(0, cy, cx, cy));      //左下角图像
	Mat q3(magI, Rect(cx, cy, cx, cy));     //右下角图像

	//变换左上角和右下角象限
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	//变换右上角和左下角象限
	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);

	//归一化处理,用0-1之间的浮点数将矩阵变换为可视的图像格式
	normalize(magI, magI, 0, 1, CV_MINMAX);

	imshow("输入图像", I);
	imshow("频谱图", magI);
	waitKey(0);

	return 0;
}

4、傅里叶反变换复原图像

   通过傅里叶变换将图像转换到频率域之后,我们可以通过滤波器操作对图像进行噪声滤波操作,关于滤波器的操作将在下一篇文章中讲到,这里不再赘述,经过滤波操作后的图像需要通过傅里叶反变换来得到傅里叶反变换的结果,OpenCV提供了傅里叶反变换的程序,下面是一个详细示例,可以帮助你理解相关API接口及其使用方法:

#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
	//Mat input = imread(p[1], CV_LOAD_IMAGE_GRAYSCALE);//以灰度图像的方式读入图片
		//如果不知到怎么传入p[1]。可以改为
	Mat input = imread("2222.jpg", 0);
	imshow("input", input);//显示原图
	int w = getOptimalDFTSize(input.cols);
	int h = getOptimalDFTSize(input.rows);//获取最佳尺寸,快速傅立叶变换要求尺寸为2的n次方
	Mat padded;
	copyMakeBorder(input, padded, 0, h - input.rows, 0, w - input.cols, BORDER_CONSTANT, Scalar::all(0));//填充图像保存到padded中
	Mat plane[] = { Mat_<float>(padded),Mat::zeros(padded.size(),CV_32F) };//创建通道
	Mat complexIm;
	merge(plane, 2, complexIm);//合并通道
	dft(complexIm, complexIm);//进行傅立叶变换,结果保存在自身
	split(complexIm, plane);//分离通道     // 计算谱 Mag = sqrt(Re^2 + Im^2)
	magnitude(plane[0], plane[1], plane[0]);//获取幅度图像,0通道为实数通道,1为虚数,因为二维傅立叶变换结果是复数
	int cx = padded.cols / 2; int cy = padded.rows / 2;//一下的操作是移动图像,左上与右下交换位置,右上与左下交换位置
	Mat temp;
	Mat part1(plane[0], Rect(0, 0, cx, cy));
	Mat part2(plane[0], Rect(cx, 0, cx, cy));
	Mat part3(plane[0], Rect(0, cy, cx, cy));
	Mat part4(plane[0], Rect(cx, cy, cx, cy));

	part1.copyTo(temp);
	part4.copyTo(part1);
	temp.copyTo(part4);

	part2.copyTo(temp);
	part3.copyTo(part2);
	temp.copyTo(part3);
	//*******************************************************************

		//Mat _complexim(complexIm,Rect(padded.cols/4,padded.rows/4,padded.cols/2,padded.rows/2));
		//opyMakeBorder(_complexim,_complexim,padded.rows/4,padded.rows/4,padded.cols/4,padded.cols/4,BORDER_CONSTANT,Scalar::all(0.75));
	Mat _complexim;
	complexIm.copyTo(_complexim);//把变换结果复制一份,进行逆变换,也就是恢复原图
	Mat iDft[] = { Mat::zeros(plane[0].size(),CV_32F),Mat::zeros(plane[0].size(),CV_32F) };//创建两个通道,类型为float,大小为填充后的尺寸
	idft(_complexim, _complexim);//傅立叶逆变换
	split(_complexim, iDft);//结果貌似也是复数
	magnitude(iDft[0], iDft[1], iDft[0]);//分离通道,主要获取0通道
	normalize(iDft[0], iDft[0], 1, 0, CV_MINMAX);//归一化处理,float类型的显示范围为0-1,大于1为白色,小于0为黑色
	imshow("idft", iDft[0]);//显示逆变换
//*******************************************************************
	plane[0] += Scalar::all(1);//傅立叶变换后的图片不好分析,进行对数处理,结果比较好看
	log(plane[0], plane[0]);
	normalize(plane[0], plane[0], 1, 0, CV_MINMAX);

	imshow("dft", plane[0]);
	waitKey(0);
	return 0;
}

  

5、傅里叶功率谱和相位谱对图像

  我们之前都是谈到的傅里叶谱及对傅里叶谱进行操作,前面我们也讲到了,傅里叶谱可以分解为功率谱和相位谱,这里通过实验讲解一下功率谱和相位谱对应整个图像的信息。

#include "stdafx.h"
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

//将幅度归一,相角保持不变
void one_amplitude(Mat &complex_r, Mat &complex_i, Mat &dst)
{

	Mat temp[] = { Mat::zeros(complex_r.size(),CV_32FC1), Mat::zeros(complex_i.size(),CV_32FC1) };
	float realv=0.0, imaginv=0.0;
	for (int i = 0;  i < complex_r.rows;  i++) {
		for (int j = 0;  j< complex_r.cols; j++) {
			//cout << complex_r.at<float>(i, j) << endl;
			realv = complex_r.at<float>(i, j);
			imaginv = complex_i.at<float>(i, j);
			float distance = sqrt(realv*realv + imaginv * imaginv);
			float a = realv / distance;
			float b = imaginv / distance;
			temp[0].at<float>(i, j) = a;
			temp[1].at<float>(i, j) = b;
		}
	}
	merge(temp, 2, dst);//多通道合成一个通道
}

//将相角归一,幅值保持不变
void one_angel(Mat &complex_r, Mat &complex_i, Mat &dst)
{

	Mat temp[] = { Mat::zeros(complex_r.size(),CV_32FC1), Mat::zeros(complex_i.size(),CV_32FC1) };
	float realv = 0.0, imaginv = 0.0;
	for (int i = 0; i < complex_r.rows; i++) {
		for (int j = 0; j < complex_r.cols; j++) {
			realv = complex_r.at<float>(i, j);
			imaginv = complex_i.at<float>(i, j);
			float distance = sqrt(realv*realv + imaginv * imaginv);
			temp[0].at<float>(i, j) = distance / sqrt(2);
			temp[1].at<float>(i, j) = distance / sqrt(2);
		}
	}
	merge(temp, 2, dst);
}

//使用1的幅值和2的相位合并
void mixed_amplitude_with_phase(Mat &real1, Mat &imag1, Mat &real2, Mat &imag2, Mat &dst)
{
	if (real1.size() != real2.size()) {
		std::cerr << "image don‘t ==" << std::endl;
		return;
	}
	Mat temp[] = { Mat::zeros(real1.size(),CV_32FC1), Mat::zeros(real1.size(),CV_32FC1) };
	float realv1 = 0.0, imaginv1 = 0.0, realv2 = 0.0, imaginv2 = 0.0;
	for (int i = 0; i < real1.rows; i++) {
		for (int j = 0; j < real1.cols; j++) {
			realv1 = real1.at<float>(i, j);
			imaginv1 = imag1.at<float>(i, j);
			realv2 = real2.at<float>(i, j);
			imaginv2 = imag2.at<float>(i, j);
			float distance1 = sqrt(realv1*realv1 + imaginv1 * imaginv1);
			float distance2 = sqrt(realv2*realv2 + imaginv2 * imaginv2);
			temp[0].at<float>(i, j) = (realv2*distance1) / distance2;
			temp[1].at<float>(i, j) = (imaginv2*distance1) / distance2;
		}
	}
	merge(temp, 2, dst);
}

//使用1的相位和2的幅值合并
void mixed_phase_with_amplitude(Mat &real1, Mat &imag1, Mat &real2, Mat &imag2, Mat &dst)
{
	if (real1.size() != real2.size()) {
		std::cerr << "image don‘t ==" << std::endl;
		return;
	}
	Mat temp[] = { Mat::zeros(real1.size(),CV_32FC1), Mat::zeros(real1.size(),CV_32FC1) };
	float realv1 = 0.0, imaginv1 = 0.0, realv2 = 0.0, imaginv2 = 0.0;
	for (int i = 0; i < real1.rows; i++) {
		for (int j = 0; j < real1.cols; j++) {
			realv1 = real1.at<float>(i, j);
			imaginv1 = imag1.at<float>(i, j);
			realv2 = real2.at<float>(i, j);
			imaginv2 = imag2.at<float>(i, j);
			float distance1 = sqrt(realv1*realv1 + imaginv1 * imaginv1);
			float distance2 = sqrt(realv2*realv2 + imaginv2 * imaginv2);
			temp[0].at<float>(i, j) = (realv1*distance2) / distance1;
			temp[1].at<float>(i, j) = (imaginv1*distance2) / distance1;
		}
	}
	merge(temp, 2, dst);
}

cv::Mat fourior_inverser(Mat &_complexim)
{
	Mat dst;
	Mat iDft[] = { Mat::zeros(_complexim.size(),CV_32F),Mat::zeros(_complexim.size(),CV_32F) };//创建两个通道,类型为float,大小为填充后的尺寸
	idft(_complexim, _complexim);//傅立叶逆变换
	split(_complexim, iDft);//结果貌似也是复数
	magnitude(iDft[0], iDft[1], dst);//分离通道,主要获取0通道
//    dst += Scalar::all(1);                    // switch to logarithmic scale
//    log(dst, dst);
	//归一化处理,float类型的显示范围为0-255,255为白色,0为黑色
	normalize(dst, dst, 0, 255, NORM_MINMAX);
	dst.convertTo(dst, CV_8U);
	return dst;
}

void move_to_center(Mat &center_img)
{
	int cx = center_img.cols / 2;
	int cy = center_img.rows / 2;
	Mat q0(center_img, Rect(0, 0, cx, cy));
	Mat q1(center_img, Rect(cx, 0, cx, cy));
	Mat q2(center_img, Rect(0, cy, cx, cy));
	Mat q3(center_img, Rect(cx, cy, cx, cy));

	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	tmp.copyTo(q3);

	q1.copyTo(tmp);
	q2.copyTo(q1);
	tmp.copyTo(q2);
}

void fast_dft(cv::Mat &src_img, cv::Mat &real_img, cv::Mat &ima_img)
{
	src_img.convertTo(src_img, CV_32FC1);

	///////////////////////////////////////快速傅里叶变换/////////////////////////////////////////////////////
	int oph = getOptimalDFTSize(src_img.rows);
	int opw = getOptimalDFTSize(src_img.cols);
	Mat padded;
	copyMakeBorder(src_img, padded, 0, oph - src_img.rows, 0, opw - src_img.cols,
		BORDER_CONSTANT, Scalar::all(0));

	Mat temp[] = { padded, Mat::zeros(padded.size(),CV_32FC1) };
	Mat complexI;
	merge(temp, 2, complexI);
	dft(complexI, complexI);//傅里叶变换
	split(complexI, temp); //将傅里叶变换结果分为实部和虚部
	temp[0].copyTo(real_img);
	temp[1].copyTo(ima_img);
}

int main()
{

	Mat image = imread("2222.jpg", IMREAD_GRAYSCALE);

	imshow("woman_src", image);

	Mat woman_real, woman_imag;
	Mat sqrt_real, sqrt_imag;

	fast_dft(image, woman_real, woman_imag);//woman_real实部,woman_imag虚部
	fast_dft(image, sqrt_real, sqrt_imag);
	//
	Mat img_range, img_angle;
	one_amplitude(woman_real, woman_imag, img_range);//原图像的幅值归一化
	one_angel(woman_real, woman_imag, img_angle);//原图像的相位归一化
	//
	Mat woman_amp2sqrt_angle, sqrt_amp2woman_angle;
	mixed_amplitude_with_phase(woman_real, woman_imag,
		sqrt_real, sqrt_imag, woman_amp2sqrt_angle);
	mixed_phase_with_amplitude(woman_real, woman_imag,
		sqrt_real, sqrt_imag, sqrt_amp2woman_angle);
	Mat amplitude, angle, amplitude_src;
	magnitude(woman_real, woman_imag, amplitude); //计算原图像幅值
	phase(woman_real, woman_imag, angle);  //计算原图像相位
    //cartToPolar(temp[0], temp[1],amplitude, angle);

	move_to_center(amplitude);//幅值亮度集合到中心

	divide(amplitude, amplitude.cols*amplitude.rows, amplitude_src);
	imshow("amplitude_src", amplitude_src);

	amplitude += Scalar::all(1);// switch to logarithmic scale
	log(amplitude, amplitude);
	normalize(amplitude, amplitude, 0, 255, NORM_MINMAX); //归一化 方便显示,和实际数据没有关系
	amplitude.convertTo(amplitude, CV_8U);
	imshow("amplitude", amplitude);

	normalize(angle, angle, 0, 255, NORM_MINMAX); //归一化 方便显示,和实际数据没有关系
	angle.convertTo(angle, CV_8U);
	imshow("angle", angle);

	//*******************************************************************

	Mat inverse_amp = fourior_inverser(img_range);//傅里叶逆变换
	Mat inverse_angle = fourior_inverser(img_angle);
	Mat inverse_dst1 = fourior_inverser(woman_amp2sqrt_angle);
	move_to_center(inverse_angle);
	imshow("相位谱复原结果", inverse_amp);
	imshow("功率谱复原结果", inverse_angle);
	imshow("傅里叶谱图复原结果", inverse_dst1);
	waitKey(0);

	return 1;
}

  结果如下:从左到右依次是原图、傅里叶谱、功率谱、相位谱、相位谱傅里叶反变换结果、功率谱傅里叶反变换结果、傅里叶谱反变换结果

  

通过结果可以发现,相位谱决定着图像结构的位置信息,功率谱决定着图像结构的整体灰度分布的特性,如明暗、灰度变化趋势等,反应的是图像整体上各个方向上的频率分量的相对强度。

参考资料:

下面这个方波分解示意图用matlab怎么画?

如何理解傅里叶变换公式?

傅里叶频域,复数域,冲激函数,香农采样(不介绍公式-只介绍是啥)另一种思维

DFT

opencv学习(十五)之图像傅里叶变换dft

[数字图像处理]频域滤波(1)--基础与低通滤波器

CS425 Lab: Frequency Domain Processing

傅里叶分析与图像处理

二维傅里叶变换是怎么进行的?

c语言数字图像处理(六):二维离散傅里叶变换

【数字图像处理】傅里叶变换在图像处理中的应用

从菜鸟到完全学会二维傅立叶在图像处理算法中的应用【老司机教你学傅立叶】

快速傅里叶变换FFT的C程序代码实现

2DFT and 3DFT

EE261 - The Fourier Transform and its Applications

OpenCV C++使用傅里叶谱和相角重建图像

原文地址:https://www.cnblogs.com/noticeable/p/10908054.html

时间: 2024-08-02 17:00:15

14、频率域滤波基础——傅里叶变换计算及应用基础的相关文章

频率域滤波_滤波基础

这节来介绍一下频率域滤波. 在空间域内,我们是直接对像素进行操作以增强图像的有用信息.像高斯平滑,是取一个模板与图像进行卷积操作,得到处理后的图像.不同的变换域可以很方便的解决某种问题,例如空间域中值滤波是取一个区域的中值替代中间像素的灰度,可以很好的去除椒盐噪声例如下图中的(1)(2)(3).           (1)原图        (2)带有椒盐噪声的图像 (3)图像(2)复原之后的图像 (4)部分区域带有周期噪声 但不是所有的图像都可以在空间域进行图像的增强,有的时候都不知道用什么滤

openCV中的频率域变换

openCV中频率域增强的傅里叶变换已经比较成熟了,在他的官方tutorials文档里有一个完整的得到频谱图的例子,如下: #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/highgui/highgui.hpp" #include <iostream> int main(int argc, char **

Python下opencv使用笔记(十)(图像频域滤波与傅里叶变换)

前面曾经介绍过空间域滤波,空间域滤波就是用各种模板直接与图像进行卷积运算,实现对图像的处理,这种方法直接对图像空间操作,操作简单,所以也是空间域滤波. 频域滤波说到底最终可能是和空间域滤波实现相同的功能,比如实现图像的轮廓提取,在空间域滤波中我们使用一个拉普拉斯模板就可以提取,而在频域内,我们使用一个高通滤波模板(因为轮廓在频域内属于高频信号),可以实现轮廓的提取,后面也会把拉普拉斯模板频域化,会发现拉普拉斯其实在频域来讲就是一个高通滤波器. 既然是频域滤波就涉及到把图像首先变到频域内,那么把图

在频率域中直接生成滤波器

除了之前说的从空间滤波器中获得频率域滤波器,还可以从频率域中直接生成滤波器,这些滤波器被规定为距滤波器中心点的距离不同的函数.可以创建一个用于实现频率滤波器的网格数组,最主要的是需要计算任何点到频率矩形中一个指定点的距离函数,FFT(快速傅里叶)算法是假设变换的原点位于频率矩形的左上角,因此需要将原点平移到频率矩形的中心,用fftshift.网格数组如下: %(频域滤波函数) 提供了距离计算及其所需的网格数组 function [U,V] = dftuv(M,N) u=0:(M-1); v=0:

从图像空间域滤波到频域滤波

频域滤波的快速实现是工程领域的里程碑.频域滤波最让工程师兴奋的原因来自于这个公式: f(x)*g(y)<-->F(u)G(v) 这说明空间域中的复杂的卷积算子,变换到频域中就成了简单的乘法,这样不仅计算简单,而且工程上易于实现.在FFT和快速DCT(余弦变换)的数字实现之前,频域变换的计算是很头疼的事情,在计算效率上并不比普通卷积快多少:在FFT和快读DCT实现之后,频域信号处理几乎无处不在. 空间域滤波的算子可以变成频域滤波算子.假设当前图像尺寸为512x512,滤波算子大小为3x3,显然直

频率域去噪基本实现思想

1. 频率域去噪基本实现思想:首先将原始图像通过一些积分变换,将其变换到频率域,接着再通过频率域对其进行操作,得到的结果再反变换到空间域中,进而使图像得到增强.根据傅里叶频谱的特性可得到,图像的平均灰度级对应于频率为0成分,当从傅里叶变换的原点离开时,图像的慢变化分量对应着低频滤波,比如一幅图像中较平的区域:当再进一步离开原点时,较高的频率开始对应图像中变换越来越快的灰度级,它们反映了一幅图像中物体的边缘和灰度级突发改变和噪声部分的图像成分.频率域图像增强正是基于这种原理,通过对图像的傅里叶频谱

科学计算库Numpy基础操作

pycharm,python3.7,numpy版本1.15.1 2018年9月11日04:23:06 """ 科学计算库Numpy基础操作 时间:2018\9\11 0011 """ import numpy print("""\n------以矩阵的方式读取数据------\n ------------genfromtxt函数('文件路径',delimiter = '分隔符',dtype = 读取方式)------

word页面设置问题。通过域设置首页不计算页面的自定义页码格式

手工设置如图所示页面 通过域设置首页不计算页面的自定义页码格式 编辑页脚 在此位置  按  ALT  +F9 显示出域格式公式 PAGE  表示插入当前页码 SectionPages  表示“插入本节的总页数”

Java web基础总结四之—— Servlet基础

Java web基础总结四之-- Servlet基础 一.什么是Servlet? 通过名字就能看出来,Servlet 就是在服务器上运行的小程序.Servlet是sun公司(现在已经属于oracle了)实现的一门用于开发动态java web资源的技术.Sun公司在其API中提供了一个servlet接口,如果你想开发一个动态的java web资源,需要完成以下2个步骤:编写一个Java类,实现servlet接口.把开发好的Java类部署到web服务器中. Servlet接口已经有了两个默认的实现类