
1. 从“能听”到“听懂”边缘设备上的关键词唤醒最近在折腾一个智能家居的语音控制项目核心需求很简单让一个放在客厅的Raspberry Pi树莓派能时刻监听环境声音当听到“小爱同学”或“Hey Siri”这类特定唤醒词时才启动后续复杂的语音识别和指令处理流程。这听起来像是智能音箱的标配功能但当你真正尝试在树莓派这种资源有限的边缘设备上实现时会发现挑战不小。全时段的音频流处理、复杂的神经网络模型推理对树莓派那颗小小的ARM芯片来说是不小的负担。如果让树莓派一直运行一个庞大的语音识别模型不仅耗电CPU占用率也会居高不下响应速度还未必理想。这时关键词检测Keyword Spotting简称KWS技术就成了破局的关键。它的目标不是理解整句话而是像哨兵一样专注地从连续音频流中识别出少数几个预定义的词或短语。只有检测到关键词才唤醒后续的“大模型”这样既能实现低功耗的常驻监听又能保证核心功能的响应。我选择的硬件是ReSpeaker 2-Mics Pi HAT这是一款专为树莓派设计的麦克风扩展板自带双麦克风阵列能提供比树莓派板载音频输入好得多的拾音质量。软件栈的核心则是TensorFlow LiteTFLite它是TensorFlow针对移动和嵌入式设备的轻量级解决方案能将训练好的模型转换成可在资源受限环境中高效运行的形式。本文将详细记录我如何在Raspberry Pi上结合ReSpeaker HAT和TensorFlow Lite搭建一个实时、低功耗的关键词检测系统。整个过程涉及环境搭建、模型选择与转换、音频采集处理、推理优化等多个环节其中有不少从官方文档里找不到的实践细节和踩坑经验。2. 硬件与软件环境深度配置在树莓派上做音频AI项目第一步也是至关重要的一步就是打好环境基础。一个稳定、高效且配置正确的环境能避免后续开发中许多莫名其妙的问题。2.1 硬件选型与连接为什么是ReSpeaker 2-Mics Pi HAT树莓派本身具备3.5mm音频输入口和CSI摄像头接口旁的“模拟麦克风”输入但它们的音质和抗噪能力对于关键词检测来说是远远不够的。关键词检测的准确性极度依赖于输入音频信号的质量。背景噪音、低采样率、电路干扰都可能导致模型“听错”。因此一块专用的麦克风扩展板几乎是必需品。我选择ReSpeaker 2-Mics Pi HAT主要基于以下几点考量即插即用它通过树莓派的40针GPIO接口直接堆叠安装无需额外焊接或飞线供电和数据传输一体解决非常整洁。双麦克风阵列两个麦克风可以进行简单的波束成形在一定程度上抑制非目标方向的噪声提升信噪比。这对于客厅、厨房等多噪声环境尤为重要。集成音频编解码芯片板载的AC108编解码芯片提供了高质量的模拟-数字转换ADC支持高达192kHz的采样率尽管我们通常用不到这么高远胜于树莓派自身的低质量音频输入。良好的社区支持作为一款热门硬件其驱动和基础示例比较丰富降低了入门门槛。安装时只需确保树莓派断电然后将HAT的插槽与树莓派的GPIO针脚对准轻轻按压即可。安装完成后一个直观的检查方法是启动树莓派后HAT上的LED灯应该会亮起。2.2 操作系统与底层音频驱动配置我使用的是最新的Raspberry Pi OS64位版本这是一个基于Debian的稳定系统。系统安装完成后第一件事是更新软件源并升级所有包sudo apt update sudo apt upgrade -y。接下来是关键配置系统以识别并使用ReSpeaker HAT作为默认音频输入设备。这里有一个常见的坑仅仅安装驱动可能不够需要手动选择音频输出设备。安装驱动与内核模块ReSpeaker HAT的驱动通常已经包含在较新版本的内核中。我们可以通过安装seeed-voicecard这个包来确保配置正确。# 克隆驱动仓库 git clone https://github.com/respeaker/seeed-voicecard.git cd seeed-voicecard # 安装驱动需要sudo权限 sudo ./install.sh安装脚本会自动编译并加载所需的内核模块。完成后必须重启树莓派sudo reboot。验证设备识别重启后使用arecord -l命令列出所有录音设备。$ arecord -l **** List of CAPTURE Hardware Devices **** card 1: seeed2micvoicec [seeed-2mic-voicecard], device 0: bcm2835-i2s-ac108-codec0 ac108-codec0-0 [] Subdevices: 1/1 Subdevice #0: subdevice #0可以看到card 1就是我们新添加的ReSpeaker HAT。同时用aplay -l也能看到对应的播放设备。配置ALSA默认设备这是确保后续Python程序能直接访问麦克风而无需指定复杂设备参数的关键。编辑ALSA配置文件sudo nano /etc/asound.conf。如果文件不存在就新建一个。添加以下内容pcm.!default { type asym playback.pcm { type plug slave.pcm hw:1,0 } capture.pcm { type plug slave.pcm hw:1,0 } } ctl.!default { type hw card 1 }这里hw:1,0对应arecord -l中看到的card 1, device 0。保存并退出。测试录音执行一个简单的录音命令录制3秒钟arecord -d 3 -f cd -t wav test.wav然后用aplay test.wav播放如果能清晰听到自己的声音说明麦克风硬件和驱动配置成功。如果没声音检查麦克风是否被静音运行alsamixer按F6选择seeed-2mic-voicecard声卡确保所有通道的音量未被静音MM表示静音按M键解除且音量适中。2.3 Python环境与核心库安装我们将使用Python作为主要开发语言。系统自带的Python3通常就够用。我们需要创建一个虚拟环境来管理项目依赖避免污染系统环境。# 安装虚拟环境工具 sudo apt install python3-venv python3-pip -y # 创建项目目录并进入 mkdir ~/keyword_spotting cd ~/keyword_spotting # 创建Python虚拟环境 python3 -m venv venv # 激活虚拟环境 source venv/bin/activate激活后命令行提示符前会出现(venv)字样。接下来安装核心的Python库# 升级pip pip install --upgrade pip # 安装TensorFlow Lite运行时。 # 注意树莓派上通常安装tflite-runtime而非完整的tensorflow后者体积庞大且可能无法在ARM上顺利编译。 pip install tflite-runtime # 安装用于音频处理的库 pip install numpy pyaudio soundfile这里重点说下pyaudio。它是一个提供PortAudio库绑定的Python包用于跨平台的音频输入输出。在树莓派上安装它有时会遇到需要PortAudio开发头文件的问题。如果pip install pyaudio失败可以先安装系统库sudo apt install portaudio19-dev python3-all-dev然后再用pip安装。至此一个针对音频AI项目的树莓派基础环境就搭建完成了。这个环境确保了从物理音频信号采集到系统层驱动再到Python应用层访问的整个通路是畅通的。3. 关键词检测模型的选择、获取与转换环境就绪后接下来就是模型。关键词检测本质上是一个音频分类任务将一小段比如1秒的音频信号分类为“静音/背景噪音”、“关键词A”、“关键词B”等。我们需要一个既准确又足够小的模型以适应树莓派的算力。3.1 模型选型为什么是MobileNet DSCNNTensorFlow团队为边缘设备关键词检测提供了几个预训练模型其中最经典和高效的是基于深度可分离卷积Depthwise Separable Convolution, DSC的卷积神经网络常被称为MobileNet架构的变种。我选择使用**“Micro Speech”** 模型。这个模型是TensorFlow Lite官方示例之一设计目标就是在MCU级别的设备上运行。它非常小仅约20KB速度快功耗低。它默认训练识别的关键词是“yes”和“no”但我们可以利用其架构和权重进行迁移学习或者直接使用它来理解整个流程。这个模型的特点在于前端处理它将原始的1秒时长音频16kHz采样率即16000个点通过微前端Micro Frontend模块进行处理。这个模块模拟了人耳的听觉特性将波形转换为频谱图Spectrogram再进一步转换为梅尔频率倒谱系数MFCC。MFCC是语音识别中非常经典的特征它能很好地表征语音信号的频谱包络。关键点这个MFCC转换步骤是集成在模型内部的作为TFLite模型的一个自定义算子这意味着我们在喂给模型数据时只需要提供原始的PCM音频数据如16位有符号整数模型内部会自动完成特征提取。这极大地简化了部署流程。核心网络一个非常精简的DSCNN包含2个深度可分离卷积层和1个全连接层。输出对应“静音”、“未知”、“yes”、“no”四个类别。对于初学者我强烈建议先从使用这个预训练模型开始理解数据流和接口。后续若要检测自定义关键词如“打开灯光”则需要准备自己的数据集并在这个模型基础上进行迁移学习Transfer Learning即冻结前面的特征提取层只重新训练最后的分类层。这比从头训练一个模型要快得多需要的样本量也少得多。3.2 获取与理解模型文件我们可以从TensorFlow的GitHub仓库直接下载预编译的TFLite模型。# 在项目目录下 wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/micro_speech_2020_04_13.zip unzip micro_speech_2020_04_13.zip解压后你会得到几个文件其中最重要的是micro_speech.tflite这就是我们需要的TensorFlow Lite模型文件。micro_speech_quantized.tflite量化版本的模型体积更小约14KB推理速度可能更快但精度略有损失。在树莓派上我推荐使用量化版本因为性能提升显著而精度损失在可接受范围内。micro_speech_labels.txt标签文件内容为_silence_ _unknown_ yes no这定义了模型输出向量中四个索引对应的含义。索引0是静音索引1是未知词索引2是“yes”索引3是“no”。3.3 模型输入输出探秘在写代码之前必须彻底搞清楚模型对输入数据的要求这是成功调用模型的前提。我们可以使用TFLite自带的summarize工具需要安装完整的TensorFlow包可以在开发机上操作来查看模型信息但更直接的方法是阅读官方文档和示例代码。对于Micro Speech模型输入一个形状为[1, 1960]的int8张量Tensor。这1960个点代表的是1秒钟、16kHz采样率、单声道的原始PCM音频数据。注意数据类型是int8有符号8位整数这意味着音频样本值需要在-128到127之间。而我们通常从声卡读取的是int16范围-32768到32767所以需要进行缩放转换。输出一个形状为[1, 4]的int8张量。四个值分别对应_silence_,_unknown_,yes,no的得分logits。得分最高的那个索引就是模型的预测结果。注意这里有一个非常重要的细节。模型内部包含了音频特征提取MFCC的逻辑。所以我们不需要在Python代码中手动计算MFCC只需要提供原始的、归一化到int8范围的PCM数据。这简化了部署但也要求我们必须严格按照模型的输入规格来准备数据。4. 构建实时音频流与推理引擎有了模型和对它的理解我们就可以开始编写核心的Python程序了。这个程序需要持续地从ReSpeaker HAT采集音频将其处理成模型需要的格式然后送入TFLite解释器进行推理最后根据结果做出响应。4.1 初始化TFLite解释器与分配张量首先我们加载模型并创建解释器。import tflite_runtime.interpreter as tflite import numpy as np # 加载TFLite模型并分配张量 model_path ‘micro_speech_quantized.tflite’ interpreter tflite.Interpreter(model_pathmodel_path) interpreter.allocate_tensors() # 获取输入和输出张量的详细信息 input_details interpreter.get_input_details() output_details interpreter.get_output_details() # 打印输入输出形状和类型用于验证 print(“Input Shape:”, input_details[0][‘shape’]) print(“Input Type:”, input_details[0][‘dtype’]) print(“Output Shape:”, output_details[0][‘shape’]) print(“Output Type:”, output_details[0][‘dtype’])运行这段代码应该会看到输出Input Shape: [1, 1960]和Input Type: class ‘numpy.int8’确认了之前的分析。4.2 设计音频流采集循环我们需要一个循环不断地录制一小段音频然后进行处理和推理。PyAudio库非常适合这个任务。import pyaudio import queue import threading from collections import deque import time # 音频参数必须与模型要求严格匹配 SAMPLE_RATE 16000 # 16kHz DURATION_MS 1000 # 每次推理的音频长度毫秒 AUDIO_LEN int(SAMPLE_RATE * DURATION_MS / 1000) # 计算样本数16000 CHUNK 1024 # 每次从声卡读取的样本数 # 创建一个先进先出的队列用于在音频回调线程和主线程之间传递数据 audio_queue queue.Queue() def audio_callback(in_data, frame_count, time_info, status): “”“PyAudio音频流回调函数。每当声卡缓冲区填满时此函数被调用。”“” # in_data是bytes类型我们需要将其转换为numpy数组 # 模型要求int8但PyAudio默认返回int16所以需要转换和缩放 audio_data np.frombuffer(in_data, dtypenp.int16) # 将int16 (-32768~32767) 缩放并转换为int8 (-128~127) # 直接右移8位是最快的方法相当于除以256 audio_data_int8 (audio_data 8).astype(np.int8) # 将处理后的数据块放入队列 audio_queue.put(audio_data_int8) # 返回None和pyaudio.paContinue以继续流 return (None, pyaudio.paContinue) # 初始化PyAudio p pyaudio.PyAudio() # 打开音频流指定参数格式为int16单声道16kHz采样率输入设备回调模式 stream p.open(formatpyaudio.paInt16, channels1, rateSAMPLE_RATE, inputTrue, frames_per_bufferCHUNK, stream_callbackaudio_callback) print(“开始音频流...“) stream.start_stream()这段代码创建了一个非阻塞的音频流。audio_callback函数会在后台线程中持续运行每当声卡采集到1024个样本CHUNK大小就会自动调用它将数据转换后放入队列。主线程则可以从这个队列中取出数据块进行拼接和处理。4.3 实现滑动窗口与推理逻辑模型需要恰好1秒16000个样本的音频。但我们不能简单地等待攒够1秒数据再推理那样延迟会很高。标准的做法是使用滑动窗口我们维护一个能容纳稍多于1秒数据的缓冲区例如1.5秒每次推理时从这个缓冲区的末尾取出最新的1秒数据。这样既能保证数据的实时性又能进行连续推理。# 创建一个双端队列作为音频缓冲区能容纳约1.5秒的数据 buffer_len int(SAMPLE_RATE * 1.5) audio_buffer deque(maxlenbuffer_len) # 主循环从队列中获取音频块填充缓冲区并进行推理 try: while True: # 从队列中获取一个音频块非阻塞方式最多等待0.1秒 try: audio_chunk audio_queue.get(timeout0.1) except queue.Empty: # 如果队列为空跳过本次循环 continue # 将新数据块添加到缓冲区末尾 audio_buffer.extend(audio_chunk) # 只有当缓冲区中的数据量超过1秒时才进行推理 if len(audio_buffer) AUDIO_LEN: # 取出缓冲区中最后1秒的数据 # 注意deque不支持切片需要转换为list再切片 recent_audio list(audio_buffer)[-AUDIO_LEN:] # 确保数据长度准确并转换为模型需要的形状和类型 model_input np.array(recent_audio, dtypenp.int8).reshape(1, AUDIO_LEN) # 将数据设置到解释器的输入张量 interpreter.set_tensor(input_details[0][‘index’], model_input) # 执行推理 interpreter.invoke() # 获取输出结果 output_data interpreter.get_tensor(output_details[0][‘index’]) # output_data形状是[1, 4]取出第一个批次的结果 scores output_data[0] # 解读结果 # 找到得分最高的索引 predicted_index np.argmax(scores) labels [‘_silence_’, ‘_unknown_’, ‘yes’, ‘no’] predicted_label labels[predicted_index] confidence scores[predicted_index] # 注意这是int8量化后的logits不是概率 # 设置一个置信度阈值过滤掉低置信度的检测 # 由于是量化logits阈值需要实验确定例如设为-50 if confidence -50 and predicted_label not in [‘_silence_’, ‘_unknown_’]: print(f”检测到关键词 ‘{predicted_label}‘ 置信度: {confidence}“) # 在这里触发你的后续动作比如点亮一个LED或发送一个网络请求 # trigger_action(predicted_label) # 为了降低CPU占用可以适当休眠。推理本身很快主要耗时在音频IO。 time.sleep(0.05) # 每秒推理约20次 except KeyboardInterrupt: print(“\n正在停止...”) finally: # 清理资源 stream.stop_stream() stream.close() p.terminate() print(“音频流已关闭。”)这个主循环实现了滑动窗口推理。它不断从队列中取出最新的音频块填充到一个固定长度的缓冲区audio_buffer中。每次循环它检查缓冲区是否有足够的数据1秒如果有就取出最后1秒的数据送入模型推理。deque的使用保证了缓冲区不会无限增长当超过maxlen时最旧的数据会被自动丢弃。5. 性能优化与实战调试技巧上面的代码已经可以运行并检测“yes”和“no”了但在树莓派上要达到实用级别还需要进行一系列优化和调试。5.1 降低延迟与CPU占用率原始的循环可能会让树莓派单核CPU占用率达到30%以上。我们可以从几个方面优化调整推理频率不需要对每一帧新数据都做推理。代码中已经通过time.sleep(0.05)将推理频率限制在约20Hz。你可以根据实际需求调整比如对于唤醒词10Hz0.1秒间隔可能就足够了能进一步降低CPU使用率。优化音频回调回调函数audio_callback中的操作要尽可能快。我们目前只做了数据类型转换和入队操作这是合理的。避免在回调中进行复杂的计算或I/O操作。使用queue的put_nowait如果主线程处理速度跟不上音频生产速度队列可能会积压导致内存占用增加和延迟变长。可以在回调中使用put_nowait并处理可能的队列满异常或者设置一个合理的队列最大长度。def audio_callback(in_data, frame_count, time_info, status): audio_data np.frombuffer(in_data, dtypenp.int16) audio_data_int8 (audio_data 8).astype(np.int8) try: audio_queue.put_nowait(audio_data_int8) except queue.Full: # 如果队列已满丢弃最旧的一个数据块再放入新的 try: audio_queue.get_nowait() # 丢弃一个 except queue.Empty: pass audio_queue.put_nowait(audio_data_int8) return (None, pyaudio.paContinue)5.2 处理背景噪音与误触发在真实环境中背景噪音风扇声、电视声、交谈声是最大的挑战。预训练的“yes/no”模型在安静环境下表现很好但在嘈杂环境中可能会频繁误报为“unknown”甚至“yes/no”。置信度阈值调优代码中我们设置了一个简单的阈值confidence -50。这个值需要你在实际部署环境中进行校准。录制一段纯背景噪音运行程序观察模型输出的置信度分数分布。将阈值设置得比噪音产生的最高置信度再高一些。后处理非极大值抑制语音是连续的模型可能会在真正的关键词附近连续多帧都输出高置信度。我们可以添加一个简单的后处理逻辑比如在检测到一次关键词后设置一个“静默期”例如1秒在此期间忽略所有检测避免重复触发。last_trigger_time 0 SILENCE_PERIOD 1.0 # 秒 # 在主循环的判断条件中加入时间检查 current_time time.time() if confidence THRESHOLD and predicted_label not in [‘_silence_’, ‘_unknown_’]: if current_time - last_trigger_time SILENCE_PERIOD: print(f”检测到关键词 ‘{predicted_label}‘ 置信度: {confidence}“) last_trigger_time current_time # trigger_action(predicted_label)能量门限在音频送入模型之前先计算其能量音量。如果能量低于某个阈值直接判定为静音跳过推理。这可以过滤掉很多微弱的噪音。# 计算最近1秒音频的RMS能量 audio_for_energy np.array(recent_audio, dtypenp.float32) / 128.0 # 转换到-1~1范围 rms_energy np.sqrt(np.mean(audio_for_energy ** 2)) ENERGY_THRESHOLD 0.01 # 需要根据环境调整 if rms_energy ENERGY_THRESHOLD: # 认为是静音跳过本次推理 continue5.3 模型自定义与迁移学习要检测“小爱同学”或“打开灯光”这类自定义关键词你需要训练自己的模型。最实用的方法是迁移学习。准备数据集你需要收集目标关键词的音频。每个词至少需要数百个样本由不同的人、在不同的环境、用不同的语调说出。同时需要更多的“负样本”即背景噪音和其他无关词语来训练模型区分“未知词”。TensorFlow Lite有一个Micro Speech训练脚本它期望的目录结构是my_keywords/ ├── background_noise/ # 放置各种背景噪音的WAV文件 ├── my_word/ # 放置“打开灯光”的WAV文件 ├── not_my_word/ # 放置其他无关词语的WAV文件作为未知词 └── silence/ # 可以放空文件或极低能量文件可选所有WAV文件必须是16kHz采样率、单声道、16位PCM格式。你可以用arecord或Audacity等工具录制并用sox命令批量转换格式sox input.wav -r 16000 -c 1 -b 16 output.wav。使用TensorFlow进行训练你需要在功能更强大的机器如你的笔记本电脑上安装完整版TensorFlow。然后克隆TensorFlow仓库使用其中的训练脚本。# 在开发机上操作 git clone https://github.com/tensorflow/tensorflow.git cd tensorflow # 参考 tensorflow/lite/micro/examples/micro_speech/train/ 目录下的README # 通常需要运行一个Python脚本指定你的数据路径和训练参数 python3 tensorflow/lite/micro/examples/micro_speech/train/train.py \ --data_dir/path/to/my_keywords \ --model_architectureds_cnn \ --wanted_words”my_word” \ --train_dir/tmp/my_model训练脚本会基于预训练的Micro Speech模型冻结大部分层只重新训练最后的分类层。训练完成后它会生成一个新的.tflite模型文件。部署新模型将新生成的.tflite文件和对应的标签文件标签顺序通常是_silence_,_unknown_,my_word复制到树莓派上替换掉原来的模型和标签文件即可。代码中只需要修改model_path和labels列表其他部分完全不用动。5.4 系统集成与自启动最后为了让这个关键词检测服务能稳定运行我们需要把它变成一个系统服务。创建Python脚本将上述所有代码整合到一个Python文件中例如keyword_spotter.py。确保在文件开头指定Python解释器为虚拟环境中的Python#!/home/pi/keyword_spotting/venv/bin/python3。创建Systemd服务这是管理Linux后台服务的最佳实践。sudo nano /etc/systemd/system/keyword-spotter.service添加以下内容[Unit] DescriptionKeyword Spotting Service Aftermulti-user.target sound.target [Service] Typesimple Userpi WorkingDirectory/home/pi/keyword_spotting ExecStart/home/pi/keyword_spotting/venv/bin/python /home/pi/keyword_spotting/keyword_spotter.py Restarton-failure RestartSec5 [Install] WantedBymulti-user.target保存退出。启用并启动服务sudo systemctl daemon-reload sudo systemctl enable keyword-spotter.service sudo systemctl start keyword-spotter.service你可以用sudo systemctl status keyword-spotter.service查看服务状态用journalctl -u keyword-spotter.service -f实时查看日志。通过以上步骤我们就在Raspberry Pi上构建了一个从硬件驱动、音频采集、模型推理到系统集成的完整关键词检测系统。它能够低功耗地持续监听准确响应特定语音指令为智能家居、语音交互机器人等边缘AI应用提供了一个可靠的基础模块。整个过程中对音频流与模型接口的精确匹配、滑动窗口的实现、以及针对真实环境的优化调试是项目成功的关键。