来看此文的人毕竟是做编程之用,大部分也是学过傅立叶,不过尚不知如何对其运用到程序中,本文对傅立叶数学知识只做点醒,轻轻一点,但是做够让你理解其数学奥义,本文的重点以及目的是让读者从零起步学会图像处理中傅立叶变换的应用与编程。

一,菜鸡的修炼——洞察原理

图像是一个离散二维矩阵,只需用到二维离散傅立叶变换:

好了,傅立叶数学公式看一看这个就好了,其推导,原理再在次不赘述,实在想弄明白原理的同学,可以看一看花生油工人的知乎专栏https://zhuanlan.zhihu.com/p/19763358。在图像处理中的傅立叶应用中弄明白这个问题就足够使了:

从时域到频域的角度来讲:一维信号的傅立叶变换大家都能明白,但是图像是一个二维的东西,变换出来的东西如何表示原始图像的频域呢?

从纯属学函数的角度来理解这个问题:图像的傅立叶变换后所得到的每一个像素点是原图像所有像素作用的结果,每一个人打了一拳,出来的样子爹妈都不认识了,根本不知道是谁打的,所以从变换后的图你是看不出一一对应关系的。

不信你看:

I=imread('lena-gray.png'); %读入图像
I=rgb2gray(I); %去色
F=fft2(I); %FFT
subplot(1,2,1),imshow(I),title('原图像');
subplot(1,2,2),imshow(F,[]),title('二维傅立叶变换');

你可能很难想象右边这张图是一张美女图,但是它确确实实是一张美女图,这两张图在2个不同的次元中是完全等价的,从频率的角度讲,右边的杂草图完全的记录了左边这张图的全部信息。你也开始明白了在某一个平行世界里。混沌的一片什么也看不清,但是却充满了美女和生机万物。

我们再来看看二维傅里叶变换的每一个像素是做了什么样的坏事,啥?这么简单的事你又不想做?好吧,累。。。:

i=[255;255];

f=fft2(i);

对一个1X2的矩阵做一个二维傅立叶变换,结合公式⑴和欧拉公式,已知M=1,N=2,可得:

所以矩阵[255;255]经过二维傅立叶变换后得到的是[510;0],由上面的分解步骤可以看到,矩阵中所有元素都将会参与输出像素的计算。好,明白了这个我上面做的蠢事就值了。

于是,蠢事一件接一件~直到让你懂:

用画图软件做了一个2个像素的空白图片,做二维傅立叶变换:

现在明白那个lena美女图的二维傅立叶变换为啥是一个类似二维码黑白块的杂草图了吧!

二,菜鸡的顿悟——拭去假相

咦~不瞒你说,这位博主,水平也就这样,我已看出第一章中不少错误,第一:lena美女的傅立叶像并非毫无规律,第二:傅立叶变换就是每人打一拳的说法实在是蠢,明明就有许多原图像素参与计算的时候系数为0,打了假拳。

唉~高手就是高手啊,这么快就看出门道了,行,看你悟性极高,今晚三更,来我房里,嘿嘿嘿嘿......

上秘籍(代码):

I=imread('lena-gray.png'); %读入图像
I=rgb2gray(I); %去色
F=fft2(I); %FFT
F=fftshift(F); %FFT频谱平移
subplot(1,2,1),imshow(I),title('原图像');
subplot(1,2,2),imshow(F,[]),title('二维傅立叶变换');

fftshift的作用是把矩阵分成4个象项,交换对角的象项:

得到的傅立叶变换结果是:

假相一层层的拂去,等等,我似乎冥冥中想到了某位老师对我说过对数可以放大差异:

I=imread('lena-gray.png'); %读入图像
I=rgb2gray(I);
F=fft2(I); %FFT
F=fftshift(F); %FFT频谱平移
F=log(F+1); %频谱对数变换,取对数放大波动
subplot(1,2,1),imshow(I),title('原图像');
subplot(1,2,2),imshow(F,[]),title('二维傅立叶变换');

