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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/sac761/article/details/76525188

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

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

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

  好了,傅立叶数学公式看一看这个就好了,其推导,原理再在次不赘述,实在想弄明白原理的同学,可以看一看花生油工人的知乎专栏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++)
				{
					//矩阵中数据保存格式为uchar
					const 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天,这不得跑一年多吗?我这破电脑有这么破?现在终于是体会到了快速傅立叶变换的重要性!难怪,matlabfft全部都封装起来,不给看,其商业价值也是巨大的。

结合了这位博主的博客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++)
		{
			//矩阵中数据保存格式为uchar
			const 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,如今再次面对,我的内心是那么波澜不惊,甚至还有点想笑。

猜你喜欢

转载自blog.csdn.net/sac761/article/details/76525188
今日推荐