我们将使用 ECAPA-TDNN (Emphasized Channel Attention, Propagation, and Aggregation in Time Delay Neural Network),这是一种非常强大的深度神经网络,专门用于从语音中提取高度可区分的 说话人嵌入 (Speaker Embedding)。
这个系统的核心思想是:
特征提取: 将原始音频波形转换为 FBank (Filter Banks) 或 Mel Spectrogram 特征。这两种特征都比 MFCC 保留了更多的原始信号信息,更适合作为深度学习模型的输入。我们将使用 torchaudio 来完成这一步。
嵌入提取: 使用一个预训练好的 ECAPA-TDNN 模型将可变长度的声学特征转换成一个固定长度的向量(例如,192维)。这个向量就是“声纹嵌入”,它在数学上代表了说话人的声音特性。来自同一个人的不同语音,其嵌入向量在向量空间中的距离会非常近。
录入与识别:
录入 (Enrollment): 录制用户的一段或多段语音,提取其嵌入向量。为了更稳定,通常会取多段语音嵌入的平均值作为该用户的最终“声纹模板”。
识别 (Recognition): 录制一段待测语音,提取其嵌入向量,然后计算它与数据库中所有已录入声纹模板的余弦相似度 (Cosine Similarity)。相似度最高的那个就是识别结果。
我们将使用 speechbrain 库,它极大地简化了加载和使用在海量数据(如 VoxCeleb)上预训练好的 SOTA (State-of-the-Art) 模型的过程。
第 1 步:安装必要的库
这个方案需要更多的库,特别是 PyTorch 和 SpeechBrain。请在终端中运行以下命令:
# 安装 PyTorch (请访问 PyTorch 官网获取最适合你系统的命令,特别是如果你有 GPU) # CPU-only version: pip install torch torchaudio # 安装其他依赖 pip install speechbrain sounddevice numpy scipy huggingface_hub
第 2 步:编写代码
将以下代码保存为 deep_speaker_recognition.py。代码包含了完整的录入、识别流程,并有详细注释。
import os import torch import torchaudio import sounddevice as sd import numpy as np import torch.nn.functional as F import time # --- 配置参数 --- SAMPLE_RATE = 16000 # 模型期望的采样率 (Hz) RECORD_SECONDS = 5 # 每次录音的时长 (秒) EMBEDDINGS_FILE = "speaker_embeddings.pth" # 存放声纹嵌入的文件 ENROLL_COUNT = 3 # 为了获得更稳定的声纹,建议每人录入3次 SIMILARITY_THRESHOLD = 0.50 # 余弦相似度阈值,低于此值则认为是未知用户 class DeepSpeakerRecognizer: """ 使用预训练的 ECAPA-TDNN 模型进行声纹录入和识别。 """ def __init__(self, embeddings_file=EMBEDDINGS_FILE): self.device = "cuda" if torch.cuda.is_available() else "cpu" print(f"正在使用设备: {self.device}") # 1. 加载预训练的 ECAPA-TDNN 模型 print("正在加载预训练的 ECAPA-TDNN 模型...") try: from speechbrain.pretrained import EncoderClassifier self.model = EncoderClassifier.from_hparams( source="speechbrain/spkrec-ecapa-voxceleb", savedir="pretrained_models/spkrec-ecapa-voxceleb", run_opts={"device": self.device} ) print("模型加载成功!") except Exception as e: print(f"模型加载失败: {e}") print("请确保已安装 speechbrain 并且网络连接正常。") exit() # 2. 加载已保存的声纹嵌入 self.embeddings_file = embeddings_file self.speaker_embeddings = self._load_embeddings() print("-" * 30) def _load_embeddings(self): """从文件加载嵌入向量""" if os.path.exists(self.embeddings_file): print(f"从 {self.embeddings_file} 加载已存在的声纹嵌入...") return torch.load(self.embeddings_file, map_location=self.device) return {} def _save_embeddings(self): """将嵌入向量保存到文件""" torch.save(self.speaker_embeddings, self.embeddings_file) print(f"声纹嵌入已保存至 {self.embeddings_file}") def _record_audio(self, duration, sample_rate, prompt=""): """录制指定时长的音频""" if prompt: print(prompt) print("准备录音...") time.sleep(1) print(f"请在倒计时结束后开始说话,录音将持续 {duration} 秒。") for i in range(3, 0, -1): print(f"{i}...") time.sleep(1) print("开始录音!") recording = sd.rec(int(duration * sample_rate), samplerate=sample_rate, channels=1, dtype='float32') sd.wait() print("录音结束!") return torch.from_numpy(recording.flatten()) def _preprocess_audio(self, waveform): """对音频进行预处理,使其符合模型输入要求""" # 模型需要单声道 (mono) 音频 if waveform.ndim > 1: waveform = torch.mean(waveform, dim=0) # 确保采样率为 16000 Hz # (此处录音时已指定,如果加载文件则需要重采样) return waveform.to(self.device) def _get_embedding(self, waveform): """使用模型提取声纹嵌入向量""" with torch.no_grad(): # SpeechBrain 模型需要 batch 维度 waveform = waveform.unsqueeze(0) embedding = self.model.encode_batch(waveform) # [1, 1, D] -> [D] embedding = embedding.squeeze() # L2 归一化 embedding = F.normalize(embedding, p=2, dim=0) return embedding def enroll(self, speaker_name): """录入一个新的说话人声纹""" if not speaker_name: print("错误:说话人姓名不能为空。") return print(f"\n--- 开始为 '{speaker_name}' 录入声纹 ---") print(f"为了保证准确性,需要录制 {ENROLL_COUNT} 次。") embeddings = [] for i in range(ENROLL_COUNT): prompt = f"第 {i+1}/{ENROLL_COUNT} 次录音。请说一些话,内容可以不同。" audio_waveform = self._record_audio(RECORD_SECONDS, SAMPLE_RATE, prompt=prompt) processed_audio = self._preprocess_audio(audio_waveform) print(f"正在提取第 {i+1} 段录音的嵌入...") embedding = self._get_embedding(processed_audio) embeddings.append(embedding) # 计算平均嵌入作为最终的声纹模板 final_embedding = torch.mean(torch.stack(embeddings), dim=0) final_embedding = F.normalize(final_embedding, p=2, dim=0) # 再次归一化 self.speaker_embeddings[speaker_name] = final_embedding self._save_embeddings() print(f"\n成功!'{speaker_name}' 的声纹已录入。") def recognize(self): """识别说话人""" if not self.speaker_embeddings: print("\n错误:系统中没有任何已录入的声纹模型,请先录入。") return print("\n--- 开始声纹识别 ---") # 1. 录制待识别的音频 audio_waveform = self._record_audio(RECORD_SECONDS, SAMPLE_RATE) processed_audio = self._preprocess_audio(audio_waveform) # 2. 提取待测嵌入 print("正在提取待识别语音的嵌入...") test_embedding = self._get_embedding(processed_audio) # 3. 计算与所有已录入声纹的余弦相似度 similarities = {} for name, enrolled_embedding in self.speaker_embeddings.items(): # F.cosine_similarity 需要 batch 维度 similarity = F.cosine_similarity(test_embedding.unsqueeze(0), enrolled_embedding.unsqueeze(0)).item() similarities[name] = similarity # 4. 找出最匹配的结果 if not similarities: print("计算相似度失败。") return best_match_name = max(similarities, key=similarities.get) best_similarity = similarities[best_match_name] print("\n--- 识别结果 ---") for name, sim in sorted(similarities.items(), key=lambda item: item[1], reverse=True): print(f" - 与 '{name}' 的相似度: {sim:.4f}") if best_similarity >= SIMILARITY_THRESHOLD: print(f"\n最匹配的人是: ** {best_match_name} ** (相似度: {best_similarity:.4f})") else: print(f"\n未找到匹配的用户。最高相似度 ({best_similarity:.4f}) 低于阈值 ({SIMILARITY_THRESHOLD})。") print("识别为: ** 未知用户 **") def list_speakers(self): """列出所有已录入的用户""" print("\n--- 已录入的用户列表 ---") if not self.speaker_embeddings: print("系统中没有已录入的用户。") else: for i, name in enumerate(self.speaker_embeddings.keys(), 1): print(f"{i}. {name}") print("-" * 30) def main(): """主函数,提供命令行交互界面""" recognizer = DeepSpeakerRecognizer() while True: print("\n请选择一个操作:") print("1. 录入新的声纹 (Enroll)") print("2. 识别说话人 (Recognize)") print("3. 查看已录入列表 (List)") print("4. 退出 (Exit)") choice = input("请输入选项 (1/2/3/4): ").strip() if choice == '1': name = input("请输入您的姓名(仅限英文和数字): ").strip() recognizer.enroll(name) elif choice == '2': recognizer.recognize() elif choice == '3': recognizer.list_speakers() elif choice == '4': print("感谢使用,再见!") break else: print("无效的输入,请输入 1, 2, 3 或 4。") if __name__ == "__main__": main()
第 3 步:如何运行和使用
保存代码: 将代码保存为 deep_speaker_recognition.py。
首次运行: 第一次运行时,speechbrain 会自动从 Hugging Face Hub 下载预训练的 ECAPA-TDNN 模型,这可能需要一些时间,请耐心等待。模型文件会保存在 pretrained_models 目录下。
运行程序: 在终端中执行 python deep_speaker_recognition.py。
使用流程:
录入声纹 (Enroll):
在主菜单输入 1。
输入用户名,例如 alice。
程序会提示你进行 3 次录音。每次录音前都有 3 秒倒计时,请在倒计时后清晰、自然地说话 5 秒钟。
完成后,程序会将 alice 的平均声纹嵌入保存在 speaker_embeddings.pth 文件中。
识别声纹 (Recognize):
在主菜单输入 2。
程序会要求你录制一段 5 秒的音频。
录制完成后,它会计算这段音频的嵌入与数据库中所有已录入声纹的余弦相似度。
程序会打印出与所有用户的相似度分数,并根据最高的相似度及预设的阈值来判断说话人是谁,或者判断为“未知用户”。
网友回复
python如何实现声纹识别用户进行验证?
在哪可找到各种影视经典角色的配音并克隆音色根据文本说话?
阿里通义大模型哪些是支持多模态的api的ai模型?
js如何实现浏览器中离线语音唤醒语音聊天小助手?
浏览器中如何将WebM视频转成mp4视频?
parlant如何改成qwen 的apikey与baseurl?
如何写一个chrome插件实现截屏自动生成步骤图文教程转成pdf或网页?
python如何通过阿里云的api对域名进行解析和ecs主机服务器进行启动停止等操作?
Tesla Robotaxi可以让特斯拉车自动无人驾驶跑滴滴为车主赚钱,国内以后也会这样吗?
有没有可以监控安卓手机上的app打开后偷偷摸摸做了啥的监控软件?