得到的结果:

我似乎看到了宇宙中的几道极光,它是那么的美丽,它一定蕴藏了宇宙中最神秘的奥义!我一定要破解它!

三,菜鸡的蜕变——成为高手

经过前面的傅立叶菜鸡训练营,现在就要进入高手行列了,没错就是这么顺利,相信在高手阶段,你定可以拭去眼前浮华,洞察世间真相,无欲与菜鸡争辩,一切了然于心。

看着这频谱图中的极光这么美,忍不住要做一些手脚:

I=imread('lena.png'); %读入图像
I=rgb2gray(I);
F=fft2(I); %FFT
F=fftshift(F); %FFT频谱平移
J = ifftshift(F);
J(abs(J)>70000)=0;%去掉频谱中的亮区域(低频部分)
K = ifft2(J); %IFFT
subplot(2,2,1),imshow(I),title('原图像');
subplot(2,2,2),imshow(log(F),[]),title('二维傅立叶变换');
subplot(2,2,3),imshow(fftshift(log(J)),[]),title('修改频谱');
subplot(2,2,4),imshow(K,[]),title('二维傅立叶逆变换');

发现去掉频谱中比较亮的地方,反变换出来的是lena的轮廓。再来:

I=imread('lena.png'); %读入图像
I=rgb2gray(I);
F=fft2(I); %FFT
F=fftshift(F); %FFT频谱平移
J = ifftshift(F);
J(abs(J)<70000)=0;%去掉频谱中的暗区域(高频部分)
K = ifft2(J); %IFFT
subplot(2,2,1),imshow(I),title('原图像');
subplot(2,2,2),imshow(log(F),[]),title('二维傅立叶变换');
subplot(2,2,3),imshow(fftshift(log(J)),[]),title('修改频谱');
subplot(2,2,4),imshow(K,[]),title('二维傅立叶逆变换');

发现去掉中心亮处之外的点,反变换出来的是一个朦胧的lena。

我们发现,频谱图中很亮的中心区域对应着图像的平坦像素,比较暗的区域对应图像的轮廓。这也是有科学根据的,傅立叶变换得到信号的频域,原图像中变化明显的轮廓区域分解出来的频谱中就包含很多高频分量,这些分量分布在频谱的中心轴附近,做了频谱对角平移后就处于频谱远离中心轴的位置了,看上去是比较暗的部分。相应的图像中的平坦区域信号比较弱,对应的就是低频分量,其幅度比较大,在频谱上就是比较亮的部分。

所以我们滤除一些频谱的中心区域较亮的部分,就是做了一个高通滤波器,得到图像轮廓;

去除频谱中心区域以外较暗的区域就是做了一个低通滤波器,图像变得平滑模糊。

要想去除图像中的噪点,就要去除中心区域和十字以外的孤鹜的暗点。

以上就是图像处理中傅立叶变换所能做的事情,合上秘籍,内心是那么平静,这套拳法,我似乎可以用来走上图像处理这座山,是时候向里面的boss领教领教了。

四,用C++干掉傅立叶——成为绝世高手

有的人在学到秘籍的第三篇,觉得自己的战车已经造出,在博主的唆使下开着奇瑞向图像处理的秋名山扬长而去。

好了,留下的人,咱们继续上课,坚持到底,你一定可以开着属于自己的五菱宏光上路的!

经过研究,作为老司机似乎简简单单就开发出了二维傅立叶变换的C++版:

