C++实现 (FFT)一维快速傅里叶变换

2023-05-16

一维离散傅里叶变换的公式为:

如果直接基于该定义进行编程实现,则算法时间复杂度为O(N2)。具体的编程实现我们已经在《C++实现一维离散傅里叶变换》中介绍过了。

当一维信号长度达到几十万个信号时,当前主流4G主频CPU完成一次傅里叶变换需要约几十到几百秒的时间,这样的效率显然是让人无法接受的。

为了解决傅里叶变换的计算效率问题,行业专家们提出了蝶形算法,极大地提升了傅里叶变换的运算效率。

在蝶形算法中,较为流行的是基于时间抽取的基-2快速傅里叶变换算法(以下简称为基-2FFT算法)。

基-2FFT算法要求原始信号长度L=2N,N为正整数。也就是说,信号长度必须为2的整数次方,如4、8、16、32、64、512、1024。这是由蝶形算法的二进分解性质决定的。

设原始信号序列为f(x),长度为L(L=2N,为2的整数次方),x=0,1,2,3,…,L-1,则傅里叶变换可表达为:


将f(x)按x的奇偶性分成两组:

则式1-1变为:

其中,F1(u)和F2(u)分别为f1(x)和f2(x)的(L/2)点的一维离散傅里叶变换。


例如,对于8个点的信号序列,可先分解两个长度为4的离散傅里叶变换,再将两个长度为4的离散傅里叶变换,各自分解为两个长度为2的离散傅里叶变换。故只需要计算4个长度为2的离散傅里叶变换,就可以递归算出原序列共8个点的dft。更为广泛地,对于长度为L=2N个点的信号序列,只需要计算2N-1个长度为2的离散傅里叶变换,就可以递归算出原序列共2N个点的dft。

现在我们研究信号输入端的输入顺序和变换结果输出端的输出顺序的关系。

8点序列的蝶形运算流程图如下:


(图片来源自百度文库《05-第五章快速傅里叶变换(蝶形运算)》)

观察上图(最左边可视为f(x),最右边视为F(x)),右边输出端的输出顺序为自然顺序,而最左边的输入端则并不是按自然顺序。这是因为序列f(x)在蝶形运算过程中被反复进行奇偶分解,对于长度为L=2N的序列,共分解N-1次,每一次分解,都是按二进制码0和1进行奇偶排列。经观察,发现这是一种二进制码位的倒序重排,例如,对于8点的序列,其码位倒序图为:

(图片来源自百度文库《05-第五章快速傅里叶变换(蝶形运算)》)

至此,对于以2为基的快速傅里叶变换算法,我们可以构思出一条清晰的实现思路,具体如下:

1.根据序列长度L,计算出倒序的码位;

2.算出加权序列W(WuL,上下标打不出来);

3. 计算2N-1个长度为2的离散傅里叶变换;

4.基于前述的蝶形运算公式1-2和分解流程,递归算出原序列共2N个点的dft。

至于逆变换,只需要按照上述思路进行逆向推导,即从结果逆推回到第一步,就可以得到自然序列的原始信号。

 

下面给出根据上述思路编写的FFT类头文件和实现文件:

Fft1.h
#pragma once
 
#define      MAX_MATRIX_SIZE                   4194304             // 2048 * 2048
#define      PI                                           3.141592653
#define      MAX_VECTOR_LENGTH              10000             //
 
typedef struct Complex
{
    double rl;
    double im;
}Complex;
 
class CFft1
{
public:
    CFft1(void);
    ~CFft1(void);
 
public:
    bool fft(Complex IN const inVec[], int IN const len, Complex IN outVec[]);            // 基于蝶形算法的快速傅里叶变换
    bool ifft(Complex IN const inVec[], int IN const len, Complex IN outVec[]);
 
    bool is_power_of_two(int IN num);
    int    get_computation_layers(int IN num);         // calculate the layers of computation needed for FFT
};


Fft1.cpp

#include "stdafx.h"
#include "Fft1.h"
 
