在近两年里,如果说想要在本地部署离线语音识别模型,那么 Whisper 和 FunASR 肯定是首选项。所以为什么要使用 Vosk 呢?
优势
Vosk 是一个离线开源语音识别工具包,它的优点在于:
- 轻量:Vosk 提供轻量级的模型(小于 50MB 大小),可以用于低功耗平台(例如 Android、树莓派之类)
- 多编程语言、多平台支持:Python、Java、Node.js、C#、C++、Rust、Go 等
- 多语种支持:支持二十多种语言的识别(包括中文)
- 实时性:实时性语音识别场景下,vosk 的延迟非常低
简单来讲,你电脑中有 Python 环境,再下载一个 50 MB 的模型,就可以用 Vosk 实现一个正确率还可以接受的语言识别相关的项目。而像 Whisper 虽然识别效果好,但是对硬件要求很高,同时部署起来麻烦(例如需要配置 CUDA 环境),另外也不是很适用于实时性场景。
使用
这里以 Python 为例,首先安装 Vosk:pip3 install vosk pyaudio
。
音频文件识别 www.cqzlsb.com
以下代码实现对 test.wav
音频文件中语音内容的识别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import wave import sys from vosk import KaldiRecognizer, Model, SetLogLevel SetLogLevel(level=-1) wf: wave.Wave_read = wave.open(os.path.join(dir_path, "test.wav"), "rb") if wf.getnchannels() != 1 or wf.getsampwidth() != 2 or wf.getcomptype() != "NONE": print("Audio file must be WAV format mono PCM.") sys.exit(1) model = Model(model_path="/path/to/vosk-model-small-en-us-0.15") rec = KaldiRecognizer(model, wf.getframerate()) rec.SetWords(True) rec.SetPartialWords(True) while True: data = wf.readframes(4000) if len(data) == 0: break if rec.AcceptWaveform(data): print(rec.Result()) else: print(rec.PartialResult()) print(rec.FinalResult()) |
解释一下上述 Vosk 相关代码调用:
- 首先
SetLogLevel(-1)
用来控制运行过程中日志打印,这里为了清晰的输出选择关闭日志。 - 之后初始化语音识别模型,这里通过指定模型路径的方式完成:
Model(model_path="/path/to/vosk-model-small-en-us-0.15")
,模型的下载地址可以参考官网。 - 然后创建
KaldiRecognizer
对象,SetWords(True)
的作用是后续调用Result()
和FinalResult()
方法时将在输出中包含识别的单词及其时间戳,SetPartialWords(True)
同理。 rec.AcceptWaveform(data)
相关代码的逻辑是:如果AcceptWaveform()
方法返回True
,表明语音识别过程已经找到了一个或多个稳定的词语(比如,话语的间隙或者片段的终点),并且可以通过Result()
方法获取完整的识别结果(返回识别的语句,可能包含了多个词语);如果返回False
,表明语音识别过程还在继续,还没找到稳定的词语,可以通过PartialResult()
方法获取部分识别结果(返回的只是当前识别的部分信息,可能只包含一个或者部分词语)。- 最后
rec.FinalResult()
用来在每次完成一段完整的音频处理(如一句话或一段对话)时调用,用于获取该段音频的最后一部分识别结果。
麦克风输入
以下代码利用 PyAudio 实现从麦克风中获取音频输入,并利用 Vosk 完成语言识别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
rec = KaldiRecognizer(model, 16000) p = PyAudio() stream = p.open(format=paInt16, channels=1, rate=16000, input=True, frames_per_buffer=4096) stream.start_stream() try: while stream.is_active(): data: bytes = stream.read(4096) if rec.AcceptWaveform(data): print(json.loads(rec.Result())) else: res = json.loads(rec.PartialResult()) if res["partial"]: print(res['partial']) except KeyboardInterrupt: print("KeyboardInterrupt...") finally: stream.stop_stream() stream.close() p.terminate() |
补充
- 如果只想获得识别结果文本,可以将
rec.Result()
改为json.loads(rec.Result())["text"]
,将rec.FinalResult()
改为json.loads(rec.FinalResult())["text"]
。但是注意rec.PartialResult()
不存在text
键。 KaldiRecognizer
对象存在Reset()
方法,调用它会重置识别器到初始状态,这意味着识别器会清除之前处理的所有音频数据和中间状态,使用Reset()
方法可以在不创建新识别器的情况下开始新的一轮语音识别。KaldiRecognizer
对象存在SetMaxAlternatives(num)
方法,用来设置识别结果的最大候选项数量,之后Result()
和FinalResult()
方法将会在输出中包含最容易识别为正确结果的num
个候选项。KaldiRecognizer
对象可以使用SetGrammar(json.dumps(grammar_list))
方法,可以用它来设置特定的语法集,这意味着它定义了语音识别应该识别的确切词或短语模式。通俗的来讲,可以把它看作是为识别器编写的一个“听力测试”,测试中只包括预先编写好的词汇和句型。这样识别器就可以忽略无关的语音,只专注于理解这个集合中的内容。