/*
* Copyright (c) 2017
*
* Author: SYJ
*/
#include <opencv2/opencv.hpp>
#include <math.h>
#include <complex>
using namespace std;
using namespace cv;
#define PI 3.14159265358979323846 complex<float> fun_e_i2pi(float x);
void fourier_2d(Mat f);int fmain()
{Mat image=imread("../lena.png",0);fourier_2d(image);cout<<"SUCCESS!"<<endl;waitKey(0);return 0;
}
/*
*fun_e_i2pi(float x)
*返回复数 e^-j2PIx
*/
complex<float> fun_e_i2pi(float x)
{complex<float> y(cos(2*PI*x),-sin(2*PI*x));return y;
}/*
*fourier_2d(Mat f)
*对数据矩阵f进行二维傅立叶变换
*并将傅立叶频谱保存
*/
void fourier_2d(Mat f)
{Mat F;int M=f.cols;int N=f.rows;f.copyTo( F);for (int u=0;u<=M-1;u++){for (int v=0;v<=N-1;v++){complex<float> tmp_p(0,0);for (int x=0;x<=M-1;x++){for (int y=0;y<=N-1;y++){//矩阵中数据保存格式为ucharconst uchar* data_str=f.datastart+x+y*M;//定位数据int tmp_i=0;tmp_i=data_str[0];//取出数据,格式为整型float tmp_s=(float)tmp_i;float tmp_para=u*x/(M*1.0)+v*y/(N*1.0);//自然指数参数complex<float> tmp_e(0,0);tmp_e=fun_e_i2pi(tmp_para);//计算e^-j2PI(ux/M+vy/N)tmp_p+=tmp_s*tmp_e;//相乘并累积cout<<u<<","<<v<<","<<x<<","<<y<<endl;}}int data=tmp_p.real();//取实部F.col(u).row(v)=data;//将结果存入矩阵}}cv::imwrite("../lena_fourier.png",F);
}

人格保证,以上代码绝对是最原始,原汁原味,绝对没问题的二维傅里叶变换代码,亲测可用,看着有点小激动!于是用这个程序跑哪个512X512像素的lena图,一个晚上也只循环出来一圈,要跑512圈,等等 待我掐指一算,word天,这不得跑一年多吗?我这破电脑有这么破?现在终于是体会到了快速傅立叶变换的重要性!难怪,matlab中fft全部都封装起来,不给看,其商业价值也是巨大的。

结合了这位博主的博客http://www.elecfans.com/engineer/blog/20161008440131.html

,最终写出来快速傅立叶变换的C++代码:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <opencv2/opencv.hpp>
#include <complex>
using namespace std;
using namespace cv;#define PI 3.14159265358979323846
#define intsize sizeof(int)
#define complexsize sizeof(complex<float>)Mat image;
int *a,*b;
int nLen,mLen;
complex<float> *A,*A_In;void reverse(int);
void fft(int);
void saveResult();
void readImg();int main()
{int i,j;image=imread("E://LEARN//computer vision//code_c//fourier//lena.png",0);readImg();//一维A = (complex<float> *)malloc(complexsize*nLen);reverse(nLen);for(i=0; i<mLen; i++){for(j=0; j<nLen; j++){A[j] = A_In[i*nLen+b[j]];}fft(nLen);printf("%d\n",i);for(j=0; j<nLen; j++){A_In[i*nLen+j] = A[j];}}free(A);//二维A = (complex<float> *)malloc(complexsize*mLen);reverse(mLen);for(i=0; i<nLen; i++){for(j=0; j<mLen; j++){A[j] = A_In[b[j]*nLen+i];}printf("_%d\n",i);fft(mLen);for(j=0; j<mLen; j++){A_In[j*nLen+i] = A[j];}}free(A);saveResult();printf("success!\n");return 0;
}/*
*readImg()
*读取图像提取数据
*/
void readImg()
{int i,j;nLen=image.cols;mLen=image.rows;A_In = (complex<float> *)malloc(complexsize*nLen*mLen);for (int x=0;x<=mLen-1;x++){for (int y=0;y<=nLen-1;y++){//矩阵中数据保存格式为ucharconst uchar* data_str=image.datastart+x+y*mLen;//定位数据int tmp_i=data_str[0];//取出数据,格式为整型A_In[x*nLen+y]=tmp_i*1.0;}}}/*
*fourier_2d(Mat f)
*对数据矩阵f进行二维傅立叶变换
*/
void fft(int fft_nLen)
{int dist,p;complex<float> B,*W;int fft_M=log((float)fft_nLen)/log(2.0);W = (complex<float> *)malloc(complexsize*fft_nLen/2);for(int lev=1; lev<=fft_M; lev++)/*共log2N级*/{//一级蝶形运算dist = (int)pow(2.0,lev-1);/*计算每一组蝶形的个数*/for(int t=0; t<dist; t++)/*每dist+1个蝶形是一组*/{/* 计算每一组蝶形的旋转因子WN[k].real=cos(2*PI/N*k);WN[k].img=-sin(2*PI/N*k);*/p = t*(int)pow(2.0,fft_M-lev);complex<float> tmp((float)cos(2*PI*p/fft_nLen),(float)(-1*sin(2*PI*p/fft_nLen)));/*旋转因子*/W[p] = tmp;for(int i=t; i<fft_nLen; i+=(int)pow(2.0,lev))/*计算每一级的所有组*/{B = A[i]+A[i+dist]*W[p];complex<float> tmp2(A[i].real()-(A[i+dist]*W[p]).real(),A[i].imag()-(A[i+dist]*W[p]).imag());A[i+dist] = tmp2;A[i] = B;}}}free(W);
}/*
*saveResult()
*将傅立叶频谱保存
*/
void saveResult()
{Mat F;image.copyTo( F);int i,j;for(i=0; i<mLen; i++){for(j=0; j<nLen; j++){if(A_In[i*nLen+j].imag() < 0){F.col(i).row(j)=A_In[i*nLen+j].real();//将结果存入矩阵}else{F.col(i).row(j)=A_In[i*nLen+j].real();//将结果存入矩阵}}}cv::imwrite("E://LEARN//computer vision//code_c//fourier//lena_fft.png",F);free(A_In);
}/*
*reverse(int len)
*码位倒序函数
*/
void reverse(int len)
{int i,j;int M=log((float)len)/log(2.0);a = (int *)malloc(intsize*M);b = (int *)malloc(intsize*len);for(i=0; i<M; i++){a[i] = 0;}b[0] = 0;for(i=1; i<len; i++){j = 0;while(a[j] != 0){a[j] = 0;j++;}a[j] = 1;b[i] = 0;for(j=0; j<M; j++){b[i] = b[i]+a[j]*(int)pow(2.0,M-1-j);}}
}

出来的结果完全正确:

好了,教程到这里就结束了,曾经让我害怕过的傅立叶大boss,如今再次面对,我的内心是那么波澜不惊,甚至还有点想笑。

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

  1. java---编写一个方法,返回一个int型的二维数组,数组中的元素通过解析字符串参数获得。

    题目: 编写一个方法,返回一个int型的二维数组,数组中的元素通过解析字符串参数获得,字符串如下"1,2:3,4,5:6,7"对应的数组为: d[0][0]=1 d[0][1]=2 ...

  2. 13.请编一个函数void fun(int tt[M][N],int pp[N]),tt指向一个M行N列的二维数组,求出二维数组每列中最小元素,并依次放入pp所指一维数组中。

    13.请编一个函数void fun(int tt[M][N],int pp[N]),tt指向一个M行N列的二维数组,求出二维数组每列中最小元素,并依次放入pp所指一维数组中.二维数组中的数已在主函数中 ...

  3. 请编写一个函数void fun(int tt[M][N],int pp[N]),tt指向一个M行N列的二维数组,求出二维数组每列中最小元素,并依次放入pp所指一维数组中。

    #include <iostream> #include<iomanip> using namespace std; #define M 3 #define N 4 /*求出二 ...

  4. python:实现9×9二维数组数独算法(附完整源码)

    python:实现9×9二维数组数独算法 from __future__ import annotationsMatrix = list[list[int]]# assigning initial v ...

  5. c语言从txt中读取二维坐标,C语言二维数组在文件中读写的问题,谢谢

    已结贴√ 问题点数:10 回复次数:4 C语言二维数组在文件中读写的问题,谢谢 这是一个用二位数组写的五子棋小游戏的代码,我的思路是通过键盘输入坐标显示棋子,当输0 0时保存棋盘并结束游戏,下一次进入 ...

  6. C语言编程>第七周 ⑧ 请编一个函数void fun(int a[M][N],int b[N]),c指向一个M行N列的二维数组,求出二维数组每列中最大元素,并依次放入b所指一维数组中。

    例题:请编一个函数void fun(int a[M][N],int b[N]),c指向一个M行N列的二维数组,求出二维数组每列中最大元素,并依次放入b所指一维数组中.二维数组中的数己在主函数中赋予. ...

  7. 【halcon 线扫相机二维码矫正算法】

    halcon 线扫相机畸变二维码矫正算法 线扫相机拍照畸变 1.二维码定位与裁剪 图像矫正 运行结果 总结 线扫相机拍照畸变 线扫相机拍摄图片分辨率较高,但是由于相机本身或者或者拍照目标的运动,容易造 ...

  8. 创建二维数组 以及 python中[0 ]* n与[0 for _ in range(n)]的区别与联系

    一.浅拷贝于深拷贝 关于浅拷贝于深拷贝:Python 的深拷贝和浅拷贝 直接赋值:其实就是对象的引用(别名). 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象. 深拷贝(deepcopy) ...

  9. 利用matlab实现DMD动态模态分解(在一维信号或二维流场矢量中的应用)

    利用matlab实现DMD动态模态分解(在一维信号或二维流场矢量中的应用) 0 前言 0.1 特征根的计算与含义 1 DMD的基本思路 2 一维DMD算法 3 二维DMD算法 4 总结 (2020年9 ...

  10. 《MATLAB智能算法30个案例》:第23章 基于蚁群算法的二维路径规划算法

    <MATLAB智能算法30个案例>:第23章 基于蚁群算法的二维路径规划算法 1. 前言 2. MATLAB 仿真示例 3. 小结 1. 前言 <MATLAB智能算法30个案例分析& ...

最新文章

  1. 派森编程软件python-零基础学习Python需要用什么开发工具?
  2. 二叉树的按层打印和ZigZag打印
  3. mybatisplus修改单个属性_Mybatis Plus 中 参数传递的优化之路
  4. CSS text-indent 属性
  5. Ananagrams Uva 156
  6. formSelects使用
  7. make it a chorus笔记
  8. java外部工具配置_eclipse配置外部工具利用javah编译生成头文件
  9. 这家云提供商虽挫败勒索攻击,但仍需支付赎金
  10. 面向对象——类设计(七)
  11. Crackeme021
  12. java 电子签章_PDF开发+电子签章,如何实现真正地脱离硬件的无纸化办公体验(实战篇)?...
  13. lingo入门教程之二 --- 集合运用
  14. 数据库挖掘 概念 定义 什么是数据挖掘
  15. 计算三角形的周长和面积
  16. STM32C8T6+面板板+3只LED点亮流水灯
  17. Perl/Tk入门学习(上)
  18. 2019牛客暑期多校9H:Cutting Bamboos【主席树+二分】
  19. 如何破解加密ppt文档的密码
  20. append和extend的差别

热门文章

  1. 2022年全球与中国GPS天线模块市场现状及未来发展趋势
  2. hello Java 第一天的认识
  3. 蓝桥杯2014java_【图片】2014-2016蓝桥杯java本科B组省赛题_蓝桥杯吧_百度贴吧
  4. cause: duplicate entry: meta-inf/maven pom.xml
  5. umts是移动还是联通_移动网络类型umts是什么意思,umts是什么网络类型-
  6. 骨传导耳机靠谱吗?骨传导耳机是不是智商税?
  7. 使用sort(function(a,b){return a-b})对数组进行排序的原理
  8. 【多轮对话】任务型多轮对话数据集和采集方法
  9. 人民币大写转换 java_java人民币转大写中文
  10. html怎么添加样式,HTML添加样式三种办法