博客專欄

EEPW首頁 > 博客 > 淺談FFT以及FFT算法的基本實現(xiàn)

淺談FFT以及FFT算法的基本實現(xiàn)

發(fā)布人:凋葉棕 時間:2018-12-14 來源:工程師 發(fā)布文章

相信網(wǎng)上現(xiàn)在有很多關(guān)于FFT的教程,我曾經(jīng)也參閱了很多網(wǎng)上的教程,感覺都不怎么通俗易懂。在基本上的研究FFT,并且通過編程的形式實現(xiàn)之后。我決定寫一篇通俗易懂的關(guān)于FFT的講解。因此我在接下來的敘述中盡量非常通俗細致的講解。

本人最早知道傅里葉變換的時候是沉迷于音樂的頻譜跳動無法自拔,當(dāng)時就很想做一個音樂頻譜顯示器。搜閱了很多資料之后,才了解到傅里葉變換,和FFT。當(dāng)然這都是以前的事情了,經(jīng)過了系統(tǒng)的學(xué)習(xí)+2個星期的研究,自制了一個FFT的算法,不敢說速度上的優(yōu)勢,但是個人認(rèn)為是一種通俗易懂的實現(xiàn)方法。經(jīng)過實際的VC++模擬實驗、和STM32跑的也很成功。

 

         首先,要會FFT,就必須要對DFT有所了解,因為兩者之間本質(zhì)上是一樣的。在此之前,先列出離散傅里葉變換對(DFT):

1.png

2.png

其中:3.png


