[音頻處理]專科狗眼中的傅里葉變換

寫在前面

不是科研狗,基礎理論薄弱,寫的比較匆忙,有理解有誤的地方還請理解和指正
網上大佬們寫的傅里葉公式推導,證明已經很多了(瑟瑟發抖),我這裏主要是講傅里葉的應用,不涉及公式證明,而是直接拿起公式使用。
由於自己獲取知識也是看大佬們博文理解學習得來的,所以圖片中多少有一些是別人的圖,不過我附上了別人的鏈接。
看完這篇你能收穫到:
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章