一.实验内容
用C++实现快速傅立叶变换FFT
二.实验目的:
更好的了解快速傅立叶变换的实质,用代码进行实现增强自己的编程能力。
三.实现思想
在实现过程中采用一行一行的处理进行傅立叶变化,在处理 的过程中采用W记号,由于计算机没有虚数这个基本的数据类型,所以用2个函数double CWvltDoc::W_image(int a, int b, int c)和double CWvltDoc::W_real(int a, int b ,int c)来返回W的虚部和实部,用2个数组*transient_real和*transient_image来记录再频域化的过程中一行中的原始数据频域化后的实部和与虚部和。在计算完一行像素值后,用该像素点的对应能量=transient_real[]2+transient_image[]2来修改原始数据的蓝色灰度值(后面用蓝色灰度值进行灰度化)。这样处理完一行中所有的像素点之后用可视化算子进行可视化
lpData[y*biAlign+3*i]=255*log10(1+fabs(lpData[y*biAlign+3*i]))/log10(1+max);
四.具体代码
voidCWvltDoc::OnFft()
{
LPBITMAPINFOHEADER lpBitmapInfoHeader =
(LPBITMAPINFOHEADER)(m_pBitmap+14);
LPBITMAPFILEHEADER lpBitmapFileHeader = (LPBITMAPFILEHEADER)m_pBitmap;
unsigned char *lpData = m_pBitmap + lpBitmapFileHeader->bfOffBits;
int x, y, cur; for (y = 0; y < (int)biHeight; y++) //处理第y行 { double *transient_real=new double[biWidth]; double *transient_image=new double[biWidth]; for(x=0;x<(int)biWidth; x++) //第y行第x个数 { unsigned long biHeight = lpBitmapInfoHeader->biHeight; unsigned long biWidth = lpBitmapInfoHeader->biWidth; unsigned long biAlign = ( biWidth*3+3) /4 *4; unsigned long bmSize = biHeight * biAlign; if (m_pTransfered == NULL) m_pTransfered = (unsigned char*)malloc(bmSize); if (m_pTransfered == NULL) return ; // TODO: Add your command handler code here
double real=0; double image=0; for(inti=0;i<(int)biWidth;i++) { real+=W_real(x,i,(int)biWidth)*double(lpData[y*biAlign+3*i]); image+=W_image(x,i,(int)biWidth)*double(lpData[y*biAlign+3*i]); lpData[y*biAlign+3*i]=sqrt(pow(transient_real[i],2)+pow(transient_i } for(inti=0;i<(int)biWidth; i++) //修改第y行的值 } transient_real[x]=real; transient_image[x]=image; mage[i],2));
} delete []transient_real; //释放内存 delete []transient_image;
for (y = 0; y < (int)biHeight; y++) //可视化算法 { lpData[y*biAlign+3*i]=255*log10(1+fabs(lpData[y*biAlign+3*i]))/logint max=0; for (x = 0; x < (int)biWidth; x++) //找到该行最大的像素值 { } for(inti=0;i<(int)biWidth;i++) cur = y*biAlign+3*x; if(fabs(lpData[cur])>max) max=fabs(lpData[cur]); 10(1+max);//可视化算子
}
for (y = 0; y < (int)biHeight; y++) //进行灰度化
{ for (x = 0; x < (int)biWidth; x++) { cur = y*biAlign+3*x; //current pixel m_pTransfered[cur] = lpData[cur]; m_pTransfered[cur+1] = lpData[cur];
} } } m_pTransfered[cur+2] = lpData[cur]; UpdateAllViews(NULL);
doubleCWvltDoc::W_real(int a, int b ,int c)
{
}
doubleCWvltDoc::W_image(int a, int b, int c)
{
} return sin((-2*3.1415*a*b)/c); return cos((-2*3.1415*a*b)/c);
五.实验过程中遇到的问题及解决办法
在具体实现时,开始没有想到灰度化,最后出来的结果还是彩色的图像,并且有的像素点的能量值可能大于255单可视化后一直是白色的点,解决办法是用可视化算子进行可视化,在实现的过程中一直修改的都是像素点的第一个灰度值即蓝色的灰度值,在用可视化算子可视化之后,再用蓝色的分量值进行灰度化即让其他的红色绿色灰度值都等于蓝色灰度值。
//实验用的头文件 MYFFT.H
//作用:为帮助小虎子做实验,这个头文件提供了完整的一维与二维FFT算法,我想应改是够你折腾了吧!
#include <complex> // complex<float>
using namespace std;
typedef complex<float> Comp; // 复数类型定义
const float _2PI_ = 2.0f * 3.14159265f; // 常数2PI定义 const int MAX_N = 256; // 最大DFT点数
/*----*----*----*----*----*----*----*----*----*----*----*----* FFT算法模块接口定义
*----*----*----*----*----*----*----*----*----*----*----*----*/
///////////////////////////////////////////
// Function name : BitReverse
// Description : 二进制倒序操作
// Return type : int
// Argument : int src 待倒读的数
// Argument : int size 二进制位数
int BitReverse(int src, int size)
{
int tmp = src;
int des = 0;
for (int i=size-1; i>=0; i--)
{
des = ((tmp & 0x1) << i) | des;
tmp = tmp >> 1;
}
return des;
}
////////////////////////////////////////////////// // Function name : Reorder
// Description : 数据二进制整序
// Return type : void
// Argument : Comp x[MAX_N] 待整序数组
// Argument : int N FFT点数
// Argument : int M 点数的2的幂次
void Reorder(Comp x[MAX_N], int N, int M)
{
Comp new_x[MAX_N];
for (int i=0; i<N; i++)
new_x = x[BitReverse(i, M)];
// 重新存入原数据中(已经是二进制整序过了的数据) for (i=0; i<N; i++)
x = new_x;
}
////////////////////////////////////////////////// // Function name : CalcW
// Description : 计算旋转因子
// Return type : void
// Argument : Comp W[MAX_N] 存放因子的数组 // Argument : int N FFT的点数
// Argument : int flag 正逆变换标记
void CalcW(Comp W[MAX_N], int N, int flag)
{
for (int i=0; i<N/2; i++)
{
if (flag == 1)
W = exp(Comp(0, -_2PI_ * i / N)); // 正FFT变换 else
W = exp(Comp(0, _2PI_ * i / N)); // 逆FFT变换 }
}
///////////////////////////////////////////////// // Function name : FFT_1D_Kernel
// Description : FFT算法核心
// Return type : void
// Argument : Comp* x 数据
// Argument : int M 幂次
// Argument : int flag 正逆变换标记
以下本应由自己完成。
void FFT_1D(Comp* x, int M, int flag)
{
int N = (1 << M);
// 二进制整序
Reorder(x, N, M);
// 旋转因子计算
Comp W[MAX_N];
CalcW(W, N, flag);
// 级内群数
int GroupNum = N/2; // 第一级的群数为N/2
// 群内蝶形单元数
int CellNum = 1; // 第一级的群内蝶形单元数为1
// 处理各级
for (int i=0; i<M; i++)
{
// 处理各群
for (int j=0; j<GroupNum; j++)
{
// 处理各蝶形单元
for (int k=0; k<CellNum; k++)
{
// (1) 计算出当前蝶形单元所含元素在数据数组中的位置
// 第一元素位置
int Pos1 = CellNum * j * 2 + k ; // 已经处理了前 j 群,每群有CellNum 个单元,
每单元有 2 个元素
// 第二元素位置
int Pos2 = Pos1 + CellNum;
// (2) 计算旋转因子与单元的第二元素的复数乘积
Comp TMP = x[Pos2] * W[k * GroupNum] ;
// (3) 计算最终结果, 并存入到数组的原先位置
x[Pos2] = x[Pos1] - TMP ;
x[Pos1] = x[Pos1] + TMP ;
}
}
GroupNum >>= 1; // 级别增加, 则相应的群数减少一半
CellNum <<= 1; // 级别增加, 则相应的群内单元数增加一倍 }
// 如果是IFFT,各元素还要再除以N
if (flag != 1)
{
for (i=0; i<N; i++)
x /= N;
}
}
////////////////////////////////////////////////////// // Function name : FFT_2D_Kernel
// Description : 2D FFT核心算法
// Return type : void
// Argument : Comp x[MAX_N][MAX_N] 二维数据
// Argument : int M 幂次
// Argument : int flag 正逆变换标记
以下本应由自己完成。
void FFT_2D(Comp x[MAX_N][MAX_N], int M, int flag) {
int N = (1 << M);
// 先逐行进行 1D-FFT
for (int i=0; i<N; i++)
FFT_1D(x, M, flag); // <--- 计算结果再存入矩阵x中
// 再逐列进行 1D-FFT
Comp col[MAX_N];
for (int j=0; j<N; j++)
{
// 取得第j列的数据
for (int i=0; i<N; i++)
col = x[j];
// 对第j列数据进行 1D-FFT
FFT_1D(col, M, flag); // <--- 计算结果在数组col中
// 将结果放回矩阵第j列中
for (i=0; i<N; i++)
x[j] = col;
}
}
// <--- End of [FFT.H]
快速傅立叶变换的应用(转贴)
20xx年11月24日 星期一 12:10
只要是理工科毕业的朋友,都学过傅立叶级数与傅立叶变换,但真正要与实际应用联系起来,用它来阐述应用中的各类问题,我们总会感觉概念模糊,似懂非懂,不 知从何说起。是的,作者和你一样,常常有这样的体会。现在,让我与你一起重新学习傅立叶的基本理论和应用,最后还给出一份FFT(快速傅立叶变换)的源码 (基于C)。希望对你有所帮助。Let’s go!
1. 历史回顾
谈傅立叶变换,不能不说三角函数。三角函数起源于18世纪,主要是与简谐振动的研究有关。当时的科学家傅立叶对三角函数作了深入研究,并用三角级数解决了很多热传导的问题。三角函数的展开式如下:
f(t) = (a0/2) + (a1·cos(x)+b1·sin(x)) + (a2·cos(2x)+b2·sin(2x)) + …
其中,系数a和b表示不同频率阶数下的幅度。
成立条件:
n 周期性条件,也就是说f(x)描述的波形必须每隔一段时间周期T就会重复出现;
n Dirichlet条件,周期T内,有限的最大最小值,有限的不连续点;
任何区间内绝对可积;
研究目的:
把一个基于时间变量t的函数展开成傅立叶级数的目的是分解为不同的频率分量,以便进行各种滤波算法。这些基本的组成部分是正弦函数SIN(nt)和余弦函数COS(nt)。
应用领域:
l 信号分析,包括滤波、数据压缩、电力系统的监控等;
l 研究偏微分方程,比如求解热力学方程的解时,把f(t)展开为三角级数最为关键。
l 概率与统计,量子力学等学科。
2. 傅立叶变换
H(w) = ∫h(t)·e^jwt·dt, (区间:-∽~+∽,w = 2πf)
讨论:这里为什么会选择复指数的形式而没有用正弦余弦表示?
答案:欧拉公式的引入使得这条经典的数学公式变得更简单,即e^jx = cos(x) + jsin(x)
3. 快速傅立叶变换(FFT)
常规的傅立叶变换算法并不适用于嵌入式控制系统,原因是运算量太大(涉及到复数运算),比如离散的傅立叶变换等同于用序列Y(n×1列矢量)乘以n×n 矩阵Fn,需要n×n次乘法。若n=1024,则是104,8576次乘法运算。哇,这么多呀!什么概念呢?如果你选用的CPU单周期指令为 25ns, 单周期也可以完成一次乘法运算,那么要计算1024点的傅立叶变换则需要26.2144ms,这还不包括加法或其它运算,对于大多数实时系 统,这个处理时间实在太长。于是寻找一个快速的傅立叶变换算法是人们所期望的。
本来我想把FFT的整个数学推导过程列完出来,但当自己硬着头 皮看完后,发现对我没有任何用处,我又不是专门研究数学算法的,哪有那么多时间跟着书本的公式去慢慢推导。我想,这些推导问题还是让数学家想去吧。我需要 的不过是理解它,然后学会应用它就行。有兴趣的读者可以参考相关的资料,这方面的资料实在太多了。
虽然FFT大幅度地降低了常规傅立叶变换的 运算量,但对于一般的单片机而言,处理FFT运算还是力不从心。主要原因是FFT计算过程中的蝶形运算是复数运算,要分开实部和虚部分别计算,想想这是多 么繁琐的事情。可能会有些初学者认为,有这么复杂吗?我在PC上使用C++一样可以对复数直接进行加、减、乘、除运算。你说得不错,可以这么做,但那是 C++封装了对复数处理的类,直接调用就行。在PC上运算这种类型的算法一般不考虑时间和空间,多一两秒的运行时间不会有什么灾难性的结果。
所以我们要衡量一个处理器有没有足够的能力来运行FFT算法,根据以上的简单介绍可以得出以下两点:
l 处理器要在一个指令周期能完成乘和累加的工作,因为复数运算要多次查表相乘才能实现。其二就是间接寻址,可以实现增/减1个变址量,方便各种查表方法。
2 FFT要对原始序列进行反序排列,处理器要有反序间接寻址的能力。
所以,在数字信号的分析处理应用中,DSP比其它的处理器有绝对的优势,因为DSP完全具备以上条件。这就是单片机(51系列,AVR,PIC等等)或ARM处理器很少用来进行数字信号分析的原因。
4. FFT的C实现方法
//**********************************************************
// 函数名: 快速傅立叶变换(来源《C常用算法集》)
// 本函数测试OK,可以在TC2.0,VC++6.0,Keil C51测试通过。
// 如果你的MCS51系统有足够的RAM时,可以验证一下用单片机处理FFT有多么的慢。
//
// 入口参数:
// l: l = 0, 傅立叶变换; l = 1, 逆傅立叶变换
// il: il = 0,不计算傅立叶变换或逆变换模和幅角;il = 1,计算模和幅角 // n: 输入的点数,为偶数,一般为32,64,128,...,1024等
// k: 满足n=2^k(k>0),实质上k是n个采样数据可以分解为偶次幂和奇次幂的次数
// pr[]: l=0时,存放N点采样数据的实部
// l=1时, 存放傅立叶变换的N个实部
// pi[]: l=0时,存放N点采样数据的虚部
// l=1时, 存放傅立叶变换的N个虚部
//
// 出口参数:
// fr[]: l=0, 返回傅立叶变换的实部
// l=1, 返回逆傅立叶变换的实部
// fi[]: l=0, 返回傅立叶变换的虚部
// l=1, 返回逆傅立叶变换的虚部
// pr[]: il = 1,i = 0 时,返回傅立叶变换的模
// il = 1,i = 1 时,返回逆傅立叶变换的模
// pi[]: il = 1,i = 0 时,返回傅立叶变换的辐角
// il = 1,i = 1 时,返回逆傅立叶变换的辐角
// data: 2005.8.15,Mend Xin Dong
void kkfft(double pr[], double pi[], int n, int k, double fr[], double fi[], int l, int il)
{
int it,m,is,i,j,nv,l0;
double p,q,s,vr,vi,poddr,poddi;
for (it=0; it<=n-1; it++)
{
m = it;
is = 0;
for(i=0; i<=k-1; i++)
{
j = m/2;
is = 2*is+(m-2*j);
m = j;
}
fr[it] = pr[is];
fi[it] = pi[is];
}
//---------------------------- pr[0] = 1.0;
pi[0] = 0.0;
p = 6.283185306/(1.0*n);
pr[1] = cos(p);
pi[1] = -sin(p);
if (l!=0)
pi[1]=-pi[1];
for (i=2; i<=n-1; i++)
{
p = pr[i-1]*pr[1];
q = pi[i-1]*pi[1];
s = (pr[i-1]+pi[i-1])*(pr[1]+pi[1]); pr[i] = p-q;
pi[i] = s-p-q;
}
for (it=0; it<=n-2; it=it+2)
{
vr = fr[it];
vi = fi[it];
fr[it] = vr+fr[it+1];
fi[it] = vi+fi[it+1];
fr[it+1] = vr-fr[it+1];
fi[it+1] = vi-fi[it+1];
}
m = n/2;
nv = 2;
for (l0=k-2; l0>=0; l0--)
{
m = m/2;
nv = 2*nv;
for(it=0; it<=(m-1)*nv; it=it+nv)
for (j=0; j<=(nv/2)-1; j++)
{
p = pr[m*j]*fr[it+j+nv/2];
q = pi[m*j]*fi[it+j+nv/2];
s = pr[m*j]+pi[m*j];
s = s*(fr[it+j+nv/2]+fi[it+j+nv/2]);
poddr = p-q;
poddi = s-p-q;
fr[it+j+nv/2] = fr[it+j]-poddr;
fi[it+j+nv/2] = fi[it+j]-poddi;
fr[it+j] = fr[it+j]+poddr;
fi[it+j] = fi[it+j]+poddi;
}
}
if(l!=0)
for(i=0; i<=n-1; i++)
{
fr[i] = fr[i]/(1.0*n);
fi[i] = fi[i]/(1.0*n);
}
if(il!=0)
for(i=0; i<=n-1; i++)
{
pr[i] = sqrt(fr[i]*fr[i]+fi[i]*fi[i]); if(fabs(fr[i])<0.000001*fabs(fi[i]))
{
if ((fi[i]*fr[i])>0)
pi[i] = 90.0;
else
pi[i] = -90.0;
}
else
pi[i] = atan(fi[i]/fr[i])*360.0/6.283185306; }
return;
//快速傅立叶变换的源代码
//------------------------------------
// fft.cpp
// The implementation of the
// Fast Fourier Transform algorithm
// (c) Reliable Software, 1996
//------------------------------------
#include "fft.h"
#include "recorder.h"
// log (1) = 0, log(2) = 1, log(3) = 2, log(4) = 2 ...
#define PI (2.0 * asin(1.0))
// Points must be a power of 2
Fft::Fft (int Points, long sampleRate)
: _Points (Points), _sampleRate (sampleRate)
{
_aTape = new double [_Points];
#if 0
// 1 kHz calibration wave
for (int i = 0; i < _Points; i++)
_aTape[i] = 1600 * sin (2 * PI * 1000. * i / _sampleRate); #else
for (int i = 0; i < _Points; i++)
_aTape[i] = 0;
#endif
_sqrtPoints = sqrt((double)_Points);
// calculate binary log
_logPoints = 0;
Points--;
while (Points != 0)
{
Points >>= 1;
_logPoints++;
}
_aBitRev = new int [_Points];
_X = new Complex[_Points];
_W = new Complex* [_logPoints+1];
// Precompute complex exponentials
int _2_l = 2;
for (int l = 1; l <= _logPoints; l++)
{
_W[l] = new Complex [_Points];
for ( int i = 0; i < _Points; i++ ) {
double re = cos (2. * PI * i / _2_l); double im = -sin (2. * PI * i / _2_l); _W[l][i] = Complex (re, im);
}
_2_l *= 2;
}
// set up bit reverse mapping
int rev = 0;
int halfPoints = _Points/2;
for (i = 0; i < _Points - 1; i++) {
_aBitRev[i] = rev;
int mask = halfPoints;
// add 1 backwards
while (rev >= mask)
{
rev -= mask; // turn off this bit mask >>= 1;
}
rev += mask;
}
_aBitRev [_Points-1] = _Points-1; }
Fft::~Fft()
{
delete []_aTape;
delete []_aBitRev;
for (int l = 1; l <= _logPoints; l++) {
delete []_W[l];
}
delete []_W;
delete []_X;
}
void Fft::CopyIn (SampleIter& iter) {
int cSample = iter.Count();
if (cSample > _Points)
return;
// make space for cSample samples at the end of tape // shifting previous samples towards the beginning memmove (_aTape, &_aTape[cSample],
(_Points - cSample) * sizeof(double));
// copy samples from iterator to tail end of tape int iTail = _Points - cSample;
for (int i = 0; i < cSample; i++, iter.Advance()) {
_aTape [i + iTail] = (double) iter.GetSample(); }
// Initialize the FFT buffer
for (i = 0; i < _Points; i++)
PutAt (i, _aTape[i]);
}
//
// 0 1 2 3 4 5 6 7
// level 1
// step 1 0
// increm 2 W
// j = 0 <---> <---> <---> <---> 1
// level 2
// step 2
// increm 4 0
// j = 0 <-------> <-------> W 1
// j = 1 <-------> <-------> 2 W
// level 3 2
// step 4
// increm 8 0
// j = 0 <---------------> W 1
// j = 1 <---------------> 3 W 2
// j = 2 <---------------> 3 W 3
// j = 3 <---------------> 3 W
// 3
//
void Fft::Transform ()
{
// step = 2 ^ (level-1)
// increm = 2 ^ level;
int step = 1;
for (int level = 1; level <= _logPoints; level++) {
int increm = step * 2;
for (int j = 0; j < step; j++)
{
// U = exp ( - 2 PI j / 2 ^ level )
Complex U = _W [level][j];
for (int i = j; i < _Points; i += increm)
{
// butterfly
Complex T = U;
T *= _X [i+step];
_X [i+step] = _X[i];
_X [i+step] -= T;
_X [i] += T;
}
}
step *= 2;
}
}
快速成型制造技术实验报告快速成型制造技术实验报告一实验目的1了解激光器的工作原理及其运行特点2了解高功率横流CO2激光成套设备的工…
开放性实验快速成型制造技术实验报告班级学号姓名指导教师一快速成型介绍快速原理制造技术又叫快速成型技术简称RP技术英文RAPIDPR…
快速成型实验报告专业姓名学号实验日期实验地点快速成型及逆向工程实验室成绩实验名称快速成型实验实验目的本次快速成型实验包括Magic…
成都理工大学核技术与自动化工程学院实验报告实验名称CADCAM实训实验姓名学号专业机械工程及自动化学期20xx20xx任课教师孙未…
成都理工大学核技术与自动化工程学院实验报告实验名称CADCAM实训实验快速原型成型姓名学号20xx060403专业机械工程及自动化…
个人教学工作总结东疏中学张美华一学期的时光就这样过去了,回顾这一学期的工作,想说的真是太多太多。人民教师这一职业是非常辛苦的。本学…
美国证交所黄金板块指数(hui)近以来一直下跌,我听到各种各样的理由来解释这其中的原因,包括中央银行出售储备金等等。的确,在过去两…
本学期的工作在学校、教导处的领导下已圆满结束,为了在以后的工作中能够总结经验,吸取教训,特把本学期初三级部的工作做一下总结:一:深…
20xx—20xx学年新课程改革下教师教育观念的转变总结开展解放思想、更新观念学习,是我校作出的重要部署,它贯彻“三个代表”重要思…
动漫社团工作总结这学期,在学校的支持和组织下,这个学期动漫社团,通过活动提高了学生的绘画能力和审美,为了下学年能更好的开展兴趣小组…