免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

从零构建本地语音输入法:基于Vosk与Whisper的实践指南

从零构建本地语音输入法:基于Vosk与Whisper的实践指南 1. 先搞清楚“废物语音输入法”到底在做什么看到“废物语音输入法”这个标题很多人第一反应可能是“一个不好用的输入法”或者“自嘲的玩具项目”。但结合“【39 more until 2400】”和“22動”来看这更像是一个正在进行中的、带有实验性质的个人开发项目日志。它的核心价值不在于提供一个开箱即用的商业级输入法而在于展示如何从零开始用相对简单或“废物利用”的思路去实现语音转文字这个功能。对于开发者尤其是对语音识别、本地化AI应用或输入法开发感兴趣的新手来说这类项目最大的看点在于“过程”。它拆解了语音输入法这个看似复杂的黑盒让你能看到从麦克风拾音、音频预处理、模型推理到文字上屏的每一个环节。你不需要一开始就追求媲美大厂的识别准确率而是先让整个流程跑通理解每个模块的作用和它们之间的数据流转。所以如果你在找的是一个稳定、高精度的语音输入工具这个项目可能暂时不适合你。但如果你想学习语音识别的基础流程、了解本地运行轻量级模型的坑或者想自己动手攒一个可定制的语音输入原型那么跟随这类项目的迭代记录会比直接看成熟的SDK文档更有启发性。接下来的内容我会基于常见的语音识别项目开发经验帮你梳理从环境搭建到核心环节实现的完整路径并重点说明那些容易卡住的地方。2. 动手前的环境与思路准备在开始敲代码之前有几件事必须想清楚。这决定了你的开发路径是顺畅还是步步踩坑。2.1 明确技术栈与依赖一个最基础的本地语音输入法通常包含几个核心环节音频采集从麦克风实时获取音频流。音频预处理将原始音频流转换成模型能接受的格式如特定的采样率、位深可能还需要做降噪、分帧。语音识别ASR使用模型将音频转换为文本。这是最核心的部分。文本后处理与上屏对识别出的文本进行纠错、添加标点并模拟键盘输入到当前焦点的应用程序中。对应的你的开发环境需要准备好以下依赖音频处理库如PyAudio用于录音、librosa或soundfile用于音频文件处理。在Windows上安装PyAudio有时需要预编译的wheel文件这是第一个小坑。机器学习/推理框架取决于你选择的语音识别模型。如果是PyTorch或TensorFlow训练的模型就需要对应框架。为了轻量化和快速推理很多人会选择ONNX Runtime或OpenVINO。语音识别模型这是关键。你可以选择大型预训练模型如 OpenAI 的 Whisper有不同尺寸的版本。优点是准确率高支持多语言但模型体积大小型号也有几百MB推理需要一定的CPU/GPU算力。轻量级专用模型如Vosk提供的离线模型。它专门为嵌入式或离线场景优化模型小几十MB速度快但可能在某些领域或口音上表现不如大模型。自己训练的小模型这需要语音数据和训练能力不适合入门。模拟键盘输入在Python中可以使用pyautogui或pynput库来模拟键盘事件将识别出的文本“打”出来。我的建议是对于第一次尝试优先选择Vosk的中文小模型 或Whisper tiny模型。它们的平衡性较好既有不错的识别率对硬件要求也相对友好文档和社区资源也丰富。2.2 规划项目结构与数据流不要一上来就写一个巨大的脚本。合理的结构能让调试变得简单。我通常会这样组织speech_input_project/ ├── main.py # 主程序入口控制流程 ├── audio_capture.py # 封装音频采集模块 ├── audio_processor.py # 封装音频预处理模块 ├── asr_engine.py # 封装语音识别引擎加载模型、推理 ├── text_output.py # 封装文本后处理和上屏逻辑 ├── models/ # 存放下载的语音识别模型 │ └── vosk-model-small-cn-0.22/ │ └── whisper-tiny/ ├── config.yaml # 配置文件采样率、模型路径、快捷键等 └── logs/ # 日志目录数据流可以简单理解为麦克风-audio_capture(原始PCM数据) -audio_processor(重采样、分帧等) -asr_engine(文本) -text_output(纠错、上屏)。先让每个模块能独立运行和测试。比如先写audio_capture.py确保它能稳定录音并保存成wav文件。再用一个已知的wav文件去测试asr_engine.py看能否正确识别。最后再把它们串起来。3. 核心模块实现与踩坑点下面我们分模块拆解我会把重点放在实际编码中容易出问题的地方。3.1 音频采集稳定获取声音流使用PyAudio进行录音是常见选择。关键参数是format采样格式、channels声道数、rate采样率和frames_per_buffer每次读取的帧数。import pyaudio import wave def record_audio_to_file(filename, record_seconds5): CHUNK 1024 FORMAT pyaudio.paInt16 CHANNELS 1 RATE 16000 # 很多模型要求16000Hz p pyaudio.PyAudio() stream p.open(formatFORMAT, channelsCHANNELS, rateRATE, inputTrue, frames_per_bufferCHUNK) frames [] print(录音中...) for i in range(0, int(RATE / CHUNK * record_seconds)): data stream.read(CHUNK) frames.append(data) print(录音结束) stream.stop_stream() stream.close() p.terminate() wf wave.open(filename, wb) wf.setnchannels(CHANNELS) wf.setsampwidth(p.get_sample_size(FORMAT)) wf.setframerate(RATE) wf.writeframes(b.join(frames)) wf.close() # 测试录音 record_audio_to_file(test.wav)踩坑点PyAudio安装在Windows上直接pip install pyaudio可能会失败。建议使用pip install pipwin然后pipwin install pyaudio。采样率匹配你录音的采样率RATE必须与后续ASR模型期望的采样率一致。Whisper通常是16000HzVosk模型也多用16000Hz。不一致会导致识别率骤降或失败。权限问题在macOS或Linux上确保程序有访问麦克风的权限。实时流处理上面的例子是录固定时长存文件。在实际输入法中你需要一个循环持续读取stream.read(CHUNK)的数据并送入一个缓冲区供后续模块处理。这里要注意缓冲区的大小和管理避免内存堆积。3.2 语音识别引擎加载模型与推理这里以Vosk和Whisper为例展示两种风格。方案A使用 Vosk轻量流式友好from vosk import Model, KaldiRecognizer import json class VoskASREngine: def __init__(self, model_path): # 加载模型这里路径指向你下载的模型目录 self.model Model(model_path) self.recognizer KaldiRecognizer(self.model, 16000) self.recognizer.SetWords(True) # 可选返回词级时间戳 def transcribe_audio_data(self, audio_data): 接收PCM音频数据16kHz, 16bit, mono返回识别文本 if self.recognizer.AcceptWaveform(audio_data): result json.loads(self.recognizer.Result()) return result.get(text, ) else: # Partial result can be fetched with recognizer.PartialResult() # 对于实时输入我们可能更关注最终结果这里先返回空 return None def transcribe_file(self, filename): 识别整个音频文件 import wave wf wave.open(filename, rb) # 检查音频参数是否匹配 if wf.getnchannels() ! 1 or wf.getsampwidth() ! 2 or wf.getframerate() ! 16000: print(音频格式不符合要求需转换) return None data wf.readframes(wf.getnframes()) if self.recognizer.AcceptWaveform(data): result json.loads(self.recognizer.Result()) return result.get(text, ) return None # 使用示例 engine VoskASREngine(./models/vosk-model-small-cn-0.22) text engine.transcribe_file(test.wav) print(f识别结果{text})方案B使用 Whisper功能强精度高import whisper class WhisperASREngine: def __init__(self, model_sizetiny, devicecpu): # 首次运行会下载模型模型会保存在 ~/.cache/whisper/ self.model whisper.load_model(model_size, devicedevice) # 定义识别选项比如语言、是否带时间戳等 self.options { language: zh, task: transcribe, # 转录 fp16: False, # CPU上设为False } def transcribe_file(self, filename): 识别整个音频文件 result self.model.transcribe(filename, **self.options) return result[text] def transcribe_audio_array(self, audio_numpy_array, sr16000): 识别numpy数组格式的音频需要是float32格式 # 注意Whisper的transcribe也接受numpy数组但要求是float32且数值在-1到1之间 import numpy as np if audio_numpy_array.dtype ! np.float32: audio_numpy_array audio_numpy_array.astype(np.float32) / 32768.0 # 假设输入是int16 result self.model.transcribe(audio_numpy_array, **self.options) return result[text] # 使用示例 engine WhisperASREngine(model_sizetiny, devicecpu) # device可设为cuda text engine.transcribe_file(test.wav) print(f识别结果{text})踩坑点与选择建议模型下载Whisper 模型第一次加载时会从网络下载需要稳定的网络环境。Vosk模型需要手动下载并解压到指定目录。性能与资源Whisper-tiny 在CPU上推理一段几秒的音频也可能需要1-3秒不适合极低延迟的实时输入。Vosk的速度通常更快延迟更低。如果你的目标是“实时语音输入”Vosz的流式识别特性更有优势。内存占用Whisper模型加载后占用内存较大。Vosk相对较小。实时性处理Whisper 更适合“说完一段话统一识别”的模式。Vosz 的AcceptWaveform可以逐块喂数据更适合实现“边说边识别”的效果。准确率通常同等级别下Whisper的准确率高于Vosk尤其是对于复杂背景音或口音。我的建议先实现“按键说话松开识别”的模式。这样逻辑简单按下快捷键开始录音松开快捷键停止录音然后将录制的整段音频交给识别引擎Whisper或Vosk均可。这个模式能跑通再考虑更复杂的流式识别。3.3 文本上屏把文字“打”到光标处识别出文本后我们需要模拟键盘输入。pyautogui是最简单直接的选择。import pyautogui import time class TextOutput: def __init__(self): # 为了安全设置一个延迟防止失控 pyautogui.PAUSE 0.02 def type_text(self, text): 将文本输入到当前焦点窗口 if not text: return # 注意pyautogui.typewrite 不支持直接输入中文。 # 我们需要借助剪贴板 import pyperclip original_clipboard pyperclip.paste() # 备份原剪贴板内容 try: pyperclip.copy(text) # 模拟 CtrlV 粘贴 pyautogui.hotkey(ctrl, v) time.sleep(0.05) # 给粘贴一点时间 finally: # 恢复原剪贴板内容可选但比较友好 pyautogui.hotkey(ctrl, a) time.sleep(0.02) pyautogui.hotkey(ctrl, c) time.sleep(0.05) # 这里理论上应该恢复但操作复杂且可能干扰用户生产环境可省略 # pyperclip.copy(original_clipboard) # 使用示例 output TextOutput() output.type_text(你好世界)踩坑点中文输入pyautogui.typewrite()函数无法直接输入中文它模拟的是物理键盘按键。所以我们必须通过“复制-粘贴”的方式。剪贴板冲突我们的程序会覆盖系统剪贴板。上面的代码尝试备份和恢复但在实际中如果用户在我们操作期间复制了其他内容这个逻辑会变得复杂。一个更简单的方案是不恢复或者弹窗提示用户。对于个人使用的工具不恢复可能是可接受的。焦点问题确保在你触发输入时目标窗口如记事本、浏览器输入框是激活状态。pyautogui无法强制切换窗口焦点。权限在macOS上模拟键盘输入可能需要辅助功能权限。在Linux上可能需要特定的环境如X11。3.4 主循环与热键控制将以上模块串联起来。我们使用pynput来监听全局热键因为它比pyautogui的监听更稳定。from pynput import keyboard import threading import time from audio_capture import AudioCapture # 假设这是你封装好的录音类 from asr_engine import WhisperASREngine # 或 VoskASREngine from text_output import TextOutput class SpeechInputApp: def __init__(self): self.is_recording False self.audio_capture AudioCapture() self.asr_engine WhisperASREngine(model_sizetiny) self.text_output TextOutput() self.audio_data [] def on_press(self, key): # 这里假设按 F2 键开始录音 if key keyboard.Key.f2 and not self.is_recording: self.is_recording True self.audio_data [] print([开始录音]) # 在新线程中开始录音避免阻塞热键监听 self.recording_thread threading.Thread(targetself.record_loop) self.recording_thread.start() def on_release(self, key): # 松开 F2 键停止录音并识别 if key keyboard.Key.f2 and self.is_recording: self.is_recording False print([停止录音开始识别...]) # 等待录音线程结束 self.recording_thread.join() # 处理录音数据 if self.audio_data: # 将音频数据保存为临时文件或直接传给引擎 # 这里以保存为文件为例 temp_file temp_recording.wav self.audio_capture.save_to_wav(self.audio_data, temp_file) # 识别 text self.asr_engine.transcribe_file(temp_file) print(f识别结果{text}) # 上屏 if text: self.text_output.type_text(text) print([识别完成]) def record_loop(self): 录音循环 while self.is_recording: # 假设 audio_capture.get_chunk() 返回一小段PCM数据 chunk self.audio_capture.get_chunk() if chunk: self.audio_data.append(chunk) time.sleep(0.01) # 避免CPU空转 def run(self): print(语音输入法已启动按住 F2 说话松开识别。) with keyboard.Listener(on_pressself.on_press, on_releaseself.on_release) as listener: listener.join() if __name__ __main__: app SpeechInputApp() app.run()踩坑点热键冲突F2键可能被其他程序占用。可以选择不常用的组合键如CtrlShift[但pynput在某些系统上对复杂组合键的支持可能有问题需要测试。线程安全录音和主事件监听在不同线程对共享变量is_recording和audio_data的访问要确保安全。这里用了简单的标志位对于复杂场景可能需要用threading.Lock。性能主循环中的time.sleep值会影响录音的实时性和CPU占用。需要根据CHUNK大小调整。异常处理真实环境中要添加大量异常处理比如麦克风被占用、模型加载失败、剪贴板访问失败等。4. 从能跑到好用优化与问题排查让程序跑起来只是第一步。要成为一个“勉强可用”的废物输入法还需要解决以下问题。4.1 提升识别准确率与体验音频预处理在录音后、识别前可以加入简单的预处理。端点检测VAD自动检测语音的开始和结束而不是固定时长录音。可以使用webrtcvad库。这能有效去除静音段提升识别效率和准确率。降噪使用noisereduce等库进行简单的降噪处理。对于环境噪声较大的场景有奇效。音量归一化确保音频音量在一个合理的范围内。模型选择如果 Whisper-tiny 准确率不够可以尝试base甚至small模型但代价是速度更慢、内存占用更大。需要在速度和精度间权衡。文本后处理标点恢复Whisper 自带标点但 Vosz 可能没有。可以接入一个简单的标点预测模型或者使用规则根据停顿时间添加逗号句号。纠错对于特定领域词汇可以维护一个纠错词典对识别结果进行替换。4.2 降低延迟与资源占用流式识别这是降低延迟的关键。Vosz 原生支持。对于 Whisper可以使用其流式转录的社区实现如faster-whisper或whisper.cpp但复杂度较高。第一步先实现非流式稳定后再考虑流式。优化推理使用faster-whisper基于CTranslate2它比原版 Whisper 快数倍内存占用更少。如果使用GPU确保CUDA/cuDNN安装正确并将模型加载到GPU上。使用int8量化模型可以大幅减少内存占用和提升速度但可能轻微损失精度。精简依赖检查是否安装了不必要的库。使用虚拟环境管理项目依赖。4.3 常见问题排查清单当你的“废物输入法”不工作时按这个顺序查没声音/录音失败检查麦克风权限是否授予。检查PyAudio是否找到了正确的输入设备索引。尝试用系统录音机确认麦克风本身是好的。识别结果全是空或乱码首先检查音频格式采样率16000Hz、位深16bit、声道数单声道是否与模型要求严格一致。用Audacity或sox查看音频文件属性。检查模型路径是否正确模型文件是否完整。如果是Vosz检查是否初始化时传入了正确的采样率。尝试用一个标准的、清晰的普通话测试音频文件比如新闻片段输入排除麦克风录音质量的问题。程序卡死或无响应检查热键监听是否被阻塞。确保耗时操作录音、识别放在独立线程中。检查识别模型推理时间。第一次推理可能较慢后续会快。如果一直很慢考虑换更小模型或优化推理环境。查看任务管理器确认内存或CPU是否被占满。文本无法输入到正确窗口确认目标窗口在触发输入前已被手动激活。检查pyautogui的粘贴操作是否被其他软件的快捷键拦截。在Linux上尝试设置export DISPLAY:0等环境变量。识别速度慢确认使用的是CPU还是GPU。在CPU上Whisper-small 以上模型会较慢。考虑使用faster-whisper。缩短单次录音时长或者实现真正的流式识别。4.4 把它变得像样一点配置文件将模型路径、采样率、热键、录音时长等参数放到config.yaml或config.ini中方便修改。日志系统使用logging模块记录程序运行状态、错误信息方便排查。简单UI使用tkinter或PyQt做一个简单的状态托盘图标显示当前是“就绪”、“录音中”还是“识别中”并允许配置热键。安装与打包使用pyinstaller或cx_Freeze将脚本打包成可执行文件方便分发。5. 总结从“废物”到“可用的玩具”开发一个本地语音输入法最大的收获不是造出一个替代搜狗的产品而是彻底理解了一个AI应用从数据输入到结果输出的完整链条。你会遇到音频处理、模型推理、多线程、全局热键、系统兼容性等一系列实际问题每一个问题的解决都是实打实的经验。我的核心建议是分步走第一步用最少的代码实现“录音 - 保存文件 - 调用现成库识别文件 - 打印结果”。确保这个单向流程能跑通。第二步加入热键控制实现“按键录音松键识别并打印”。第三步用剪贴板方案实现文本上屏完成闭环。第四步优化。加入VAD、尝试流式识别、换用更快模型、增加配置和日志。在这个过程中【39 more until 2400】这样的计数可能意味着作者在持续迭代每次解决一个小问题或增加一个小功能。你可以借鉴这种思路为自己的项目设立小目标逐个击破。最终这个“废物”项目会成为你技能树中非常扎实的一块。
返回列表