【音頻特征提取】傅里葉變換算法源碼學習記錄

目錄

    • 背景
    • 快速理解
      • FFT(快速傅里葉變換)
      • IFFT(逆傅里葉變換)
      • STFT(短時傅里葉變換)
    • 代碼實現
      • FFT源代碼
      • IFFT源代碼
      • FFT、IFFT自己實驗
      • STFT源代碼
      • STFT自己實驗
    • 總結

背景

最近用到了相關操作提取音頻信號特征,故在此總結一下幾個操作
他們分別是 STFT,FFT,IFFT

快速理解

FFT(快速傅里葉變換)

一張全景照片,包含景區所有風景。
FFT 就像是這張全景照片的處理器,它能快速分析整張照片,告訴你主要的景點在哪里。適合對整個信號(全景照片)進行整體的頻率分析。

IFFT(逆傅里葉變換)

一張P好的全景照片,包含景區所有風景。
IFFT 操作就像是P圖,給原始圖片上添加了各種貼紙,字體,濾鏡,可能有很多個圖層,當你最后點下了保存按鈕,多個圖層合成為一張圖片的過程就是IFFT過程。

STFT(短時傅里葉變換)

一本相冊,一系列連續的照片,每一張照片都展示了風景的一部分。
STFT 就像是分析這本相冊,它把每一張照片(時間片段)單獨分析,告訴你每一張里有哪些景點。這就能讓你知道在不同的時間點,風景(頻率成分)是如何變化的。

代碼實現

FFT源代碼

numpy庫作為基準來研究,版本1.24.3,只貼出源碼中與FFT操作相關的部分

# 根據不同的 norm 計算不同的歸一化因子
def _get_forward_norm(n, norm):if n < 1:raise ValueError(f"Invalid number of FFT data points ({n}) specified.")if norm is None or norm == "backward":return 1elif norm == "ortho":return sqrt(n)elif norm == "forward":return nraise ValueError(f'Invalid norm value {norm}; should be "backward",''"ortho" or "forward".')# 計算之前的前處理函數                     
def _raw_fft(a, n, axis, is_real, is_forward, inv_norm):# a: 輸入的數組 # n: 變換軸長度,如果指定的 n 小于輸入的長度,則會截取輸入;如果大于輸入的長度,則在末尾用零填充;默認和a一樣長。# axis: 指定計算FFT的軸# is_real 是否是實數# is_forward 是否正向FFT# inv_norm 歸一化因子axis = normalize_axis_index(axis, a.ndim)if n is None:n = a.shape[axis]fct = 1/inv_normif a.shape[axis] != n: # 調整輸入數組的形狀,長的截斷 短的補0s = list(a.shape)index = [slice(None)]*len(s)if s[axis] > n:index[axis] = slice(0, n)a = a[tuple(index)]else:index[axis] = slice(0, s[axis])s[axis] = nz = zeros(s, a.dtype.char)z[tuple(index)] = aa = zif axis == a.ndim-1: # 判斷變換軸是否為數組的最后一個軸r = pfi.execute(a, is_real, is_forward, fct) # FFT 變換else:a = swapaxes(a, axis, -1)r = pfi.execute(a, is_real, is_forward, fct)r = swapaxes(r, axis, -1)return r@array_function_dispatch(_fft_dispatcher)
def fft(a, n=None, axis=-1, norm=None): # a: 輸入的數組 # n: 變換軸長度,如果指定的 n 小于輸入的長度,則會截取輸入;如果大于輸入的長度,則在末尾用零填充;默認和a一樣長。# axis: 指定計算FFT的軸# norm: 用于指定正向/反向變換的歸一化模式。a = asarray(a)	# 將輸入統一轉換為 numpy 數組if n is None:n = a.shape[axis]inv_norm = _get_forward_norm(n, norm)	# 計算變換的歸一化因子output = _raw_fft(a, n, axis, False, True, inv_norm) # 計算FFT之前的前處理return output

這段代碼的入口在fft函數,一路看下去發現在我們可見的代碼范圍內,只是針對輸入的數組做了一些預處理,并沒有真正FFT計算的部分,真正的計算部分由r = pfi.execute(a, is_real, is_forward, fct)調用,我們跟進去之后代碼如下,是由c/c++實現的底層庫了:

# encoding: utf-8
# module numpy.fft._pocketfft_internal
# from C:\Users\admin\.conda\envs\pann-cpu-py38\lib\site-packages\numpy\fft\_pocketfft_internal.cp38-win_amd64.pyd
# by generator 1.147
# no doc
# no imports# functionsdef execute(*args, **kwargs): # real signature unknownpass# no classes
# variables with complex values__loader__ = None # (!) real value is '<_frozen_importlib_external.ExtensionFileLoader object at 0x00000177A3437A00>'__spec__ = None # (!) real value is "ModuleSpec(name='numpy.fft._pocketfft_internal', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x00000177A3437A00>, origin='C:\\\\Users\\\\admin\\\\.conda\\\\envs\\\\pann-cpu\\\\lib\\\\site-packages\\\\numpy\\\\fft\\\\_pocketfft_internal.cp38-win_amd64.pyd')"

IFFT源代碼

這部分同樣使用numpy庫作為基準來研究,版本1.24.3

# 根據不同的 norm 計算不同的歸一化因子
def _get_backward_norm(n, norm):if n < 1:raise ValueError(f"Invalid number of FFT data points ({n}) specified.")if norm is None or norm == "backward":return nelif norm == "ortho":return sqrt(n)elif norm == "forward":return 1raise ValueError(f'Invalid norm value {norm}; should be "backward", ''"ortho" or "forward".')# 計算之前的前處理函數   
def _raw_fft(a, n, axis, is_real, is_forward, inv_norm):axis = normalize_axis_index(axis, a.ndim)if n is None:n = a.shape[axis]fct = 1/inv_normif a.shape[axis] != n:s = list(a.shape)index = [slice(None)]*len(s)if s[axis] > n:index[axis] = slice(0, n)a = a[tuple(index)]else:index[axis] = slice(0, s[axis])s[axis] = nz = zeros(s, a.dtype.char)z[tuple(index)] = aa = zif axis == a.ndim-1:r = pfi.execute(a, is_real, is_forward, fct)else:a = swapaxes(a, axis, -1)r = pfi.execute(a, is_real, is_forward, fct)r = swapaxes(r, axis, -1)return r      @array_function_dispatch(_fft_dispatcher)
def ifft(a, n=None, axis=-1, norm=None):# a: 輸入的數組 # n: 變換軸長度,如果指定的 n 小于輸入的長度,則會截取輸入;如果大于輸入的長度,則在末尾用零填充;默認和a一樣長。# axis: 指定計算FFT的軸# norm: 用于指定正向/反向變換的歸一化模式。a = asarray(a)	# 將輸入統一轉換為 numpy 數組if n is None:n = a.shape[axis]inv_norm = _get_backward_norm(n, norm)output = _raw_fft(a, n, axis, False, False, inv_norm)return output

通過閱讀源碼,發現 FFT和 IFFT的代碼實現幾乎一致(僅有細節差別),都是通過改變傳入_raw_fft的參數is_forward來確定做 FFT還是 IFFT。

FFT、IFFT自己實驗

這里繪制的結果是三部分,分別是原始波形、FFT結果、IFFT結果

import librosa
import numpy as np
import matplotlib.pyplot as plt# 加載音頻文件
file_path = r'C:\Users\admin\Desktop\自己的分類數據\5.0\unbalanced\normal_audio\14-2024.02.25.10.48.37_(2.836402877697843, 7.836402877697843).wav'
y, sr = librosa.load(file_path, sr=None)# 計算 FFT
fft_result = np.fft.fft(y)# 計算 IFFT
ifft_result = np.fft.ifft(fft_result)# 繪制原始音頻信號
plt.figure(figsize=(14, 5))
plt.subplot(3, 1, 1)
plt.title('Original Audio Signal')
plt.plot(y)
plt.xlabel('Time')
plt.ylabel('Amplitude')# 繪制 FFT 結果(只取前一半,因為FFT對稱)
plt.subplot(3, 1, 2)
plt.title('FFT Result')
plt.plot(np.abs(fft_result)[:len(fft_result) // 2])
plt.xlabel('Frequency Bin')
plt.ylabel('Magnitude')# 繪制 IFFT 結果
plt.subplot(3, 1, 3)
plt.title('Reconstructed Signal from IFFT')
plt.plot(ifft_result.real)
plt.xlabel('Time')
plt.ylabel('Amplitude')plt.tight_layout()# 保存圖片
plt.savefig('fft_comparison.png')

實驗結果

STFT源代碼

librosa庫為基準來研究,版本0.9.1,仔細閱讀學習了一下源代碼,發現有很多寫法還是十分精妙的,怪不得計算效率會高很多。

@deprecate_positional_args
@cache(level=20)
def stft(y,						# 待處理音頻信號*,						# 分隔符,此分隔符之后的參數都要鍵值對輸入n_fft=2048,				# * 在時間維度上的窗口大小,用于FFT計算hop_length=None,		# 每個幀之間的跳躍長度 默認n_fft / 4win_length=None,		# * 在音頻頻率維度上的窗口大小,用于窗口函數計算window="hann",			# 選用的窗口函數:對當前窗口信號進行加權處理,減少頻譜泄漏和邊界效應center=True,			# 控制每個幀是否在中心對齊,否則左邊界對齊dtype=None,				# 輸出數組的數據類型pad_mode="constant",	# 填充模式,用于確定如何處理信號邊緣
):# By default, use the entire frame 默認窗口長度 win_length 的設置if win_length is None:win_length = n_fft# Set the default hop, if it's not already specified 默認跳躍長度 hop_length 的設置if hop_length is None:hop_length = int(win_length // 4)# Check audio is valid 音頻有效性檢查util.valid_audio(y, mono=False)# 獲取窗口函數 fftbins為True則會將窗口函數填充到 win_length 相同長度fft_window = get_window(window, win_length, fftbins=True)# Pad the window out to n_fft size 將窗口函數填充到 n_fft相同長度(為了處理win_length != n_fft的情形)fft_window = util.pad_center(fft_window, size=n_fft)# Reshape so that the window can be broadcast 重新構造fft_window的形狀以便和 y 進行計算# ndim: fft_window 擴展之后的總維度數,axes:不變的軸(將數據填充到-2軸以外的其他位置上,一般音頻信號-2是時間軸)# eg. y(2,4096) fft_window(1024,),設置 ndim=3, axes=-2#     因此擴展后的總維度為3,同時fft_window需要-2維度保持原有的值不變,其余位置進行廣播,得到(2, 1024, 4096)fft_window = util.expand_to(fft_window, ndim=1 + y.ndim, axes=-2)# Pad the time series so that frames are centered 中心填充if center:if n_fft > y.shape[-1]:warnings.warn("n_fft={} is too small for input signal of length={}".format(n_fft, y.shape[-1]),stacklevel=2,)padding = [(0, 0) for _ in range(y.ndim)]padding[-1] = (int(n_fft // 2), int(n_fft // 2))y = np.pad(y, padding, mode=pad_mode)elif n_fft > y.shape[-1]:	# 如果 n_fft 大于輸入信號的長度,報錯raise ParameterError("n_fft={} is too large for input signal of length={}".format(n_fft, y.shape[-1]))# Window the time series. 按照n_fft和hop_length進行滑動窗口從時間維度切分y,y_frames(num_frames, frame_length)# 補充說明:# STFT 通常會生成一個三維矩陣,維度一般為 (batch_size, num_frames, num_bins)# batch_size:批次大小,表示處理多個音頻片段# num_frames:時間幀數,表示音頻片段被分割成多少個時間窗口# num_bins(frame_length):頻率成分數,表示每個時間窗口內計算的頻率分量數y_frames = util.frame(y, frame_length=n_fft, hop_length=hop_length)fft = get_fftlib() # 獲取FFT庫對象,便于后續操作if dtype is None:	# 設置數據類型dtype = util.dtype_r2c(y.dtype)# Pre-allocate the STFT matrix 預分配STFT矩陣shape = list(y_frames.shape)shape[-2] = 1 + n_fft // 2 # 因為FFT結果是鏡像對稱,所以存儲空間減半,+1是因為FFT結果是復數stft_matrix = np.empty(shape, dtype=dtype, order="F") # 預分配內存,empty函數創建的數組所有元素都沒有默認值# 1 np.prod(stft_matrix.shape[:-1]) * stft_matrix.itemsize # 計算出所有時間窗口和所有批次中,一個頻率成分所需的總內存(一列)# stft_matrix.itemsize 返回 stft_matrix 中每個元素的字節大小# stft_matrix.shape[:-1] 返回除了最后一個維度的剩余形狀,eg.(1024,256)[:-1] 返回 1024# np.prod(...) 求出...的所有維度的乘積(總元素個數)# 2 計算出需要的列數 util.MAX_MEM_BLOCK // ...# util.MAX_MEM_BLOCK 代表限定的最大可用內存大小# 補充說明:# 計算結果是列數的原因:在stft中,橫軸x代表時間,縱軸y代表頻率,因此每一列代表一個時間窗口的內存大小n_columns = util.MAX_MEM_BLOCK // (np.prod(stft_matrix.shape[:-1]) * stft_matrix.itemsize)n_columns = max(n_columns, 1)# 分塊計算for bl_s in range(0, stft_matrix.shape[-1], n_columns):bl_t = min(bl_s + n_columns, stft_matrix.shape[-1]) # 結束索引# 計算當前塊的STFTstft_matrix[..., bl_s:bl_t] = fft.rfft(fft_window * y_frames[..., bl_s:bl_t], axis=-2)return stft_matrix # 橫軸是時間,縱軸是頻率

STFT自己實驗

了解這些之后自己寫個例子試試看,stft_result 是得到的直接計算結果,D是全部求絕對值之后的結果,DB是為了更好的可視化做的后處理,我加上原始波形圖,三者放在一起比較,保存了一張結果。

import librosa.display
import matplotlib.pyplot as plt
import numpy as np# 加載音頻文件
audio_file = r'C:\Users\admin\Desktop\test.wav'
y, sr = librosa.load(audio_file)# 參數設置
n_fft = 2048  # FFT窗口大小
hop_length = 512  # 幀之間的跳躍長度# 計算STFT
stft_result = librosa.stft(y, n_fft=n_fft, hop_length=hop_length)# 獲取幅度譜
D = np.abs(stft_result)# 將幅度譜轉換為分貝單位
DB = librosa.amplitude_to_db(D, ref=np.max)# 繪制原始波形
plt.figure(figsize=(10, 8))plt.subplot(3, 1, 1)
librosa.display.waveshow(y, sr=sr)
plt.title('Waveform')
plt.xlabel('Time')# 繪制原始幅度譜 D
plt.subplot(3, 1, 2)
librosa.display.specshow(D, sr=sr, hop_length=hop_length, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f')
plt.title('STFT Magnitude Spectrum (D)')
plt.xlabel('Time')
plt.ylabel('Frequency')# 繪制分貝單位的幅度譜 DB
plt.subplot(3, 1, 3)
librosa.display.specshow(DB, sr=sr, hop_length=hop_length, x_axis='time', y_axis='log')
plt.colorbar(format='%+2.0f dB')
plt.title('STFT Magnitude Spectrum (DB)')
plt.xlabel('Time')
plt.ylabel('Frequency')plt.tight_layout()# 保存圖片
plt.savefig('stft_comparison.png')

結果展示

總結

FFT是時域信號——>頻域信號,IFFT是頻域信號——>時域信號,輸入是一維音頻信號的前提下,這兩個操作得到的結果都是一維的特征。

一般 IFFT都會配合 FFT操作一起使用,先使用FFT,然后從頻率角度處理音頻信號,最后使用 IFFT操作重新將信號恢復。

STFT是時域信號——>時間-頻率域信號,輸入是一維音頻信號的前提下,這個操作得到的結果都是二維的特征。

二者的使用場景根本區別在于是否需要了解頻率隨著時間的變化,如果需要就使用 STFT,否則使用 FFT + IFFT

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/44361.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/44361.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/44361.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Vue3 根據相對路徑加載vue組件

一、設置動態組件加載器 1、"DynamicFormLoader.vue" <template><div><component :is"formComponent" v-if"formComponent" /></div> </template><script setup> import { ref, watch } from vue; import …

如何測試掃地機器人的穩定性

測試掃地機器人的穩定性是一個綜合性的過程&#xff0c;旨在確保機器人在各種環境和條件下都能穩定運行。以下是一些關鍵的測試步驟和方面&#xff1a; 清潔效果測試 目的&#xff1a;評估掃地機器人在不同地面和污漬類型上的清潔能力。 方法&#xff1a; 使用不同類型的地面&…

標簽印刷檢測,如何做到百分百準確?

印刷標簽是一種用于標識、識別或包裝產品的平面印刷制品。這些標簽通常在紙張、塑料膜、金屬箔等材料上印刷產品信息、條形碼、圖像或公司標識&#xff0c;以便于產品識別和管理。印刷標簽有各種形狀、尺寸和材質&#xff0c;可以根據具體需求進行定制設計。常見的印刷標簽包括…

FlutterFlame游戲實踐#15 | 生命游戲 - 演繹啟動

theme: cyanosis 本文為稀土掘金技術社區首發簽約文章&#xff0c;30天內禁止轉載&#xff0c;30天后未獲授權禁止轉載&#xff0c;侵權必究&#xff01; Flutter\&Flame 游戲開發系列前言: 該系列是 [張風捷特烈] 的 Flame 游戲開發教程。Flutter 作為 全平臺 的 原生級 渲…

android 居中對齊

在 Android 中&#xff0c;要使 LinearLayout 中的內容居中對齊&#xff0c;你可以通過設置 android:gravity 屬性或使用 android:layout_gravity 屬性來實現。這兩個屬性的使用取決于你希望對齊的內容是 LinearLayout 內部的子視圖還是 LinearLayout 本身相對于其父布局的對齊…

4.3 設備管理

大綱 設備分類 輸入輸出 虛設備和SPOOLING技術

管理客戶的10個CRM系統技巧

客戶是企業生存和發展的基石。為此&#xff0c;客戶關系管理系統&#xff08;CRM&#xff09;應運而生&#xff0c;旨在幫助企業實現大規模的個性化客戶接觸&#xff0c;并通過定制化的互動增強客戶忠誠度&#xff0c;從而推動企業的持續增長。 然而&#xff0c;引入CRM系統并…

vue3對比 Setup、Render、SFC 從 vue 底層實現和性能開銷上全面分析三者區別及優略

vue3 中對比 Setup、Render、SFC 從 vue 底層實現和性能開銷上全面分析三者區別及優略 /* setup 方式 */ export const Setup defineComponent({setup() {const handleChange (v: any) > {};return () > {return (<div><button onClick{handleChange}>Tes…

AD確定板子形狀

方法1 修改柵格步進值&#xff0c;手動繪制 https://cnblogs.com/fqhy/p/13768031.html 方法2 器件擺放確定板子形狀 https://blog.csdn.net/Mark_md/article/details/116445961

Java實戰:尋找完美數

文章目錄 一、何謂完美數二、尋找完美數&#xff08;一&#xff09;編程思路&#xff08;二&#xff09;編寫程序&#xff08;三&#xff09;運行程序 三、實戰小結 一、何謂完美數 完美數是一種特殊的自然數&#xff0c;它等于其所有正除數&#xff08;不包括其本身&#xff…

百問網全志D1h開發板MIPI屏適配

MIPI屏適配 100ASK-D1-H_DualDisplay-DevKit V11 1. 顯示適配 1.1 修改設備樹 1.1.1 修改內核設備樹 進入目錄&#xff1a; cd /home/ubuntu/tina-d1-h/device/config/chips/d1-h/configs/nezha/linux-5.4修改board.dts: &lcd0 {lcd_used <1>;lcd…

類的生命周期詳解

第1部分&#xff1a;引言 1.1 面向對象編程簡介 面向對象編程&#xff08;OOP&#xff09;是一種編程范式&#xff0c;它使用“對象”來設計軟件。對象可以包含數據&#xff08;通常稱為屬性或字段&#xff09;和代碼&#xff08;通常稱為方法或函數&#xff09;。OOP的核心概…

Vue 項目中 history 路由模式的使用

在最近幫客戶開發的一個項目中&#xff0c;由于項目的特殊性&#xff0c;需要用到 Vue 中的 history路由模式。該模式使用時會涉及到“上傳白屏”和“刷新 404 問題”。在幫助客戶解決這兩個問題的過程中&#xff0c;總結問題的解決方案并記錄下來&#xff0c;希望能夠保留這篇…

眼外傷險失明輾轉成都愛爾眼科就醫保視力,患者復查送錦旗!

近日患者王先生到成都愛爾眼科醫院進行硅油取出后的二次復查&#xff08;硅油為眼底病手術中一種“填充物”&#xff09;&#xff0c;他激動地為蔡裕主任獻上錦旗&#xff0c;感謝醫生的救治避免了失明。 意外發生在半年之前&#xff0c;王先生不慎滑倒右眼磕碰到茶幾邊緣&…

【前端從入門到精通:第九課:CSS3新增屬性及伸縮盒布局】

彈性盒模型 介紹 伸縮盒模型也叫彈性盒模型&#xff0c;或flex。它決定一個盒子在其它盒子中的分布&#xff0c;以及如何處理可用的空間。使用該模型&#xff0c;可以輕松的創建“自適應”瀏覽器窗口的流動布局。 flexbox是一個很新的東西&#xff0c;在w3c希望可以使用flexbo…

力扣1472.設計瀏覽器歷史記錄

力扣1472.設計瀏覽器歷史記錄 用雙指針記錄歷史記錄 以及棧頂高度移動時會直接把之前的記錄消掉 class BrowserHistory {int pos-1;int top0;string history[5010];public:BrowserHistory(string homepage) {visit(homepage);}void visit(string url) {pos ;top pos;histor…

[激光原理與應用-103]:配電箱的柜門與柜體為啥要接一根導線?

目錄 一、概述 1.1、電氣安全 1.2、減少電磁干擾 1.3、方便維修和更換 1.4、其他因素 一、鉸鏈的材質 二、鉸鏈的設計 三、結論 二、正確連接銅線的步驟 1、選擇正確的銅線 2、清潔連接處 3、正確連接 4、檢查連接是否牢固 參考&#xff1a; 一、概述 配電機柜上…

探索AI藝術的無限可能:SD模型與大模型的融合之美

藝術與科技的結合從未像今天這樣緊密。AI繪畫技術正以驚人的速度改變著我們創作和欣賞藝術的方式。在這場革命中&#xff0c;Stable Diffusion&#xff08;SD&#xff09;模型扮演了至關重要的角色。 &#x1f31f; SD模型&#xff1a;藝術創作的新維度 SD模型以其生成高質量圖…

力扣682.棒球比賽

力扣682.棒球比賽 數組模擬棧記錄分數 class Solution {public:int calPoints(vector<string>& ops) {int res0;vector<int> points;for(auto &op:ops){int n points.size();char c op[0];if(c ){res points[n-1] points[n-2];points.push_back(po…

在數據庫設計中,選擇自增 ID 還是 GUID?這篇文章講清楚

在數據庫設計中&#xff0c;選擇自增 ID 還是 GUID 取決于具體的應用場景和需求。 自增 ID 的優點&#xff1a; 性能較好&#xff1a;在插入數據時&#xff0c;自增 ID 的生成速度通常較快&#xff0c;因為數據庫可以高效地順序分配新的 ID 值。存儲空間小&#xff1a;通常只…