但是FFT之所以稱之為快速傅里葉變換,就利用了以下的幾個性質(zhì)(重中之重?。?/span>

周期性:4.png


對稱性:5.png


可約性:6.png



先把這仨公式放到這,接下來會用到。

 

根據(jù)這幾個特性,就可以將一個長的DFT運算分解為若干短序列的DFT運算的組合,從而減少運算量。在這里,為了方便理解,我就用了一個按時間抽取的快速傅里葉變換(DIT-FFT)的方法。

 

首先,將一個序列x(n)一分為二

對于7.png,k=0,1,…N-1   設(shè)N2的整次冪 也就是N=2^M

x(n)按照奇偶分組


x(2r)=X1(r)

x(2r+1)=X2(r)                     r=0,1,…..(N/2)-1

那么將DFT也分為兩組來預(yù)算

8.png


(第一項是偶  第二項是奇)


這個時候,我們上邊提的三個性質(zhì)中的可約性就起到作用了:

回顧一下:

9.png


那么這個式子就可以化為:

10.png


則X1和X2都是長度為N/2的序列x1和x2N/2點的離散傅里葉變換

                   其中:


11.png

至此,一個N點的DFT就被分解為2N/2DFT。但是只有N/2個點,也就是說式(1-1)只是DFT的前半部分。要求DFT的后半部分可以利用其對稱性求出后半部分為:

12.png

(1-2)


那么式(1-1)和(1-2)就可以專用一個蝶形信號流圖符號來表示。如圖:

tgawertgfawet.png

N=8為例,可以用下圖表示:

dft.png

那么,通過這樣的分解,每一個N/2DFT只需(N^2)/4次復(fù)數(shù)相乘計算,明顯的節(jié)省了運算量。

那么以此類推,繼續(xù)將已經(jīng)得出的X1(K)和X2(k)按奇偶繼續(xù)分解,還是上邊一樣的方法。那么就得出了百度上都可以找到的一大堆的這個圖片了。

fft.png

對于這張圖片我想強調(diào)的一點就是第二階蝶形運算的時候,W0N之后不應(yīng)該是W1N嗎,為什么是W2N了,這個問題之前困擾了我一段時間,但是不要忘了,前者的W0N的展開是 W0N/2因為此時N已經(jīng)按照奇偶分開了,所以是N/2  而W2N也就是 W1N/2是根據(jù)可約性得出的,在這里不能混淆.。

 

對于運算效率就不用多提了

 

 

以上就是FFT算法的理論內(nèi)容了,接下來就是用C語言對這個算法的實現(xiàn)了,對于FFT算法C語言的實現(xiàn),網(wǎng)上的方法層出不窮,介于本人比較懶(懶得看別人的程序),再加上自給自足豐衣足食的原則,我自己也寫了一個個人認(rèn)為比較通俗易懂的程序,并且為了幫助讀者理解,我特意盡量減少了庫函數(shù)的使用,一些基本的函數(shù)都是自己寫的(難免有很多BUG),但是作為FFT算法已經(jīng)夠用了,目前這個程序只能處理2^N的數(shù)據(jù),理論上來講如果不夠2^N的話可以在后面數(shù)列補0這種操作為了簡約我也就沒有實現(xiàn),但是主要的功能是具備的,下面是代碼:


/*
    時間:2018年11月24日
    功能:將input里的數(shù)據(jù)進行快速傅里葉變換  并且輸出
*/
 
#include <stdio.h>
#include <math.h>
#define PI 3.1415926
#define FFT_LENGTH      8
double input[FFT_LENGTH]={1,1,1,1,1,1,1,1};
struct complex1{        //定義一個復(fù)數(shù)結(jié)構(gòu)體
    double real;    //實部
    double image;   //虛部
};  
//將input的實數(shù)結(jié)果存放為復(fù)數(shù)
struct complex1 result_dat[8];
/*
    虛數(shù)的乘法
*/
struct complex1 con_complex(struct complex1 a,struct complex1 b){
    struct complex1 temp;
    temp.real=(a.real*b.real)-(a.image*b.image);
    temp.image=(a.image*b.real)+(a.real*b.image);
    return temp;
}
 
/*
    簡單的a的b次方
*/
int mypow(int a,int b){
    int i,sum=a;
    if(b==0)return 1;
    for(i=1;i<b;i++){
        sum*=a; 
    }
    return sum;
}
/*
    簡單的求以2為底的正整數(shù)
*/
int log2(int n){
    unsigned i=1;
    int sum=1;
    for(i;;i++){
        sum*=2;
        if(sum>=n)break;
    }
    return i;
}
/*
    簡單的交換數(shù)據(jù)的函數(shù)
*/
void swap(struct complex1 *a,struct complex1 *b){
    struct complex1 temp;
    temp=*a;
    *a=*b;
    *b=temp;
}
/*
    dat為輸入數(shù)據(jù)的數(shù)組
    N為抽樣次數(shù)  也代表周期  必須是2^N次方
*/
void fft(struct complex1 dat[],unsigned char N){    
    /*最終  dat_buf計算出 當(dāng)前蝶形運算奇數(shù)項與W  乘積
            dat_org存放上一個偶數(shù)項的值
    */
    struct complex1 dat_buf,dat_org;
    /*  L為幾級蝶形運算    也代表了2進制的位數(shù)
        n為當(dāng)前級蝶形的需要次數(shù)  n最初為N/2 每級蝶形運算后都要/2
        i j為倒位時要用到的自增符號  同時  i也用到了L碟級數(shù)   j是計算當(dāng)前碟級的計算次數(shù) 
        re_i i_copy均是倒位時用到的變量
        k為當(dāng)前碟級  cos(2*pi/N*k)的  k   也是e^(-j2*pi/N)*k  的  k
    */
    unsigned char L,i,j,re_i=0,i_copy=0,k=0,fft_flag=1;
    //經(jīng)過觀察,發(fā)現(xiàn)每級蝶形運算需要N/2次運算,共運算N/2*log2N  次  
    unsigned char fft_counter=0;
    //在此要進行補2   N必須是2^n   在此略
    //蝶形級數(shù)  (L級)
    L=log2(N);  
    //計算每級蝶形計算的次數(shù)(這里只是一個初始值)  之后每次要/2
    //n=N/2;
 
    //對dat的順序進行倒位
    for(i=1;i<N/2;i++){
        i_copy=i;
        re_i=0;
        for(j=L-1;j>0;j--){
            //判斷i的副本最低位的數(shù)字  并且移動到最高位  次高位  ..
            //re_i為交換的數(shù)   每次它的數(shù)字是不能移動的 并且循環(huán)之后要清0
            re_i|=((i_copy&0x01)<<j);       
            i_copy>>=1;
        }
        swap(&dat[i],&dat[re_i]);
    }
    //進行fft計算
    for(i=0;i<L;i++){
        
        fft_flag=1;
        fft_counter=0;
        for(j=0;j<N;j++){
            if(fft_counter==mypow(2,i)){        //控制隔幾次,運算幾次,
                fft_flag=0;
            }else if(fft_counter==0){       //休止結(jié)束,繼續(xù)運算
                fft_flag=1;
            }
            //當(dāng)不判斷這個語句的時候  fft_flag保持  這樣就可以持續(xù)運算了
            if(fft_flag){
                dat_buf.real=cos((2*PI*k)/(N/mypow(2,L-i-1)));
                dat_buf.image=-sin((2*PI*k)/(N/mypow(2,L-i-1)));
                dat_buf=con_complex(dat[j+mypow(2,i)],dat_buf);
                                                //計算 當(dāng)前蝶形運算奇數(shù)項與W  乘積
 
                dat_org.real=dat[j].real;
                dat_org.image=dat[j].image;     //暫存
 
                dat[j].real=dat_org.real+dat_buf.real;
                dat[j].image=dat_org.image+dat_buf.image;       
                                                    //實部加實部   虛部加虛部
 
                dat[j+mypow(2,i)].real=dat_org.real-dat_buf.real;
                dat[j+mypow(2,i)].image=dat_org.image-dat_buf.image;
                                                    //實部減實部 虛部減虛部
 
                k++;
                fft_counter++;
            }else{
                fft_counter--;              //運算幾次,就休止幾次
                k=0;
            }
        }
    }
 
    
}
void main(){
    int i;
    //先將輸入信號轉(zhuǎn)換成復(fù)數(shù)
    for(i=0;i<FFT_LENGTH;i++){
        result_dat[i].image=0;  
                                //輸入信號是二維的,暫時不存在復(fù)數(shù)
        result_dat[i].real=input[i];
        //result_dat[i].real=10;
                                //輸入信號都為實數(shù)
    }
    fft(result_dat,FFT_LENGTH);
    for(i=0;i<FFT_LENGTH;i++){
        input[i]=sqrt(result_dat[i].real*result_dat[i].real+result_dat[i].image*result_dat[i].image);
        //取模
        printf("%lf\n",input[i]);
    }
    while(1);
}

這就是本次淺談FFT以及FFT算法的基本實現(xiàn)的全部內(nèi)容了



原創(chuàng)內(nèi)容轉(zhuǎn)載其注明原作者.  



參考書籍:數(shù)字信號處理




*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。



關(guān)鍵詞:

技術(shù)專區(qū)

關(guān)閉