CFft1::CFft1()
{
}
 
 
CFft1::~CFft1()
{
}
 
 
bool CFft1::is_power_of_two(int IN num)
{
    int temp = num;
    int mod = 0;
    int result = 0;
 
    if (num < 2)
        return false;
    if (num == 2)
        return true;
 
    while (temp > 1)
    {
        result = temp / 2;
        mod = temp % 2;
        if (mod)
            return false;
        if (2 == result)
            return true;
        temp = result;
    }
    return false;
}
 
 
int CFft1::get_computation_layers(int IN num)
{
    int nLayers = 0;
    int len = num;
    if (len == 2)
        return 1;
    while (true) {
        len = len / 2;
        nLayers++;
        if (len == 2)
            return nLayers + 1;
        if (len < 1)
            return -1;
    }
}
 
 
// Fourier transform of 1 - dimension vector
// Param1: the input vector to be transformed
// Param 2: len of the input vector
// Param 3: output vector, which is the result of fft
bool CFft1::fft(Complex IN inVec[], int IN const vecLen, Complex IN outVec[])
{
    char msg[256] = "11111 ";
 
    if ((vecLen <= 0) || (NULL == inVec) || (NULL == outVec))
        return false;
    if (!is_power_of_two(vecLen))
        return false;
 
    // create the weight array
    Complex         *pVec = new Complex[vecLen];
    Complex         *Weights = new Complex[vecLen];
    Complex         *X = new Complex[vecLen];
    int                   *pnInvBits = new int[vecLen];
 
    memcpy(pVec, inVec, vecLen*sizeof(Complex));
 
    // 计算权重序列
    double fixed_factor = (-2 * PI) / vecLen;
    for (int i = 0; i < vecLen / 2; i++) {
        double angle = i * fixed_factor;
        Weights[i].rl = cos(angle);
        Weights[i].im = sin(angle);
    }
    for (int i = vecLen / 2; i < vecLen; i++) {
        Weights[i].rl = -(Weights[i - vecLen / 2].rl);
        Weights[i].im = -(Weights[i - vecLen / 2].im);
    }
 
    int r = get_computation_layers(vecLen);
 
    // 计算倒序位码
    int index = 0;
    for (int i = 0; i < vecLen; i++) {
        index = 0;
        for (int m = r - 1; m >= 0; m--) {
            index += (1 && (i & (1 << m))) << (r - m - 1);
        }
        pnInvBits[i] = index;
        X[i].rl = pVec[pnInvBits[i]].rl;
        X[i].im = pVec[pnInvBits[i]].im;
    }
 
    // 计算快速傅里叶变换
    for (int L = 1; L <= r; L++) {
        int distance = 1 << (L - 1);
        int W = 1 << (r - L);
 
        int B = vecLen >> L;
        int N = vecLen / B;
 
        for (int b = 0; b < B; b++) {
            int mid = b*N;
            for (int n = 0; n < N / 2; n++) {
                int index = n + mid;
                int dist = index + distance;
                pVec[index].rl = X[index].rl + (Weights[n*W].rl * X[dist].rl - Weights[n*W].im * X[dist].im);                      // Fe + W*Fo
                pVec[index].im = X[index].im + Weights[n*W].im * X[dist].rl + Weights[n*W].rl * X[dist].im;
            }
            for (int n = N / 2; n < N; n++) {
                int index = n + mid;
                pVec[index].rl = X[index - distance].rl + Weights[n*W].rl * X[index].rl - Weights[n*W].im * X[index].im;        // Fe - W*Fo
                pVec[index].im = X[index - distance].im + Weights[n*W].im * X[index].rl + Weights[n*W].rl * X[index].im;
            }
        }
 
        memcpy(X, pVec, vecLen*sizeof(Complex));
    }
 
    memcpy(outVec, pVec, vecLen*sizeof(Complex));
    if (Weights)      delete[] Weights;
    if (X)                 delete[] X;
    if (pnInvBits)    delete[] pnInvBits;
    if (pVec)           delete[] pVec;
    return true;
}
 
 
bool CFft1::ifft(Complex IN const inVec[], int IN const len, Complex IN outVec[])
{
    char msg[256] = "11111 ";
 
    if ((len <= 0) || (!inVec))
        return false;
    if (false == is_power_of_two(len)) {
        return false;
    }
 
    double         *W_rl = new double[len];
    double         *W_im = new double[len];
    double         *X_rl = new double[len];
    double         *X_im = new double[len];
    double         *X2_rl = new double[len];
    double         *X2_im = new double[len];
 
    double fixed_factor = (-2 * PI) / len;
    for (int i = 0; i < len / 2; i++) {
        double angle = i * fixed_factor;
        W_rl[i] = cos(angle);
        W_im[i] = sin(angle);
    }
    for (int i = len / 2; i < len; i++) {
        W_rl[i] = -(W_rl[i - len / 2]);
        W_im[i] = -(W_im[i - len / 2]);
    }
 
    // 时域数据写入X1
    for (int i = 0; i < len; i++) {
        X_rl[i] = inVec[i].rl;
        X_im[i] = inVec[i].im;
    }
    memset(X2_rl, 0, sizeof(double)*len);
    memset(X2_im, 0, sizeof(double)*len);
 
    int r = get_computation_layers(len);
    if (-1 == r)
        return false;
    for (int L = r; L >= 1; L--) {
        int distance = 1 << (L - 1);
        int W = 1 << (r - L);
 
        int B = len >> L;
        int N = len / B;
        //sprintf(msg + 6, "B %d, N %d, W %d, distance %d, L %d", B, N, W, distance, L);
        //OutputDebugStringA(msg);
 
        for (int b = 0; b < B; b++) {
            for (int n = 0; n < N / 2; n++) {
                int index = n + b*N;
                X2_rl[index] = (X_rl[index] + X_rl[index + distance]) / 2;
                X2_im[index] = (X_im[index] + X_im[index + distance]) / 2;
                //sprintf(msg + 6, "%d, %d: %lf, %lf", n + 1, index, X2_rl[index], X2_im[index]);
                //OutputDebugStringA(msg);
            }
            for (int n = N / 2; n < N; n++) {
                int index = n + b*N;
                X2_rl[index] = (X_rl[index] - X_rl[index - distance]) / 2;           // 需要再除以W_rl[n*W]
                X2_im[index] = (X_im[index] - X_im[index - distance]) / 2;
                double square = W_rl[n*W] * W_rl[n*W] + W_im[n*W] * W_im[n*W];          // c^2+d^2
                double part1 = X2_rl[index] * W_rl[n*W] + X2_im[index] * W_im[n*W];         // a*c+b*d
                double part2 = X2_im[index] * W_rl[n*W] - X2_rl[index] * W_im[n*W];          // b*c-a*d            
                if (square > 0) {
                    X2_rl[index] = part1 / square;
                    X2_im[index] = part2 / square;
                }
            }
        }
        memcpy(X_rl, X2_rl, sizeof(double)*len);
        memcpy(X_im, X2_im, sizeof(double)*len);
    }
 
    // 位码倒序
    int index = 0;
    for (int i = 0; i < len; i++) {
        index = 0;
        for (int m = r - 1; m >= 0; m--) {
            index += (1 && (i & (1 << m))) << (r - m - 1);
        }
        outVec[i].rl = X_rl[index];
        outVec[i].im = X_im[index];
        //sprintf(msg + 6, "X_rl[i]: %lf, %lf,  index: %d", out_rl[i], out_im[i], index);
        //OutputDebugStringA(msg);
    }
 
    if (W_rl)      delete[] W_rl;
    if (W_im)    delete[] W_im;
    if (X_rl)      delete[] X_rl;
    if (X_im)     delete[] X_im;
    if (X2_rl)     delete[] X2_rl;
    if (X2_im)    delete[] X2_im;
 
    return true;
}

