Processing math: 100%

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

1、理解傅里叶变换

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

1、什么是傅里叶级数

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

  • 在一个周期内只有有限个不连续点;
  • 爱一个周期内只有有限个极大和极小值
  • 积分T2T2|f(t)|dt存在,则f(t)可展开为如下傅里叶级数

f(x)=a02+k=1(akcoskx+bksinkx)

 其中

an=1πππf(x)cosnxdx(n=0,1,2,3,)bn=1πππf(x)sinnxdx(n=1,2,3,)

 式中的w=2πT称为角频率。

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

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

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

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

  • 首先,根据周期函数的定义,常数函数是周期函数,周期为任意实数。所以,分解里面得有一个常数项。
  • 任意函数可以分解和奇偶函数之和,f(x)=f(x)+f(x)2+f(x)f(x)2=f even +f odd ,由正余弦函数分别为奇偶函数,且其为周期性函数,而且还具有正交的特性,所以分解里面需要由正余弦函数sin和cos

  • 如下图所示,对于sin(nx),nN,虽然最小周期是πn,但是其周期中都有一个周期为2π,则对于周期为T的函数,可以知道角频率w=2πT,将这些函数进行加减(这里用级数表示),就保证了得到的函数的周期也为 T。
  • 又因为正余弦函数的值域只有[-1,1],而需要表示的函数的至于变化显然不止[-1,1],为了适应函数的值域,正余弦级数必然会有各自不同的系数an和bn

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

f(x)=a02+n=1(ancos(2πnTx)+bnsin(2πnTx)),a02R

 

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

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

f(x)=a02+n=1(ancos(2πnTx)+bnsin(2πnTx)),a02R

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

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

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

ππf(x)dx=ππa02dx+n=1(anππcosnxdx+bnππsinnxdx)=a0π

 于是有:

a0=1πππf(x)dx

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

ππf(x)cosmxdx=ππa02cosmxdx+n=1(anππcosnxcosnxdx+bnππcosmxsinnxdx)=ππancos2nx=anπ

所以我们可以得出

an=1πππf(x)cosnxdx

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

bn=1πππf(x)sinnxdx

 

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

 结合欧拉公式

eit=cos(t)+isin(t)

我们有

sinθ=eiθeiθ2icosθ=eiθ+eiθ2

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

f(x)=n=cnei2πnxT

其中

cn=1Tx0+Tx0f(x)ei2πnxTdx

 

 

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

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

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

f(t)=ω=aωejwtdtaw=Δω2πT2T2f(t)ejωtdt

两式合并有:

f(t)=ω=[Δω2πT2T2f(t)ejωtdt]ejwt=12πω=[T2T2f(t)ejωtdt]ejwtΔω

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

f(t)=12π[f(t)ejωtdt]ejωtdt

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

若令

F(ω)=f(t)ejωtdt

f(t)=12πF(ω)ejωtdt

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

 

 

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

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

˜f(t)=f(t)sΔT(t)=n=f(t)σ(tnΔT)

  用图像表示为:

  由图可知,序列中的任意取样值fk可用如下式子表示:

fk=f(t)δ(tkΔT)dt=f(kΔT)

 

   取样后的函数的傅里叶变换˜F(μ)

˜F(μ)=J{˜f(t)}=J{f(t)SΔT(t)}=F(μ)S(μ)

对于脉冲串SΔT(t)是周期为ΔT的周期函数,则由傅里叶级数定理有

sΔT(t)=n=cnej2πnΔTt

式中:

cn=1ΔTΔT/2ΔT/2sΔT(t)ej2πnΔTtdt

由冲激串图可以看出

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

cn=1ΔTΔT/2ΔT/2δ(t)ej2πnΔTdt

由冲激的采样特性

f(t)δ(t)dt=f(0)

cn=1ΔTej2πnΔT0=1ΔTe0=1ΔT

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

sΔT(t)=1ΔTn=ej2πnΔTt

 由傅里叶变换的指数形式可以知道冲激δ(tt0)的傅里叶变换为ej2πμt0,所以S(μ)可以化简为:

S(μ)={sΔT(t)}={1ΔTn=ej2πnΔTt}=1ΔTS{n=ej2πnΔTt}=1ΔTn=δ(μnΔT)

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

˜F(μ)=F(μ)S(μ)=F(τ)S(μτ)dτ=1ΔTF(τ)n=δ(μτnΔT)dτ=1ΔTn=F(τ)δ(μτnΔT)dτ=1ΔTn=F(μnΔT)

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

   

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

˜F(μ)=˜f(t)ej2πμtdt

将最开始我们给出的˜f(t)函数带入有

˜F(μ)=˜f(t)ej2πμtdt=n=f(t)δ(tnΔT)ej2πμtdt=n=f(t)δ(tnΔT)ej2πμtdt=n=fnej2πμnΔT

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

  假设我们想在周期μ=0μ=1/ΔT得到˜F(μ)的M个等间距样本。即:

μ=mMΔT,m=0,1,2,,M1

  将其带入˜F(μ),则有:

Fm=M1mfnej2πmn/M,m=0,1,2,,M1

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

  反之,给定{Fm},我们可以用傅里叶反变换复原样本集{fn}

fn=1MM1m=0Fmej2πmn/M,n=0,1,2,,M1

6、二维傅里叶变换

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

F(u,v)=f(x,y)ej2π(μx+vy)dxdy

 反变换为:

f(x,y)=F(u,v)ej2π(μx+vy)dudv

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

F(u,v)=M1x=0N1y=0f(x,y)ej2π(ux/M+vy/N)

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

f(x,y)=1MNM1u=0N1v=0F(u,v)ej2π(ux/M+vy/N)

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

F(u,v)=|F(u,v)|ejϕ(u,v)

其中,幅度

|F(u,v)|=[R2(u,v)+I2(u,v)]1/2

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

ϕ(u,v)=arctan[I(u,v)R(u,v)]

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

最后,功率谱定义为:

F(u,v)=|F(u,v)|2=R2(u,v)+I2(u,v)

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

 

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

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

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

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

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

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

  二维傅立叶变换

 

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

 平面波求和

 

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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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]为虚部<br>        // 计算谱 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;
}

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

                

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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;
}

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#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]为虚部<br>        // 计算功率谱 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接口及其使用方法:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#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);//分离通道<br>     // 计算谱 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、傅里叶功率谱和相位谱对图像

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

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#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++使用傅里叶谱和相角重建图像

posted @   noticeable  阅读(6687)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
· Manus爆火,是硬核还是营销?
点击右上角即可分享
微信分享提示