寫在前面
不是科研狗,基礎理論薄弱,寫的比較匆忙,有理解有誤的地方還請理解和指正。
網上大佬們寫的傅里葉公式推導,證明已經很多了(瑟瑟發抖),我這裏主要是講傅里葉的應用,不涉及公式證明,而是直接拿起公式使用。
由於自己獲取知識也是看大佬們博文理解學習得來的,所以圖片中多少有一些是別人的圖,不過我附上了別人的鏈接。
看完這篇你能收穫到:
1 傅里葉變換的原理
2 傅里葉變換在音頻的應用
3 離散傅里葉變換處理音頻的C語言代碼及講解
背景
最近接觸音視頻處理比較多,就遇到了採集的音頻數據有噪音的情況。可不可以用傅里葉變換去除音頻噪音呢?在此之前,我對傅里葉變換的概念只有一句話,“時域轉頻域的一個玩意兒”。我將音頻時域轉爲頻域,再去除噪音的頻段,不就達到去噪的效果嗎? 嗯,試試就逝世。
嗯,經過幾天的折騰,效果(上圖爲原始音頻信號,下圖爲在頻域去掉高頻正交基後的音頻信號)
結論:
1,有點效果,但噪音只是變小了,沒有去除。使用的是DFT,還有待改進。
2,琢磨了一段時間,還有諸多地方沒有理解到,傅里葉變換是需要長期研究和推敲的學問。把自己理解到的知識點做下筆記,以後要走圖像處理什麼的路子,再來細細研究吧。
一 什麼是傅里葉變換(分析)?
可以參考這篇
在回答這個問題的時候,我們需要先知道什麼是變換?爲什麼要變換?
(1)什麼是變換?
舉個大家常用的例子,兩個向量,a,b在座標系中表示,可以變換爲一組座標。
(2)爲什麼變換?
比如想求c點座標,在圖中是不好處理的問題,但是變換後,c=a+b=(2+1,1+2)=(3,3) ,在數上就很好處理了。總之,一個問題不好處理的時候,換個思路(變換),就能迎刃而解。嗯嗯,有點像線性代數的味道。
傅里葉變換的思路就是時域和頻域的相互變換。
二 時域和頻域
時域和頻域一一對應,通過頻域可以變換爲時域
傅里葉變換類型
傅里葉變換有許多類型,通常不加前綴說的是連續傅里葉變換,離散傅里葉變換常用在計算機處理上,在此之上進行算法改進,降低時間複雜度,叫快速傅里葉變換。
對於時域是週期性連續的函數進行變換叫傅里葉級數
對於時域是非週期連續的函數進行變換叫傅里葉變換
三傅里葉級數(Fourier Series)
所以啊,傅里葉就提出了這句話:任何周期函數都由多個不同的正弦波(sin和cos)疊加構成(滿足狄裏赫利條件情況下)
如圖,頻域由若干個正弦波構成,時域的矩形波可以由多個正弦波疊加構成(當正弦波個數趨向無窮大,逼近爲矩形波,有點極限的趕腳)
周期函數其實說的就是時域信號,多個不同的正弦波說的是頻域的信號。
當然,如上圖所示,一個正弦波的確定,需要知道他的相位,振幅和角頻率,所以時域轉頻域得到的是一系列的初相,振幅和角頻率。
傅里葉說的這句話寫成公式就是
又或者可以這樣寫
這個公式該這麼理解呢?這就需要知道正弦波的正交性。
(1)正弦波的正交性
正弦波自己給自己內積爲1,自己和別人內積爲0,也就是sin(nwt),cos(nwt)都是標準正交基,再加上常量1, 任何的周期函數都可以用着三個表示 。所以呢,只需要將時域的f(t)分別和各種標準正交基做內積,等於0代表沒有該正弦波,否則存在該分量。
什麼是標準正交基
線性代數:在空間中找到一組向量,他們的內積爲0,自身的模爲1時,稱這組向量爲標準正交基,空間中的任意向量都可以由這些標準正交基構成。
什麼是內積?
嗯,你需要學習到晚上2點[手動滑稽]
三傅里葉變換公式
(1) 歐拉公式
但上圖的f(t)沒有和各種正弦波做內積啊,而是和e^-iwt 做內積。其實這是歐拉公式將正弦波與指數之間進行了變換,本質上上圖公式就是和各種正弦波做內積。現在就說說歐拉公式。
數系
回顧下初中知識
自然數:1,2,3,4。。。
整數:-1,1,-2,2.。。。
有理數:-1,2,2/3,4/5.。。
無理數:根號2等
這些一起就構成了實數,如圖 實數軸。
但遇到x^2=-1這樣的解,用實數就無法解決了,引入虛數。虛數+實數=複數,如圖爲複平面,任意點可以用a+bi 來表示
在複平面上畫圓,由三角公式,可以求得a點爲cosx+i *sinx。
複平面上乘法的意義:
乘-1,逆時針旋轉180度。乘-i ,逆時針旋轉90度,複數乘法如圖
現在就可以 拿出歐拉公式了
公式推導就是使用的泰勒公式,e,sin,cos都是十大必背泰勒展開式,兩邊展開是相等的。
到這裏就打住吧,總之我們到這裏能知道傅里葉變換公式e^iwt其實就是與各個sin,cos做內積。具體歐拉公式講解可以看這篇
歐拉公式講解
歐拉恆等式
也就是角度到達180度時候,又回到實數軸
離散傅里葉變換(DFT)
離散傅里葉變換可以參考這篇
離散傅里葉變換講解
離散和連續的區別在於,連續是積分而離散是求和
原理,如下圖,a,b圖是待檢測信號,c,d是3個週期的正弦信號,很顯然a圖含有正弦波,e=ac,將e圖的各點相加,很顯然值是正的,這就說明a圖含頻率爲3的正弦波,f=bd,顯然將f圖中各點相加結果約等於0了,說明b圖不含有周期爲3的正弦波
在計算機中用這個公式更好處理一點
n和N是在一個正弦週期內採樣N個點,採樣間隔爲2pi\N,n用來步進,一次步進2pi\N,最後進行累加求和,就得出了X(k)
最後 離散傅里葉變換完整代碼
1,從文件讀取8000個音頻數據,由於現實中的音頻沒有虛部,所以只設置實部。
2,離散傅里葉變換關鍵處
temp的re就是對應上圖公式的cos,同理im就是對應上圖的sin,每個X[k]進行累加求和
for (int k = 0; k < N; k++)
{
X[k].re = 0;
X[k].im = 0;
for (int n = 0; n < N; n++)
{
temp.re = (float)cos(2 * pi*k*n / N);
temp.im = -(float)sin(2 * pi*k*n / N);
X[k] = complexadd(X[k], complexMult(x[n], temp));
}
}
3,離散傅里葉逆變換就是X(k)乘上e^(j2tkn/N),也就是實部虛部都爲正
temp.re = (float)cos(2 * pi*k*n / N);
temp.im = (float)sin(2 * pi*k*n / N);
x[k] = complexadd(x[k], complexMult(X[n], temp));
4,下圖公式求幅度
在代碼中表示
printf("%d ", int(2.0/N*sqrt(X[k].re*X[k].re + X[k].im*X[k].im)));
//打印的爲幅度
完整代碼
#include <stdio.h>
#include <math.h>
#define pi 3.1415926
struct complex
{
float re;
float im;
};
complex complexadd(complex a, complex b) { //複數加
complex rt;
rt.re = a.re + b.re;
rt.im = a.im + b.im;
return rt;
}
complex complexMult(complex a, complex b) { //複數乘
complex rt;
rt.re = a.re*b.re - a.im*b.im;
rt.im = a.im*b.re + a.re*b.im;
return rt;
}
complex complexSet(complex *a, short *b, int N)//複數設置
{
for (int i = 0; i < N; i++)
{
a[i].re = b[i];
a[i].im = 0;
}
}
//離散傅里葉變換
//X[]標識變換後頻域,x[]爲時域採樣信號
void dft(complex X[], complex x[], int N)
{
complex temp;
int k, n;
for (int k = 0; k < N; k++)
{
X[k].re = 0;
X[k].im = 0;
for (int n = 0; n < N; n++)
{
temp.re = (float)cos(2 * pi*k*n / N);
temp.im = -(float)sin(2 * pi*k*n / N);
X[k] = complexadd(X[k], complexMult(x[n], temp));
}
printf("%d ", int(2.0/N*sqrt(X[k].re*X[k].re + X[k].im*X[k].im)));
//打印的爲幅度
if (k >= 6000)//去除高頻信號
{
X[k].re = 0.0;
X[k].im = 0.0;
}
}
}
//離散傅里葉逆變換
//X[]標識變換後頻域,x[]爲時域採樣信號
void idft(complex X[], complex x[], int N) {
complex temp;
//int k, n;
for (int k = 0; k < N; k++)
{
x[k].re = 0;
x[k].im = 0;
for (int n = 0; n < N; n++)
{
temp.re = (float)cos(2 * pi*k*n / N);
temp.im = (float)sin(2 * pi*k*n / N);
x[k] = complexadd(x[k], complexMult(X[n], temp));
}
x[k].re /= N;
x[k].im /= N;
}
}
#define N 8000//採樣率爲8000
int main() {
complex samples[N], X[N], x[N]; //原始數據,變換後的頻域數據,逆變換後的時域數據
FILE *fp;
FILE *fp2;
short buf[N];
int re=0;
fp=fopen("./ttt.pcm", "rb");
fp2 = fopen("./trans.pcm", "wb");
if (!fp ||!fp2) {
printf("I could not open file.\n");
return 1;
}
else
{
while (fread(buf, sizeof(short)*N,1 , fp) > 0)//末尾數據忽略
{
complexSet(samples, buf, N);
dft(X, samples, N);
idft(X, x, N);
for (int i = 0; i < N; i++)
{
buf[i] = x[i].re;
}
fwrite(buf, sizeof(short)*N,1 , fp2);
}
}
fclose(fp);
fclose(fp2);
return 0;
}