现在,我们编写并运行一个测试线程,对一个16点的一维信号进行快速傅里叶变换,以验证上述代码。

DWORD WINAPI test(LPVOID lParam)
{
    double vec[] = { 15, 32, 9, 222, 118, 151, 5, 7, 56, 233, 56, 121, 235, 89, 98, 111 };
    int len = sizeof(vec) / sizeof(double);
 
    Complex *inVec = new Complex[len];
    Complex *outVec = new Complex[len];
    Complex *invert = new Complex[len];
 
    memset(inVec, 0, len*sizeof(Complex));
    for (int i = 0; i < len; i++)
        inVec[i].rl = vec[i];
 
    // Fourier transformation
    CFft1 t;
    t.fft(inVec, len, outVec);
 
    // print result
    char msg[256] = "11111 ";
    OutputDebugStringA("11111 快速傅里叶变换结果为:");
    for (int i = 0; i < len; i++) {
        if (outVec[i].im < 0)
            sprintf(msg + 6, "result[%d]: %lf - %lfi", i+1, outVec[i].rl, -outVec[i].im);
        else
            sprintf(msg + 6, "result[%d]: %lf + %lfi", i + 1, outVec[i].rl, outVec[i].im);
        OutputDebugStringA(msg);
    }
 
    OutputDebugStringA("11111 逆变换结果为:");
    t.ifft(outVec, len, invert);
    for (int i = 0; i < len; i++) {
        sprintf(msg + 6, "ifft[%d]: %lf", i + 1, invert[i].rl);
        OutputDebugStringA(msg);
    }
 
    delete[] inVec;
    delete[] outVec;
    delete[] invert;
    return 0;
}
在MFC对话框资源中添加一个test按钮,在按钮事件响应函数中添加:
::CreateThread(NULL,0, test, 0, 0, NULL);
然后编译项目。编译成功后,先打开DebugView日志观察工具,再启动生成的exe,点击test按钮,可以在DebugView中看到以下日志输出: 

