離散傅立葉變換
一直很納悶,幾乎所有數字圖像處理的書都會介紹數字時頻變換,但是卻很少書會講時頻變換的作用,這也是讓我一直很疑惑的地方(不過也極有可能是本人愚鈍)。頻譜技術通常用於提高圖像的處理操作速率,頻譜相當於是圖像的特徵,時頻變換雖然是一種數學技巧,但是運用到數字圖像處理上會方便和簡單。研究的圖像變換基本上都是正交變換,正交變換可以減少圖像數據的相關性,獲取圖像的整體特點,有利於用較少的數據量表示原始圖像,這對圖像的分析、存儲以及圖像的傳輸都是非常有意義的。這裏介紹了離散傅立葉變換、離散餘弦變換、沃爾什-哈達瑪變換及小波變換的基本理論和知識並進行圖像時頻變換實驗。
一、二維離散傅里葉變換
一個圖像尺寸爲M×N的 函數f(x,y)的離散傅里葉變換由以下等式給出:
其中 u=0,1,2,...,M-1和v=0,1,2,...,N-1。其中變量u和v用於確定它們的頻率,頻域系統是由F(u,v)所張成的座標系,其中u和v用做(頻率)變量。空間域是由f(x,y)所張成的座標系。可以得到頻譜系統在頻譜圖四角(0,0),(0,N-1),(N-1,0),(N-1,N-1)處沿u和v方向的頻譜分量均爲0。
離散傅里葉逆變換由下式給出:
令R和I分別表示F的實部和需部,則傅里葉頻譜,相位角,功率譜(幅度)定義如下:
在頻譜的原點變換值稱爲傅里葉變換的直流分量,下面是傅里葉變換的週期公式:
DFT實現僅計算一個週期,所以可以處理M X N的數組,由週期性可知,A,B,C,D是四個分別的四分之一週期。
圖像的頻率是表徵圖像中灰度變化劇烈程度的指標,是灰度在平面空間上的梯。傅立葉頻譜圖上我們看到的明暗不一的亮點,實際上圖像上某一點與鄰域點差異的強弱,即梯度的大小,也即該點的頻率的大小(可以這麼理解,圖像中的低頻部分指低梯度的點,高頻部分相反)。一般來講,梯度大則該點的亮度強,否則該點亮度弱。這樣通過觀察傅立葉變換後的頻譜圖,也叫功率圖,我們首先就可以看出,圖像的能量分佈,如果頻譜圖中暗的點數更多,那麼實際圖像是比較柔和的(因爲各點與鄰域差異都不大,梯度相對較小),反之,如果頻譜圖中亮的點數多,那麼實際圖像一定是尖銳的,邊界分明且邊界兩邊像素差異較大的。對頻譜移頻到原點以後,可以看出圖像的頻率分佈是以原點爲圓心,對稱分佈的。變換最慢的頻率成分(u=v=0)對應一幅圖像的平均灰度級。當從變換的原點移開時,低頻對應着圖像的慢變換分量,較高的頻率開始對應圖像中變化越來越快的灰度級。這些事物體的邊緣和由灰度級的突發改變(如噪聲)標誌的圖像成分。通常在進行傅里葉變換之前用(-1)^(x+y)乘以輸入的圖像函數,這樣就可以將傅里葉變換的原點F(0,0)移到(M/2,N/2)上。
二、算法原理
離散快速傅里葉變換(FFT)是在離散傅立葉變換(DFT)的基礎上改進的,爲了更好的理解FFT算法,先對DFT進行梳理下,
1、DFT運算的特點:
首先分析有限長序列x(n)進行一次DFT運算所需的運算量。
一般,x(n)和都是複數,因此,每計算一個X(k)值,要進行N次複數相乘,和N-1次複數相加,X(k)一共有N個點,故完成全部DFT運算,需要N^2次複數相乘和N(N-1)次複數相加,在這些運算中,乘法比加法複雜,需要的運算時間多,尤其是複數相乘,每個複數相乘包括4個實數相乘和2個實數相加,例
又每個複數相加包括2個實數相加,所以,每計算一個X(k)要進行4^N次實數相乘和2N+2(N-1)=2(2N-1)次實數相加,因此,整個DFT運算需要4N^2實數相乘和2N(2N-1)次實數相加。
2、FFT算法的基本思想:
考察DFT與IDFT的運算髮現,利用以下兩個特性可減少運算量:
1)係數 是一個周期函數,它的週期性和對稱性可用來改進運算,提高計算效率。
例
又如因此因此
利用這些週期性和對稱性,使DFT運算中有些項可合併;
2)利用的 週期性和對稱性,把長度爲N點的大點數的DFT運算依次分解爲若干個小點數的DFT。因爲DFT的計算量正比於N二次方,N小,計算量也就小。
FFT算法正是基於這樣的基本思想發展起來的。它有多種形式,但基本上可分爲兩類:時間抽取法和頻率抽取法。
3、按時間抽取的FFT(N點DFT運算的分解)
假設採樣序列點數爲N=2^L,L爲整數,如果不滿足這個條件可以人爲地添加若干個0以使採樣序列點數滿足這一要求。首先我們將序列x(n)按照奇偶分爲兩組如下:
將DFT運算也相應分爲兩組:
注意到,H(k),G(k)有N/2個點,即k=0,1,…,N/2-1,還必須應用係數 的週期性和對稱性表示X(k)的N/2 ~N-1點:
可見,一個N點的DFT被分解爲兩個N/2點的DFT,這兩個N/2點的DFT再合成爲一個N點DFT
依此類推,G(k)和H(k)可以繼續分下去,這種按時間抽取算法是在輸入序列分成越來越小的子序列上執行DFT運算,最後再合成爲N點的DFT。
蝶形信號流圖
將G(k)和H(k) 合成X(k)運算可歸結爲:
蝶形運算的簡化
圖(a)爲實現這一運算的一般方法,它需要兩次乘法、兩次加減法。考慮到-bW和bW兩個乘法僅相差一負號,可將圖(a)簡化成圖2.7(b),此時僅需一次乘法、兩次加減法。圖(b)的運算結構像一蝴蝶通常稱作蝶形運算結構簡稱蝶形結,採用這種表示法,就可以將以上所討論的分解過程用流圖表示。
N=2^3=8的例子。
按照這個辦法,繼續把N/2用2除,由於N=2M,仍然是偶數,可以被2整除,因此可以對兩個N/2點的DFT再分別作進一步的分解。即對{G(k)}和{H(k)}的計算,又可以分別通過計算兩個長度爲N/4=2點的DFT,進一步節省計算量,見圖。這樣,一個8點的DFT就可以分解爲四個2點的DFT。
最後剩下的是2點DFT,它可以用一個蝶形結表示:
這樣,一個8點的完整的按時間抽取運算的流圖
由於這種方法每一步分解都是按輸入時間序列是屬於偶數還是奇數來抽取的,所以稱爲“按時間抽取法”或“時間抽取法”。
4、按頻率抽取的FFT(按輸出X(k)在頻域的順序上屬於偶數還是奇數分解爲兩組)
對於N=2^M情況下的另外一種普遍使用的FFT結構是頻率抽取法。
對於頻率抽取法,輸入序列不是按偶數奇數,而是按前後對半分開,這樣便將N點DFT寫成前後兩部分:
令
這兩個序列都是N/2點的序列,將其代入上兩式,得
這正是兩個N/2點的DFT運算,即將一個N點的DFT分解爲兩個N/2點的DFT,上式的運算關係可用下圖表示.
以N=8的頻率抽取爲例
按頻率抽取將8點DFT分解成四個2點DFT
與時間抽取法一樣,由於N=2M,N/2仍是一個偶數,這樣,一個N=2M點的DFT通過M次分解後,最後只剩下全部是2點的DFT,2點DFT實際上只有加減運算。但爲了比較,也爲了統一運算的結構,仍用一個係數爲W0N的蝶形運算來表示。
頻率抽取法的流圖是時域抽取法流圖的左右翻轉。下圖是N=8的頻率抽取法FFT流圖。
N=8的按頻率抽取FFT運算流圖
5、實數序列的FFT
以上討論的FFT算法都是複數運算,包括序列x(n)也認爲是複數,但大多數場合,信號是實數序列,任何實數都可看成虛部爲零的複數,例如,求某實信號x(n)的復譜,可認爲是將實信號加上數值爲零的虛部變成覆信號(x(n)+j0),再用FFT求其離散傅里葉變換。這種作法很不經濟,因爲把實序列變成復序列,存儲器要增加一倍,且計算機運行時,即使虛部爲零,也要進行涉及虛部的運算,浪費了運算量。合理的解決方法是利用複數據FFT對實數據進行有效計算,下面介紹兩種方法。
(1)用一個N點FFT同時計算兩個N點實序列的DFT設x(n)、y(n)是彼此獨立的兩個N點實序列,且X(k)=DFT[x(n)],Y(k)=DFT[y(n)],則X(k)、Y(k)可通過一次FFT運算同時獲得。
首先將x(n)、y(n)分別當作一復序列的實部及虛部,令g(n)=x(n)+jy(n)
通過FFT運算可獲得g(n)的DFT值
利用離散傅里葉變換的共軛對稱性
通過g(n)的FFT運算結果G(k),由上式可得到X(k)的值。
通過g(n)的FFT運算結果G(k),由上式也可得
到Y (k) 的值。
作一次N點復序列的FFT,再通過加、減法運算就可以
將X(k)與Y(k)分離出來。顯然,這將使運算效率提高一倍。
6、用FFT計算二維離散的傅里葉變換
前面介紹的FFT原理都是關於一維傅里葉變換的,終於又能切回正題了,數字圖像傅里葉變換明顯是一個二維的離散傅里葉變換,二維信號有圖象信號、時空信號、時頻信號等。二維離散傅里葉變換可用於處理二維離散信號。
二維離散傅里葉變換的定義爲:
二維離散傅里葉變換可通過兩次一維離散傅里葉變換來實現:
1)作一維N點DFT(對每個m做一次,共M次)
2)作M點的DFT(對每個k做一次,共N次)
這兩次離散傅里葉變換都可以用快速算法求得,若M和N都是2的冪,則可使用基二FFT算法,所需要乘法次數爲
而直接計算二維離散傅里葉變換所需的乘法次數爲(M+N)MN,當M和N比較大時用用FFT運算,可節約很多運算量。
三、基於數字圖像處理FFT編程實現
數字圖像處理的快速傅里葉變換操作一共有下面3步
1)獲取圖片的像素的灰度數組
double[] iPix = new double[iw * ih];//輸入灰度
pixels = common.grabber(iImage, iw, ih); //獲得像素數組
for (int i = 0; i < iw*ih; i++) //獲得像素灰度值
iPix[i] = pixels[i]&0xff;
2)對灰度數組進行傅里葉變換
//FFT變換
FFT2 fft2 = new FFT2();
fft2.setData2(iw, ih, iPix);
fftData = fft2.getFFT2();
第二層循環:由於第L級有2^(L-1)個蝶形因子(乘數),第二層循環根據乘數進行控制,保證對於每一個蝶形因子第三層循環要執行一次,這樣,第三層循環在第二層循環控制下,每一級要進行2^(L-1)次循環計算。(L=1,2.......m)
第三層循環:由於第L級共有N/2^L個羣,並且同一級內不同羣的乘數分佈相同,當第二層循環確定某一乘數後,第三層循環要將本級中每個羣中具有這一乘數的蝶形計算一次,即第三層循環每執行完一次要進行N/2^L個碟形計算。
可以得出結論:在每一級中,第三層循環完成N/2^L個碟形計算;第二層循環使第三層循環進行 2^(L-1)次,因此,第二層循環完成時,共進行2^(L-1) *N/2^L=N/2個碟形計算。實質是:第二、第三層循環完成了第L級的計算。
幾個要注意的數據:
① 在第L級中,每個碟形的兩個輸入端相距b=2^(L-1)個點(程序中的bfsize/2)。
② 同一乘數對應着相鄰間隔爲2^L=2*b個點的N/2^L個碟形。
③ 第L級的2^(L-1)個碟形因子W N[p]中的P,可表示爲p = j*2^(m-L),
其中j = 0,1,2,...,2^(L-1)-1
- private Complex[] getData1()
- {
- //蝶形運算
- for (k = 0; k < power; k++)
- {
- for (j = 0; j < 1 << k; j++)
- {
- bfsize = 1 << (power - k);
- for (i = 0; i < bfsize / 2; i++)
- {
- Complex temp1 = new Complex(0, 0);
- Complex temp2 = new Complex(0, 0);
- p = j * bfsize;
- x2[i + p] = temp1.Add(x1[i + p], x1[i + p + bfsize / 2]);
- temp2 = temp1.Sub(x1[i + p], x1[i + p + bfsize / 2]);
- x2[i + p + bfsize / 2] = temp1.Mul(temp2, wc[i * (1 << k)]);
- }
- }
- x = x1;
- x1 = x2;
- x2 = x;
- }
- //重新排序
- for (j = 0; j < count; j++)
- {
- p = 0;
- for (i = 0; i < power; i++)
- if ((j & (1 << i)) != 0)
- p += 1 << (power - i - 1);
- fd1[j] = x1[p];
- }
- return fd1;
- }
3)FFT數據的可視化
使用傅里葉頻譜來可視化FFT變換得到的數據
- //FFT數據可視化
- public int[] toPix(Complex[] fftData, int iw, int ih)
- {
- int[] pix = new int[iw*ih];
- int u, v, r;
- for (int j = 0; j < ih; j++)
- {
- for (int i = 0; i < iw; i++)
- {
- double tem = fftData[i + j * iw].re * fftData[i + j * iw].re
- + fftData[i + j * iw].im * fftData[i + j * iw].im;
- r = (int)(Math.sqrt(tem) / 100);
- if (r > 255) r = 255;
- //移到中心
- if (i < iw / 2) u = i + iw / 2;
- else u = i - iw / 2;
- if (j < ih / 2) v = j + ih / 2;
- else v = j - ih / 2;
- pix[i + j * iw] = 255 << 24|r << 16|r << 8|r;
- }
- }
- return pix;
- }
實驗效果如下: