了解如何在 Python 中创建 DSP 管道,并将其转换为使用 C/C++ 和 Arm 的 CMSIS-DSP 库在基于Arm的 Cortex-M 上运行。
作者:Sandeep Mistry, Henri Woodcock (代表Arm软件开发者团队)
介绍
当机器学习(ML)应用于音频系统时,通常使用数字信号处理(DSP)技术将输入音频信号转换为2D“图像”,以便可以使用计算机视觉技术对音频进行分类。现实世界中的例子包括音频或语音识别,以及关键词识别。
本指南将使用Python和NumPy代码示例向您介绍这些系统中使用的DSP技术。一旦您熟悉了这些DSP技术,我们将介绍如何在Python中使用Arm的CMSIS-DSP库,使用定点数学执行相同的操作。
最后,我们将演示如何在C中创建等效管道,以便将其部署到基于Arm Cortex-M的微控制器(MCU)。在基于Arm Cortex-M的系统上使用定点数学可以大大减少内存使用,同时提高运行时性能!
本指南的Jupyter笔记本版本也可以在GitHub上找到(https://github.com/ArmDeveloperEcosystem/fixed-point-dsp-for-data-scientists/blob/main/fixed_point_dsp_for_data_scientists.ipynb),也可以使用Google Colab访问(https://colab.research.google.com/github/ArmDeveloperEcosystem/fixed-point-dsp-for-data-scientists/blob/main/fixed_point_dsp_for_data_scientists.ipynb)。
目录
- 音频频谱图
- Python中的音频频谱图
- 基于CMSIS-DSP的音频频谱图
- 不同音频声音的频谱图示例
- 将CMSIS-DSP代码从Python移植到C
- 结论
音频频谱图
在本笔记本中,我们将在NumPy和CMSIS-DSP中开发音频频谱图管道。这是一个用于音频数据的通用预处理管道。原始音频样本被转换为音频信号的视觉表示,该音频信号显示频率随时间的变化。它通常表示为二维图像。
要更深入地解释频谱图,可以查看TensorFlow音频识别教程。
为了将音频信号转换为频域,使用快速傅里叶变换(FFT)。这会将振幅随时间变化表示的音频信号转换为频域,因此您可以了解信号中包含的频率。然而,当对整个音频信号执行FFT时,您将丢失该音频信号的时域信息,为了抵消这一点,我们将音频分解为单独的段。
频谱图步骤
创建频谱图可分为以下步骤:
- 音频信号被分解成称为“窗口”的较短段。这意味着当我们以后应用FFT时,我们仍然保留一些时间信息,因为我们知道每个窗口发生在什么时间点。将音频信号拆分为窗口时,窗口之间通常会有一些重叠,以帮助保持输出更连续。
- 然后对每个窗口应用窗口功能。将信号分割成重叠的窗口将使音频信号出现不连续性,而这将无法准确表示真实世界的音频信号。为了减少这种影响,窗口功能将通过将窗口乘以0和1之间的平滑对称曲线来平滑每个窗口振幅的边缘。
- 然后使用FFT将每个窗口转换为频域。由于FFT产生复数输出,因此取该输出的绝对值以保留振幅信息,但去除复数。
- 最后创建一个称为频谱图的二维数组,其大小(窗口数、FFT箱数)。
我们在下面为您选择了一些音频输入示例及其音频频谱图表示:
Python中的音频频谱图
现在您已经大致了解了音频频谱图是什么,我们将介绍如何使用Python和Numpy从预先录制的波形文件创建一个音频频谱图。
安装Python模块
您需要使用pip安装NumPy、Matplotlib、SciPy和requests Python模块
pip install numpy scipy matplotlib requests
下载示例音频文件
我们将使用“ESC-50:环境声音分类数据集”GitHub repo(https://github.com/karolpiczak/ESC-50)中的“door knocking“.wav文件作为笔记本中DSP管道的输入信号。您可以使用Python中的请求库从GitHub下载.wav文件:
import requests
wave_file = "1-103995-A-30.wav"
response = requests.get(
f"https://github.com/karolpiczak/ESC-50/raw/master/audio/{wave_file}"
)
with open(wave_file, "wb") as f:
f.write(response.content)
download_wav_file.py hosted with ❤ by GitHub
从示例音频文件中读取数据
SciPy的SciPy.io.wavfile.read(…)API现在可以用来读取.wav文件中的音频样本。
.wav文件的采样率为44.1 kHz,这意味着每1秒的音频由44100个样本表示。由于44.1 kHz采样率包含的分辨率比我们需要的分辨率高,我们将使用SciPy的SciPy.signal.resample(…)API将音频数据的采样率从44.1 kHz降低到16 kHz,以减少分析中使用的采样数。
import numpy as np
from scipy.io import wavfile
from scipy import signal
def read_wave_file(file, sample_rate):
# read sample rate and data from wave file
original_sample_rate, orginal_audio_data = wavfile.read(file)
# normalize input to floating-point value, if needed
if orginal_audio_data.dtype is np.float32:
audio_data_float32 = orginal_audio_data
else:
audio_data_float32 = orginal_audio_data / np.iinfo(orginal_audio_data.dtype).max
# downsample to desired sample rate
audio_data = signal.resample(audio_data_float32, (len(audio_data_float32) // original_sample_rate) * sample_rate)
return audio_data
audio_sample_rate = 16000
audio_samples = read_wave_file(wave_file, audio_sample_rate)
print(f"Successfully read {len(audio_samples)} samples from '{wave_file} with sample rate of {audio_sample_rate}")
read_wav_file.py hosted with ❤ by GitHub
现在我们已经从.wav文件中读取了音频样本,让我们使用Matplotlib绘制音频信号随时间的变化:
您还可以在计算机上下载并收听1–103995-A-30.wav文件。
根据整个音频信号的图表,播放声音时,我们知道在开始时大约有1秒的声音,然后是静音。让我们使用Matplotlib放大并仅绘制音频信号随时间变化的前1秒,以便仔细查看:
窗口化
现在我们可以将音频样本分割为重叠窗口。通常选择2的幂,例如16、32、64、128、256、512、1024或2048作为窗口大小。我们将使用256的窗口大小,同时步进音频信号128个样本。
当前窗口以绿色突出显示在整个信号的上方,以蓝色突出显示。您可以看到第一个窗口从0到256,第二个窗口从128到384(128+256),第三个窗口从256到512,第四个窗口从384到640。
.wav文件的窗口总数可按如下方式计算:
number_of_windows = (len(audio_samples) - window_size) // step_size
窗口功能和汉宁窗口
在处理连续信号(如音频)时,在应用FFT变换之前应用窗口函数,以便音频片段的边缘更平滑。窗口功能是一种(通常)范围为(0–1)的功能,可按如下方式应用于窗口:
𝑦 = 𝑤𝑖𝑛𝑑𝑜𝑤_𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛 × 𝑤𝑖𝑛𝑑𝑜𝑤
最常见的窗口功能之一(本例中使用的功能)是Hanning窗口。NumPy有一个内置的方法来计算任意窗长的Hanning窗:
import numpy as np
hanning_window = np.hanning(window_size)
让我们使用Matplotlib绘制汉宁窗口:
现在,可以使用以下方法将汉宁窗口应用于第一个音频信号窗口:
window_1 = audio_samples[0:window_size]
processed_window_1 = hanning_window * window_1
然后,我们可以使用Matplotlib绘制第一个窗口和已处理的窗口
您可以看到,在整个窗口周期内,处理窗口的振幅从未超过汉宁窗口的值。
让我们扩展我们的图,在前几个窗口中循环。
FFT
对于每个处理过的窗口,可以使用NumPy的np.FFT.rfft(…)API计算FFT,并使用np.absolute(…)获取FFT的绝对值:
# Calculate the FFT.
fft_1 = np.fft.rfft(processed_window_1)
# Take the absolute value.
fft_bins_1 = np.absolute(fft_1)
然后,我们可以使用Matplotlib绘制FFT bins 。
让我们绘制前4个窗口随时间变化的FFT,以便我们可以看到音频信号的频率内容随时间的变化:
程序
我们可以创建一个音频信号频域随时间变化的2D图,作为热图,也称为谱图,而不是为每个窗口周期创建单独的FFT图!该频谱图显示x轴上的时间,y轴上的频率,最后是带有颜色的振幅。
让我们以重叠的步长和窗口大小遍历所有样本,以计算随时间变化的信号频率,并显示为频谱图。这里我们计算1秒音频中的窗口数。
# Putting this all together
window_size = 256
step_size = 128
# Calculate the number of windows
number_of_windows = int(1 + (audio_sample_rate - window_size) // step_size)
# Calculate the FFT Output size
num_fft_bins = int(window_size // 2 + 1)
# Create an empty array to hold the Spectrogram
spectrogram = np.empty((number_of_windows, num_fft_bins))
# Apply hanning window and apply fft
for index in range(number_of_windows):
window_start = index * step_size
window_end = window_start + window_size
# Take the window from the waveform.
window = audio_samples[window_start:window_end]
# Apply the Hanning Window.
processed_window = window * hanning_window
# Calculate the FFT
fft = np.fft.rfft(processed_window)
# Take the absolute value of the FFT and add to the Spectrogram.
spectrogram[index] = np.abs(fft)
create_spectrum.py hosted with ❤ by GitHub
现在我们已经计算了音频信号的频谱图,让我们绘制它:
从频谱图中,我们可以清楚地看到每个knock的 5个感兴趣的区域,频率低于4000 Hz的区域更为普遍。
让我们放大频谱图的下半部分,仔细观察:
让我们再画一个图,但将时域中的信号添加到频谱图上方。这样,我们可以看到信号的振幅和频率计数与时间的相关性。
回顾
让我们回顾一下我们迄今为止所取得的成就:
- 使用请求库从GitHub下载敲门声的.wav文件。
- 使用SciPy库以44.1 kHz的频率从.wav文件中读取音频样本,并对其重新采样,以获得16 kHz的采样率。
- 了解什么是窗口以及为什么需要窗口功能,如汉宁窗口。
- 学习了如何在特定窗口周期内创建音频信号的FFT。
- 通过跨越音频信号,应用汉宁窗口,并对每个窗口周期使用FFT函数,创建音频信号的频谱图表示。
基于CMSIS-DSP的音频频谱图
现在您已经了解了如何使用NumPy创建音频频谱图,我们可以使用带有定点数学的CMSIS-DSP Python包装器来做同样的事情。
定点数学和CMSIS-DSP简介
与浮点运算不同,定点数学数表示精度较低的实数,因为它们有固定位数来表示小数点前后的数字。例如,如果我们有1.15定点格式的16位数字,16位值可以表示-1和1之间的值,固定精度为1/2^15=0.000030517578125。
Arm Cortex-M处理器在基于整数的计算方面比浮点运算更快。Arm Cortex-M4、Cortex-M7和后者处理器支持单指令多数据(SIMD)指令,允许它们一次对多个值执行相同的指令。CMSIS-DSP库利用这些SIMD指令进行更高性能的DSP处理。
Arm的CMSIS-DSP库包含针对Arm Cortex-M处理器的常见DSP操作的高度优化实现。
CMSIS-DSP Python包装器
通常,在ARM CORTEX-M设备上使用C或C++的CMIS-DSP库。然而,CMSIS-DSP团队已经创建了一个Python包装器来利用Python中的这些DSP函数。
该库包含针对Arm Cortex-M处理器的高度优化的DSP函数实现和针对非Arm处理器的通用C实现。如果您在PC上使用Arm CMSIS-DSP Python包装器(可能基于x86),则从输入和输出的角度来看,这些函数的行为将与在Arm处理器上的行为相同,但是您可能看不到运行时性能的提高。
让我们继续安装CMSIS-DSP Python包装器:
pip install git+https://github.com/ARM-software/CMSIS_5.git@5.8.0#egg=CMSISDSP\&subdirectory=CMSIS/DSP/PythonWrapper
量化输入信号
我们可以使用CMSIS-DSP的arm_float_to_q15(..)函数将浮点值转换为16位定点(q15)值:
from cmsisdsp import arm_float_to_q15
audio_samples_q15 = arm_float_to_q15(audio_samples)
现在,让我们沿着原始NumPy值绘制定点值:
您可以看到信号的形状与使用NumPy创建的信号相似,但是Y轴上的值范围为-32768到32767,而不是0到1。
CMSIS-DSP中的汉宁窗
CMSIS-DSP没有为特定窗口大小创建汉宁窗口的内置功能。但是,我们可以利用内置的arm_cos_f32(…)和arm_float_to_q15(…)API来创建一个定点汉宁窗口。让我们使用Hann函数的公式:
# Generate the Hanning Window
from numpy import pi as PI
from cmsisdsp import arm_cos_f32, arm_float_to_q15
hanning_window_f32 = np.zeros(window_size)
for i in range(window_size):
hanning_window_f32[i] = 0.5 * (1 - arm_cos_f32(2 * PI * i / window_size ))
hanning_window_q15 = arm_float_to_q15(hanning_window_f32)
create_hanning_window_cmsisdsp.py hosted with ❤ by GitHub
现在,让我们使用Matplotlib沿着NumPy Hanning窗口绘制Q15 Hanning窗口:
您可以看到它的形状与使用NumPy创建的形状相似,但是值的范围是0到32767,而不是0到1。
要像在NumPy中一样应用汉宁窗口,我们可以使用arm_mult_q15(…)乘法函数将两个长度相等的向量相乘在一起。
from cmsisdsp import arm_mult_q15
window_1_q15 = audio_samples_q15[:window_size]
# Multiply the signal by the window
processed_window_1_q15 = arm_mult_q15(window_1_q15, hanning_window_q15)
apply_hanning_window_cmsisdsp.py hosted with ❤ by GitHub
现在让我们看一下应用q15格式的汉宁窗口后的第一个窗口:
让我们将其与NumPy值并排绘制:
用CMSIS-DSP实现FFT
CMSIS-DSP为各种数据类型提供了许多FFT功能:q15、q31和f32。在本例中,我们将使用实FFT函数。要使用CMSIS-DSP的Q15 RFFT函数,我们首先需要创建一个arm_RFFT_实例_Q15实例,并使用arm_RFFT_init_Q15(…)函数对其进行初始化。之后,该过程类似于NumPy,您可以使用arm_rfft_q15(…)函数来计算FFT,使用arm_cmplx_mag_q15(…)来计算FFT的魔力。
from cmsisdsp import arm_rfft_instance_q15, arm_rfft_init_q15, arm_rfft_q15, arm_cmplx_mag_q15
# Initialize the FFT
rfft_instance_q15 = arm_rfft_instance_q15()
status = arm_rfft_init_q15(rfft_instance_q15, window_size, 0, 1)
# Apply the FFT to the audio
rfft_1_q15 = arm_rfft_q15(rfft_instance_q15, processed_window_1_q15)
# Take the absolute value
fft_bins_1_q15 = arm_cmplx_mag_q15(rfft_1_q15)[:window_size // 2 + 1]
xf = np.fft.rfftfreq(len(processed_window_1_q15), d=1./audio_sample_rate)
calculate_fft_cmsis_dsp.py hosted with ❤ by GitHub
现在,我们可以使用Matplotlib绘制Q15定点值:
现在,让我们将fft_bins_1_q15 q15值转换为浮点值,以便我们可以将该值与NumPy计算值进行比较。
使用256 rfft长度时,arm_rfft_q15(…)函数的输出格式将为9.7(而不是1.15)。arm_cmplx_mag_q15(…)功能的文档说明输入值为1.15格式,输出为2.14格式。因此,如果我们将一个9.7格式的数字传递到arm_cmplx_mag_q15(…),输出的格式将为10.6,这意味着它的值将在-512到511之间。
我们可以使用arm_Q15_to_float(…)函数将10.6数字从Q15转换为浮点值,然后将其乘以512(2^9):
from cmsisdsp import arm_q15_to_float
# Let's rescale them and compare the two
fft_bins_1_q15_scaled = arm_q15_to_float(fft_bins_1_q15) * 512
我们可以覆盖它们,看看它们有多近:
从上面我们可以看到,这两条管道在视觉上产生了非常相似的特征。
虽然使用定点函数会丢失一些信息,但我们设法计算出一个相似的形状,并在相同的频率下显示峰值。定点函数无法计算的频率(2000年的右边)都具有非常低的振幅,表明信息损失最小。
基于CMSIS-DSP的频谱图
最后,我们需要把所有这些放在一起,就像我们对NumPy做的那样。
# Putting this all together
window_size = 256
step_size = 128
# Convert the audio to q15
audio_samples_q15 = arm_float_to_q15(audio_samples)
# Calculate the number of windows
number_of_windows = int(1 + (audio_sample_rate - window_size) // step_size)
# Calculate the FFT Output size
fft_size = int(window_size // 2 + 1)
# Create an empty array to hold the Spectrogram
spectrogram_q15 = np.empty((number_of_windows, fft_size))
start_index = 0
# Apply hanning window and apply fft
for index in range(number_of_windows):
# Take the window from the waveform.
audio_window_q15 = audio_samples_q15[start_index:start_index + window_size]
# Apply the Hanning Window.
processed_audio_q15 = arm_mult_q15(audio_window_q15, hanning_window_q15)
# Calculate the FFT
rfft_q15 = arm_rfft_q15(rfft_instance_q15, processed_audio_q15)
# Take the absolute value of the FFT and add to the Spectrogram.
rfft_mag_q15 = arm_cmplx_mag_q15(rfft_q15)[:fft_size]
spectrogram_q15[index] = rfft_mag_q15
# Increase the start index of the window by the overlap amount.
start_index += step_size
create_spectrogram_cmsisdsp.py hosted with ❤ by GitHub
现在,让我们用Matplotlib绘制输入信号和Q15频谱图:
最后将Q15频谱图与NumPy版本进行比较:
回顾
正如我们所看到的,CMSIS-DSP管道与NumPy管道类似。让我们回顾一下我们为创建它所做的工作:
- 使用arm_float_to_Q15功能将音频样本转换为Q15格式。
- 使用arm_cos_F32和arm_float_to_q15函数,使用Hann函数公式计算Hanning窗口。
- 学习了如何使用arm_mult_q15函数乘以相同长度的向量。
- 学习了如何使用arm_rfft_实例_Q15、arm_rfft_初始_Q15和arm_rfft_Q15函数在CMSIS-DSP中计算Q15 FFT。
- 通过跨越音频信号,应用汉宁窗口,并对每个窗口周期使用FFT函数,创建音频信号的频谱图表示。
不同音频声音的频谱图示例
现在让我们看一下ESC-50代码库(https://github.com/karolpiczak/ESC-50)中不同音频文件的几个不同的频谱图。
将CMSIS-DSP代码从Python移植到C
现在,我们已经大致了解了如何在上一节中使用Python中的各种CMSIS-DSP API,我们可以开始将它们映射到C,以便在基于Cortex-M的系统上运行。
Includes
第一步是在.c文件的顶部添加一个#include<arm_math.h>:
#include <arm_math.h>
常数
然后我们可以为窗口和步长定义一些常量变量:
const int WINDOW_SIZE = 256;
const int STEP_SIZE = 128;
输入信号
可以声明一个全局数组变量来存储输入信号:
q15_t input_q15[WINDOW_SIZE];
用于创建频谱的输入信号可存储在输入_q15变量中。
出于测试目的,我们将其设置为固定正弦波,频率为440 Hz,采样频率为16 kHz。
for (int i = 0; i < WINDOW_SIZE; i++) {
float32_t f = sin((2 * PI * 440) / 16000 * i);
arm_float_to_q15(&f, &input_q15[i], 1);
}
汉宁窗
对于Hanning窗口函数,必须声明一个全局数组变量来存储窗口函数值:
q15_t hanning_window_q15[WINDOW_SIZE];
然后我们可以创建一个C函数,在运行时初始化hanning_window_q15数组:
void hanning_window_init_q15(q15_t* hanning_window_q15, size_t size) {
for (size_t i = 0; i < size; i++) {
// calculate the Hanning Window value for i as a float32_t
float32_t f = 0.5 * (1.0 - arm_cos_f32(2 * PI * i / size ));
// convert value for index i from float32_t to q15_t and
// store in window at position i
arm_float_to_q15(&f, &hanning_window_q15[i], 1);
}
}
我们需要另一个变量来存储输入信号上应用的汉宁窗口的值:
q15_t processed_window_q15[WINDOW_SIZE];
现在我们可以在输入信号上应用汉宁窗口:
// equivalent to:
// processed_window_q15 = input_q15 * hanning_window_q15
arm_mult_q15(input_q15, hanning_window_q15, processed_window_q15,
WINDOW_SIZE);
FFT
变量
我们需要FFT实例和输出的全局变量:
arm_rfft_instance_q15 S_q15;
// this is twice the size because each FFT output has a real and
// imaginary part
q15_t fft_q15[WINDOW_SIZE * 2];
// this is half the size of WINDOW_SIZE becase we just need the
// magnitude from the first half of the FFT output
q15_t fft_mag_q15[WINDOW_SIZE / 2];
初始化
我们需要调用arm_rfft_init_q15来初始化q15 rfft实例:
arm_rfft_init_q15(&S_q15, WINDOW_SIZE, 0, 1);
执行FFT
执行FFT和计算FFT的输出幅度现在可以通过以下方式完成:
arm_rfft_q15(&S_q15, processed_window_q15, fft_q15);
arm_cmplx_mag_q15(fft_q15, fft_mag_q15, WINDOW_SIZE / 2);
把所有的东西都集成在一起做一个Arduino Sketch
#include <arm_math.h>
// constants
const int WINDOW_SIZE = 256;
const int STEP_SIZE = 128;
// global variables
q15_t input_q15[WINDOW_SIZE];
q15_t hanning_window_q15[WINDOW_SIZE];
q15_t processed_window_q15[WINDOW_SIZE];
arm_rfft_instance_q15 S_q15;
// this is twice the size because each FFT output has a real and imaginary part
q15_t fft_q15[WINDOW_SIZE * 2];
// this is half the size of WINDOW_SIZE becase we just need the magnitude from
// the first half of the FFT output
q15_t fft_mag_q15[WINDOW_SIZE / 2];
void setup() {
Serial.begin(9600);
while (!Serial);
Serial.println("Hello from CMSIS DSP Q15 FFT example");
// initialize input with a 400 Hz sine wave
for (int i = 0; i < WINDOW_SIZE; i++) {
float32_t f = sin((2 * PI * 440) / 16000 * i);
arm_float_to_q15(&f, &input_q15[i], 1);
}
// pipeline initialization
hanning_window_init_q15(hanning_window_q15, WINDOW_SIZE);
arm_rfft_init_q15(&S_q15, WINDOW_SIZE, 0, 1);
}
void loop() {
unsigned long pipeline_start_us = micros();
// equivalent to: processed_window_q15 = input_q15 * hanning_window_q15
arm_mult_q15(input_q15, hanning_window_q15, processed_window_q15, WINDOW_SIZE);
// calculate the FFT and FFT magnitude
arm_rfft_q15(&S_q15, processed_window_q15, fft_q15);
arm_cmplx_mag_q15(fft_q15, fft_mag_q15, WINDOW_SIZE / 2);
unsigned long pipeline_end_us = micros();
Serial.print("Pipeline run time = ");
Serial.print(pipeline_end_us - pipeline_start_us);
Serial.println(" microseconds");
// all done loop forever ...
while (1);
}
void hanning_window_init_q15(q15_t* hanning_window_q15, size_t size) {
for (size_t i = 0; i < size; i++) {
// calculate the Hanning Window value for i as a float32_t
float32_t f = 0.5 * (1.0 - arm_cos_f32(2 * PI * i / size ));
// convert value for index i from float32_t to q15_t and store
// in window at position i
arm_float_to_q15(&f, &hanning_window_q15[i], 1);
}
}
cmsisdsp_fft.ino hosted with ❤ by GitHub
性能基准测试
现在,让我们在以下Arduino或Arduino兼容板上对上面的Arduino sketch进行基准测试:
- Arduino Nano 33 IoT
- Arduino Nano 33 BLE
- Teensy 4.0
我们已经创建了一个等效的草图,它使用32位浮点而不是定点来与上面的定点草图进行比较。这两个草图都可以在GitHub上找到:
- cmsis_dsp_fft_q15.ino
- cmsis_dsp_fft_f32.ino
内存使用
当我们使用16位Q15而不是32位浮点时,我们应该期望看到DSP操作所需的缓冲区占用一半的空间。
我们目前的缓冲区包括:
因此,对于窗口大小=256,我们期望:
- F32: 22 x 256 = 5632 bytes
- Q15: 11 x 256 = 2816 bytes
这说明,与F32使用浮点相比,Q15使用定点需要一半的RAM。
如果我们编译草图,我们会得到以下“全局变量使用”指标。
来自Arduino的值与我们希望保存的RAM量完全匹配。
运行时速度
现在,让我们将两个基准测试草图上传到电路板,以查看计算单个输入窗口的FFT bin需要多长时间:
Arduino Nano 33 IoT板基于Arm Cortex-M0+CPU,未配备FPU(浮点单元),使用16位定点DSP管道而不是32位浮点版本,完成管道的速度快11倍。Arduino Nano 33 BLE基于Arm Cortex-M4F CPU并配备FPU,其性能提高了37%。最后,Teensy 4.0基于配备FPU的Arm Cortex-M7 CPU,提高了26%。
结论
本指南概述了将音频信号转换为频谱图的DSP管道,然后可以将频谱图与计算机视觉技术结合起来对音频信号进行分类。我们已经解释了什么是频谱图,以及如何使用以下DSP操作从Python和NumPy中的.wav文件创建频谱图:加窗、汉宁窗口和FFT。在此之后,您将了解定点数学,以及如何使用CMSIS-DSP Python包装器使用16位定点操作创建等效的频谱图,并将精度损失降至最低。然后,我们介绍了如何将CMSIS-DSP管道从Python移植到C,以便在基于Arm Cortex-M的设备上运行。
在我们的基准测试中,当使用定点16位操作而不是32位浮点操作时,DSP管道所需的全局缓冲区需要的内存减少50%。当使用定点16位操作而不是浮点操作时,Cortex-M0+(没有FPU)的速度提高了11倍。Cortex-M4F和Cortex-M7F系统内置FPU,当使用定点16位操作而不是浮点操作时,速度分别提高了37%和26%。这些都是令人印象深刻的改进。使用定点还节省了转换步骤,从模拟话筒(使用ADC)或数字话筒读取音频输入值时,音频输入值(通常)存储为16位值。
有关Arm软件开发团队的更多博客、指南、资源、视频和笔记本,请访问:
@ArmSoftwareDev on Twitter
The Arm Software Developers YouTube channel
@ArmDeveloperEcosystem on GitHub
了解更多
在即将到来的Arm DevSummit(10月19日至21日为期3天的虚拟活动)上,提高技能并使用tinyML获得实践经验。该活动包括tinyML计算机视觉真实世界嵌入式设备研讨会,以及使用基于Arm Cortex-M的MCU构建大词汇量语音控制。我们希望在那里见到你!