可以看到,逆变换的结果和原始信号完全一致。

使用Matlab的fft()函数对原始信号进行变换,得到的结果也和上述变换结果一致。

因此我们的实现代码是有效的,输出了正确的变换结果。
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++实现 (FFT)一维快速傅里叶变换 的相关文章

随机推荐

  • 委托事件的线程问题

    事件注册方法或委托后 xff0c 事件所在的线程执行注册的方法或委托 xff0c 所以如果方法中有跨线程控件就需要使用invoke等处理
  • 怎么判断应用程序是多少位运行的

    C 中 int bitSize 61 IntPtr Size 指针多少字节 if bitSize 61 61 8 MessageBox Show 34 64位程序 34 else if bitSize 61 61 4 MessageBox
  • 图像的色彩类别,灰度化,二值化

    灰度化 xff1a 在RGB模型中 xff0c 如果R 61 G 61 B时 xff0c 则彩色表示一种灰度颜色 xff0c 其中R 61 G 61 B的值叫灰度值 xff0c 因此 xff0c 灰度图像每个像素只需一个字节存放灰度值 xf
  • 服务器与客户端概念

    比如说 xff0c 你浏览百度的网页 xff0c 你的电脑就是客户端 xff0c 而百度网页所存放的机器就是服务器 你通过internet互联网连到百度网页服务器 xff0c 才能浏览网页 再比如说 xff0c 你玩网络游戏 xff0c 你
  • 图像灰度图,直方图,像素通道问题

    1 图像直方图概述 直方图广泛运用于很多计算机视觉运用当中 xff0c 通过标记帧与帧之间显著的边缘和颜色的统计变化 xff0c 来检测视频中场景的变化 在每个兴趣点设置一个有相近特征的直方图所构成 标签 xff0c 用以确定图像中的兴趣点
  • 深入浅出的讲解傅里叶变换(真正的通俗易懂)

    我保证这篇文章和你以前看过的所有文章都不同 xff0c 这是 2012 年还在果壳的时候写的 xff0c 但是当时没有来得及写完就出国了 于是拖了两年 xff0c 嗯 xff0c 我是拖延症患者 这篇文章的核心思想就是 xff1a 要让读者
  • 理解图像傅里叶变换的频谱图

    很多人都不了解图像 xff08 二维 xff09 频谱中的每一点究竟代表了什么 xff0c 有什么意义 一句话解释为 xff1a 二维频谱中的每一个点都是一个与之一 一对应的二维正弦 余弦波 视觉的优势永远大于其他器官对人的作用 xff0c
  • 机器视觉自动数据标注方法

    目录 一 背景阅读 个人总结 xff1a xff08 半 xff09 自动数据标注的方法基本都是采用类似的思路 xff0c 即通过少量标注数据进行训练后得到一个预训练模型 xff0c 然后再次基础上对该网络的输出结果进行人工核验 xff0c
  • 理解图像的傅里叶变换

    最近在看图像的傅里叶变换 xff0c 看着频谱图一直没看明白到底为啥是那样的 xff0c 跟同学研究了好久 xff0c 终于想明白了 感谢同学的耐心指导 xff01 大家相互讨论真的很快就能出结果 xff0c 多讨论 xff0c 多学习 图
  • 快速傅里叶变换(FFT)详解

    快速傅里叶变换 xff08 FFT xff09 详解 xff08 这是我第一次写博 xff0c 不喜勿喷 xff09 关于FFT已经听闻已久了 xff0c 这次终于有机会在Function2的介绍下来了解一下FFT了 快速傅里叶变换 Fas
  • 图像处理:如何理解傅里叶变换在图像处理中的应用

    声明 xff1a 这篇文章的主要目的是通过建立一维傅里叶变换与图像傅里叶变换中相关概念的对应关系来帮助读者理解图像处理中的离散傅里叶变换 xff0c 因此 xff0c 理解图像中离散傅里叶变换的前提条件是读者需要了解一维傅里叶变换的基本知识
  • 图像处理中的傅里叶变换和频率域滤波概念

    写在前面的话 作者是一名在读的硕士研究僧 xff0c 方向是机器视觉 由于视觉是一门相对复杂的学科 xff0c 作者在课堂上学到的东西只是非常浅显的内容 xff0c 我们老师说是 xff0c 领我们进了个门 现在打算利用图书馆和网络上的资源
  • 图像处理的傅里叶变换理解

    傅立叶变换在图像处理中有非常非常的作用 因为不仅傅立叶分析涉及图像处理的很多方面 xff0c 傅立叶的改进算法 xff0c 比如离散余弦变换 xff0c gabor与小波在图像处理中也有重要的分量 印象中 xff0c 傅立叶变换在图像处理以
  • 傅里叶变换分类

    傅里叶变换 傅里叶变换 xff08 Fourier transform xff09 是一种线性的积分变换 xff0c 从时间转换为频率的变化 1 1 连续傅里叶变换 这是将频率域的函数F 表示为时间域的函数f xff08 t xff09 的
  • C++实现二维离散傅里叶变换

    在上一篇文章 C 43 43 实现一维离散傅里叶变换 中 xff0c 我们介绍了一维信号傅立叶变换的公式和C 43 43 实现 xff0c 并阐述了频域幅值的意义 一维傅立叶变换只适用于一维信号 xff0c 例如音频数据 心脑电图等 在图像
  • MFC显示JPG,bmp图片

    主要代码如下 xff1a 方法说明 显示JPG和GIF BMP图片 参数说明 CDC pDC 设备环境对象 参数说明 CString strPath 要显示的图片路径 参数说明 int x 要显示的X位置 参数说明 int y 要显示的Y位
  • MFC关于JPG图片显示处理的几个方式

    做远程视频监控项目 xff0c 接触较多图片处理方面问题 xff0c 作为学习做以下记录 xff1a 一 截图默认bmp格式转jpg压缩 采用jpglib库去实现 二 jpg图片接收后MFC显示 四种方式 MFC提供的CWnd只有默认加载B
  • opencv学 之图像傅里叶变换dft

    一 前言 接触了图像的傅里叶变换 xff0c 数学原理依旧不是很懂 xff0c 因此不敢在这里妄言 下午用Opencv代码实现了这一变换 xff0c 有一些经验心得 二 关键函数解析 2 1copyMakeBorder 扩展图片尺寸 傅里叶
  • UML轻松入门之动态建模

    在UML中 xff0c 静态建模可以描述系统的组织结构 xff0c 而动态建模则可以描述系统的行为和动作 在动态建模机制中 xff0c 以消息完成对象之间的交互 xff0c 用状态图 顺序图 协作图和活动图来描述系统的行为 消息 在面向对象
  • C++实现 (FFT)一维快速傅里叶变换

    一维离散傅里叶变换的公式为 xff1a 如果直接基于该定义进行编程实现 xff0c 则算法时间复杂度为O N2 具体的编程实现我们已经在 C 43 43 实现一维离散傅里叶变换 中介绍过了 当一维信号长度达到几十万个信号时 xff0c 当前