一、前言
前面我们详细介绍了文本转语音的细节和实践,今天我们继续探讨一下语音转文本(ASR),初次接触,OpenAI Whisper 是最易上手、效果最均衡的开源大模型,它无需复杂的专业知识,一行代码就能实现多语言语音转写,且在噪声、口音、多语言场景下的表现远优于传统 ASR。
今天我们从基础概念入手,逐行拆解代码、详解核心参数,结合实际场景选择参数提升转录准确性,覆盖从零基础运行到精准适配场景的全流程,所有内容优先讲解基础点,确保我们都能理解、能举一反三的可用复用。
二、基础概念
1. 语音转文本(ASR)
ASR,全称Automatic Speech Recognition,即自动语音识别,核心是把人类说话的音频信号转换成文字。日常用的微信语音转文字、会议纪要自动生成,本质都是 ASR 技术。
核心评价指标:字错率(WER),简单理解为 “转错的字数/总字数”,数值越低,转录越准确(比如 WER=5%,代表 100 个字里错 5 个)。
2. Whisper模型特征
Whisper 是 OpenAI 开源的 ASR 大模型,新手只需记住 3 个核心特点:
- 端到端设计:无需手动处理音频特征(比如传统 ASR 要做的 MFCC 特征提取),直接输入音频就能输出文本,新手无需懂声学知识;
- 多语言支持:原生支持 99 种语言,包括中文(普通话 / 方言)、英文、日语等,无需单独训练;
- 预训练数据足:基于 68 万小时多语言标注音频训练,噪声、口音、不同语速的适配性都很强。
3. Whisper模型分类
Whisper 提供 5 种预训练模型尺寸,新手可简单理解为 “模型越大,越准但越慢、占内存越多”,各尺寸的基础属性如下(新手重点看 “适用场景”):
- tiny 尺寸:参数量为 39M(百万),中文安静场景下的字错率(WER)约 8.5%,仅需要 CPU 就能运行,适合快速粗略转写(比如语音消息);
- base 尺寸:参数量为 74M,中文安静场景下的字错率(WER)约 6.2%,仅需要 CPU 就能运行,是新手首选(平衡准度和速度);
- small 尺寸:参数量为 244M,中文安静场景下的字错率(WER)约 4.1%,CPU 或 GPU 都能运行,适合要求稍高的通用场景;
- medium 尺寸:参数量为 769M,中文安静场景下的字错率(WER)约 2.8%,需要 GPU(显存≥4GB)才能流畅运行,适合高精度需求(比如会议记录);
- large-v3 尺寸:参数量为 1550M,中文安静场景下的字错率(WER)约 2.1%,需要 GPU(显存≥8GB)才能运行,适合专业场景(比如医疗、法律领域)。
4. Whisper与传统ASR的差异
| 维度 | 传统 ASR(如 MFCC+HMM) | Whisper 大模型 |
| 语言支持 | 需单独训练单语言模型 | 原生支持 99 种语言,无需额外适配 |
| 噪声鲁棒性 | 噪声场景 WER 飙升至 30%+ | 80dB 噪声下 WER 仍 < 15% |
| 特征提取 | 手动设计声学特征(MFCC) | 端到端自学习特征,适配复杂音频 |
| 部署成本 | 需部署特征提取、解码等多模块 | 单模型即可完成从音频到文本的转换 |
三、基础示例精析
转录音频内容,支持 WAV/MP3/M4A 等格式,实现音频内容解析输出
1. 示例代码
# 第一步:导入Whisper库(基础操作,所有功能都需要) import whisper # 第二步:定义基础转录函数 def transcribe_audio_basic(audio_path): # 2.1 加载模型:选择base尺寸 # load_model是Whisper的核心函数,参数传模型尺寸 model = whisper.load_model("base") # 2.2 执行转录:传入音频路径,得到结果 # transcribe是实现“音频→文本”的核心方法 result = model.transcribe(audio_path) # 2.3 返回关键结果(新手先关注text和language) return { "text": result["text"], # 完整转录文本 "language": result["language"], # 检测到的语言 "segments": result["segments"] # 段落时间戳 } # 第三步:调用函数并打印结果 if __name__ == "__main__": # 替换为你的音频文件路径 audio_path = "qqqq.wav" # 调用转录函数 result = transcribe_audio_basic(audio_path) # 打印结果(基础输出,看是否转写成功) print(f"转录文本: {result['text']}") print(f"检测语言: {result['language']}")
2. 输出结果
100%|████████████████████████████| 139M/139M [00:03<00:00, 46.9MiB/s]
C:\ProgramData\Anaconda3\lib\site-packages\whisper\transcribe.py:132: UserWarning: FP16 is not supported on CPU; using FP32 instead
warnings.warn("FP16 is not supported on CPU; using FP32 instead")
转录文本: 欢迎使用DDS文本朗读器。请在这里输入想要朗读的文本。
检测语言: zh
3. 结果解析
3.1. 逐行解析:
- 第一行:下载 base 模型权重(139M),首次运行会下载,后续直接复用;
- 警告行:FP16 is not supported on CPU,简单理解:
- FP16/FP32 是数据精度,FP16 更快、占内存更少,但 CPU 不支持,自动用 FP32;
- 仅影响速度,不影响转写结果,GPU 运行时会自动用 FP16,无此警告;
- 转录文本:音频对应的文字内容;
- 检测语言:zh代表中文(ISO 639-1 标准码,en= 英文、ja= 日语)。
3.2. 常见问题:
- 报错 “找不到音频文件”:检查audio_path是否正确(建议用绝对路径,如C:\audio\qqqq.wav);
- 报错 “ffmpeg not found”:按 2.1 节补充安装 ffmpeg 并配置环境变量;
- 转写结果乱码:打印时加ensure_ascii=False,如print(f"转录文本: {result['text']}", ensure_ascii=False)。
4. 核心参数详解
提升转写准确性的第一步,必须要搞懂“模型加载”和“转录方法”的核心参数,参数选择的合理,准确性可直接大幅度提升。
4.1 模型加载参数
模型加载方式 whisper.load_model(),仅 1 个核心参数
- 参数名:model_size
- 基础含义:选择预训练模型的尺寸,决定 “准度 - 速度 - 内存” 的平衡;
- 取值范围:tiny、base、small、medium、large-v3,可对应前面的模型分类详细了解;
- 选择建议:仅 CPU 选 base;有 GPU(显存≥4GB)选 medium。
4.2 转录方法参数
model.transcribe() 是实现转写的核心方法,重点先了解以下 6 个基础参数(按重要性排序):
4.2.1 audio 参数:
- 基础含义:输入的音频(必须传入);
- 常用取值:音频文件路径(比如 "qqqq.wav");
- 基础作用:指定要转写的音频;
- 使用建议:直接传文件路径即可。
4.2.2 language 参数:
- 基础含义:指定转写语言;
- 常用取值:"zh"(中文)、"en"(英文)、None(自动检测);
- 基础作用:避免模型误判语言(比如把中文识别成日语);
- 使用建议:纯中文音频必传 "zh",提升准确性。
4.2.3 task 参数:
- 基础含义:任务类型;
- 常用取值:"transcribe"(转写)、"translate"(翻译为英文);
- 基础作用:决定是 “转写成本语言” 还是 “翻译为英文”;
- 使用建议:默认用 "transcribe"。
4.2.4 temperature 参数:
- 基础含义:控制输出随机性;
- 常用取值:0.0(首选);
- 基础作用:0.0 代表结果固定(无随机错误),数值越高代表结果越随机;
- 使用建议:非创意场景(如会议记录)必设为 0.0。
4.2.5 beam_size 参数:
- 基础含义:集束搜索宽度(通俗理解:模型尝试的 “候选文本数量”);
- 常用取值:5(默认)、8、10;
- 基础作用:数值越大,越易找到 “最准确的文本”,但速度越慢;
- 使用建议:通用场景设 8,噪声场景设 10。
4.2.6 best_of 参数:
- 基础含义:集束搜索候选数;
- 常用取值:5(默认)、6、8;
- 基础作用:生成多组候选结果,选最优的一组;
- 使用建议:与 beam_size 配合,beam_size=8 则 best_of=6。
整体参数示例:
# 优化中文转写准确性的基础配置 result = model.transcribe( audio_path, language="zh", # 强制指定中文,避免误判 temperature=0.0, # 固定结果,无随机错误 beam_size=8, # 增加候选数量,提升准确性 best_of=6 # 配合beam_size,选最优结果 )
4.3 返回结果参数
transcribe返回的result是一个字典,我们可以先从以下 3 个基础字段入手:
4.3.1 text 字段:
- 基础含义:完整的转写文本;
- 示例值:"欢迎使用 DDS 文本朗读器。";
- 使用场景:直接获取转写结果。
4.3.2 language 字段:
- 基础含义:检测到的语言码;
- 示例值:"zh";
- 使用场景:验证模型是否正确识别语言。
4.3.3 segments 字段:
- 基础含义:段落级时间戳列表;
- 示例值:[{"start": 0.0, "end": 3.5, "text": "欢迎使用 DDS 文本朗读器。"}, ...];
- 使用场景:查看 “某段话出现在音频的第几秒”。
segments输出示例:
# 在上方代码的函数中添加以下代码,打印段落时间戳 for segment in result["segments"]: print(f"[{segment['start']:.1f}秒 - {segment['end']:.1f}秒]:{segment['text']}") #输出结果 #[0.0秒 - 3.5秒]: 欢迎使用DDS文本朗读器。 #[3.5秒 - 6.8秒]: 请在这里输入想要朗读的文本。
5. 执行流程
流程细节说明:
- 1. 环境准备:安装必要的依赖库和工具,主要包括:ASR相关库(如transformers, whisper)、音频处理库(如librosa, soundfile)
- 2. 模型选择:根据需求选择合适的语音识别模型:开源模型:Whisper、商用API、多语言/单语言模型选择
- 3. 参数配置:设置转录过程的关键参数:语言选择(自动检测或指定)、任务类型(转录/翻译)、采样率设置
- 4. 音频转录:执行核心的音频转文本处理:音频预处理(降噪、归一化)、特征提取(MFCC, Mel频谱)、模型推理、解码输出
- 5. 结果处理:对原始转录结果进行处理:提取文本内容、获取时间戳信息、说话人分离、置信度评估
- 6. 优化调整:根据结果进行优化:参数调优(温度、集束搜索宽度)、后处理(标点恢复、数字规范化)、错误校正、性能评估
- 7. 完成转录:输出最终结果:完整转录文本、带时间戳的文本分段、元数据(语言、置信度等)、可选格式(TXT, SRT, JSON等)
这个流程提供了从音频到文本的完整转录路径,每个步骤都是构建高质量语音识别系统的重要组成部分。
四、模型参数选择
提升转写准确性,无需复杂操作,按“先选模型→再调基础参数→最后适配场景”的步骤即可,每一步都基于基础操作。
1. 第一步:选对模型尺寸
模型尺寸是准确性的基础,尺寸越大,准度越高,我们需要按硬件选择即可,不同硬件对应的模型选择、准确性和转写速度如下:
- CPU(4 核 8G):选择 base 模型(首选),中文安静场景下的准确性为 WER≈6.2%,转写 1 分钟音频大约需要 15 秒;
- CPU(8 核 16G):选择 small 模型,中文安静场景下的准确性为 WER≈4.1%,转写 1 分钟音频大约需要 30 秒;
- GPU(显存≥4GB):选择 medium 模型,中文安静场景下的准确性为 WER≈2.8%,转写 1 分钟音频大约需要 1 分钟;
- GPU(显存≥8GB):选择 large-v3 模型,中文安静场景下的准确性为 WER≈2.1%,转写 1 分钟音频大约需要 2 分钟。
2. 第二步:调优基础参数
选好模型后,先改 2 个最易上手的参数,可直接提升准确性:
- 强制指定 language:纯中文音频必传language="zh",避免模型误判为日语/韩语;
- 固定 temperature=0.0:避免随机错误(如漏字、错字)。
3. 第三步:进阶参数调优
若基础参数调整后仍需提升准确性,再调以下 2 个参数(平衡准度和速度),不同场景的参数取值和基础效果如下:
- 通用安静场景(如录音笔记):beam_size 设为 8,best_of 设为 6,准确性提升 3%-5%,速度慢 20%;
- 噪声/口音场景(如户外录音):beam_size 设为 10,best_of 设为 8,准确性提升 5%-8%,速度慢 30%;
- 专业场景(如医疗/法律):beam_size 设为 12,best_of 设为 8,术语识别更准,速度慢 40%。
4. 第四步:场景化基础配置
不同场景的参数最优组合不同,我们可以按以下配置调整后观察输出效果,持续的微调优化:
场景 1:通用安静短音频(如语音消息、录音笔记)
def transcribe_basic_scene(audio_path): model = whisper.load_model("base") # CPU首选 result = model.transcribe( audio_path, language="zh", temperature=0.0, beam_size=8, best_of=6 ) return result
场景 2:噪声 / 口音重音频(如户外录音、方言口音)
def transcribe_noise_scene(audio_path): model = whisper.load_model("small") # 比base更准 result = model.transcribe( audio_path, language="zh", temperature=0.0, beam_size=10, best_of=8 ) return result
场景 3:长音频(如会议录音,>5 分钟)
def transcribe_long_audio(audio_path): model = whisper.load_model("medium") # 有GPU优先选 result = model.transcribe( audio_path, language="zh", temperature=0.0, beam_size=8, best_of=6, condition_on_previous_text=True # 保持上下文连贯(新手无需改,默认True) ) return result
五、进阶扩展
1. 带词级时间戳的转录
实现精细化转写,我们可以开启word_timestamps=True,获取每个单词的精准时间戳(如制作字幕):
def transcribe_with_word_timestamps(audio_path): model = whisper.load_model("base") result = model.transcribe( audio_path, language="zh", temperature=0.0, word_timestamps=True # 启用词级时间戳 ) # 打印词级时间戳(新手可理解为“每个字/词的起止时间”) for segment in result["segments"]: print(f"[{segment['start']:.1f}秒 - {segment['end']:.1f}秒]:{segment['text']}") for word in segment["words"]: print(f" 「{word['word']}」:{word['start']:.1f}秒 - {word['end']:.1f}秒") return result
输出示例:
[0.0秒 - 3.5秒]: 欢迎使用DDS文本朗读器。 「欢迎」:0.0秒 - 0.5秒 「使用」:0.5秒 - 1.0秒 「DDS」:1.0秒 - 1.5秒 「文本」:1.5秒 - 2.0秒 「朗读器」:2.0秒 - 3.5秒
2. 批量转录
处理多个音频文件,我们可以封装批量处理函数,避免重复操作,增加基础异常处理,防止单个文件报错中断:
def batch_transcribe(audio_files): # audio_files是音频文件路径列表,如["audio1.wav", "audio2.wav"] model = whisper.load_model("base") results = {} for file in audio_files: try: print(f"正在处理:{file}") result = model.transcribe(file, language="zh", temperature=0.0) results[file] = result["text"] # 仅保存转写文本 except Exception as e: print(f"处理{file}失败:{str(e)}") results[file] = "转写失败" return results audio_list = ["meeting1.wav", "meeting2.wav"] batch_results = batch_transcribe(audio_list) # 打印批量结果 for file, text in batch_results.items(): print(f"\n{file}转写结果:") print(text)
3. 说话人分离
识别“谁在什么时候说什么”,结合pyannote.audio实现“说话人 + 文本 + 时间戳”的转写
def transcribe_with_speaker(audio_path, hf_token): # 第一步:基础转写(获取文本+时间戳) model = whisper.load_model("base") transcribe_result = model.transcribe( audio_path, language="zh", temperature=0.0, word_timestamps=True ) # 第二步:加载说话人分离模型 from pyannote.audio import Pipeline diarization_pipeline = Pipeline.from_pretrained( "pyannote/speaker-diarization-3.1", use_auth_token=hf_token # 替换为你的HuggingFace令牌 ) diarization_result = diarization_pipeline(audio_path) # 第三步:匹配“说话人-文本-时间戳” final_result = [] for segment in transcribe_result["segments"]: # 找到该时间段内的主要说话人 speaker = "unknown" for turn, _, spk in diarization_result.itertracks(yield_label=True): if turn.start <= segment["end"] and turn.end >= segment["start"]: speaker = spk break # 保存结果 final_result.append({ "start": segment["start"], "end": segment["end"], "speaker": speaker, "text": segment["text"] }) # 打印结果 for item in final_result: print(f"[{item['start']:.1f}秒 - {item['end']:.1f}秒] {item['speaker']}:{item['text']}") return final_result hf_token = "你的HuggingFace令牌" transcribe_with_speaker("group_discussion.wav", hf_token)
输出示例:
[0.0秒 - 5.0秒] SPEAKER_00: 今天我们讨论一下项目进度。 [5.0秒 - 10.0秒] SPEAKER_01: 我这边的模块已经完成了80%。
六、辅助优化
1. 音频预处理(降噪)
进一步提升转写的准确性,可以对噪声音频先降噪,再进行转写操作,需要应用安装noisereduce库。
import noisereduce as nr import soundfile as sf # 读取音频 data, rate = sf.read(audio_path) # 提取噪声样本(取音频前1秒作为噪声) noise_sample = data[:rate] # 降噪 reduced_noise = nr.reduce_noise(y=data, y_noise=noise_sample, sr=rate) # 保存降噪后的音频 sf.write("audio_denoised.wav", reduced_noise, rate) # 对降噪后的音频转写 model.transcribe("audio_denoised.wav", language="zh")
2. 简单后处理(校正错字)
result_text = result["text"] # 替换常见错字 corrected_text = result_text.replace("北惊", "北京").replace("百份之", "百分之") print("校正后文本:", corrected_text)
七、应用示例
1. 完整的应用实例
包括单文件转录、批量转录以及带说话人分离的转录
import whisper import numpy as np import torch from typing import Dict, List import json class AdvancedTranscriber: """ 高级语音转录器,包含多种优化功能 """ def __init__(self, model_size="base", device="cuda"): """ 初始化转录器 Args: model_size: 模型尺寸 (tiny, base, small, medium, large) device: 计算设备 (cuda/cpu) """ self.device = device if torch.cuda.is_available() else "cpu" self.model = whisper.load_model(model_size).to(self.device) # 配置转录参数 self.transcribe_options = { "language": "zh", # 指定语言(可选自动检测) "task": "transcribe", # transcribe或translate "temperature": 0.0, # 采样温度(0为确定性) "best_of": 5, # 集束搜索候选数 "beam_size": 5, # 集束搜索宽度 "fp16": True if self.device == "cuda" else False, } def transcribe_with_timestamps(self, audio_path: str) -> Dict: """ 带时间戳的详细转录 """ result = self.model.transcribe( audio_path, **self.transcribe_options, word_timestamps=True # 启用词级时间戳 ) # 结构化输出 structured_output = { "full_text": result["text"], "language": result["language"], "duration": self._get_audio_duration(audio_path), "segments": [] } for segment in result["segments"]: segment_info = { "start": segment["start"], "end": segment["end"], "text": segment["text"], "confidence": segment.get("confidence", 0), "words": segment.get("words", []) } structured_output["segments"].append(segment_info) return structured_output def batch_transcribe(self, audio_files: List[str]) -> Dict[str, Dict]: """ 批量转录多个音频文件 """ results = {} for audio_file in audio_files: try: print(f"正在处理: {audio_file}") result = self.transcribe_with_timestamps(audio_file) results[audio_file] = result except Exception as e: print(f"处理失败 {audio_file}: {str(e)}") results[audio_file] = {"error": str(e)} return results def transcribe_with_speaker_diarization(self, audio_path: str): """ 结合说话人分离的转录 需要额外安装:pip install pyannote.audio """ from pyannote.audio import Pipeline # 第一步:使用Whisper转录 transcription_result = self.transcribe_with_timestamps(audio_path) # 第二步:说话人分离 diarization_pipeline = Pipeline.from_pretrained( "pyannote/speaker-diarization-3.1", use_auth_token="YOUR_HUGGINGFACE_TOKEN" ) diarization_result = diarization_pipeline(audio_path) # 第三步:对齐说话人标签和转录 aligned_result = self._align_speakers_with_transcription( transcription_result, diarization_result ) return aligned_result def _align_speakers_with_transcription(self, transcription, diarization): """ 对齐说话人标签和转录文本 """ aligned_segments = [] for transcript_segment in transcription["segments"]: # 找到该时间段内的说话人 segment_start = transcript_segment["start"] segment_end = transcript_segment["end"] # 收集该时间段的所有说话人 speakers_in_segment = [] for turn, _, speaker in diarization.itertracks(yield_label=True): if turn.start <= segment_end and turn.end >= segment_start: overlap_start = max(turn.start, segment_start) overlap_end = min(turn.end, segment_end) overlap_duration = overlap_end - overlap_start speakers_in_segment.append({ "speaker": speaker, "overlap_duration": overlap_duration }) # 选择重叠时间最长的说话人 if speakers_in_segment: main_speaker = max( speakers_in_segment, key=lambda x: x["overlap_duration"] )["speaker"] transcript_segment["speaker"] = main_speaker else: transcript_segment["speaker"] = "unknown" aligned_segments.append(transcript_segment) transcription["segments"] = aligned_segments return transcription def _get_audio_duration(self, audio_path: str) -> float: """获取音频时长""" import soundfile as sf data, sample_rate = sf.read(audio_path) return len(data) / sample_rate # 使用示例 transcriber = AdvancedTranscriber(model_size="small") # 单文件转录 result = transcriber.transcribe_with_timestamps("qqqq.wav") print(json.dumps(result, ensure_ascii=False, indent=2)) # 批量转录 batch_results = transcriber.batch_transcribe([ "meeting1.wav", "meeting2.wav", "presentation.wav" ]) # 带说话人分离的转录 diarization_result = transcriber.transcribe_with_speaker_diarization( "group_discussion.wav" )
输出结果:
{
"full_text": "欢迎使用GDS文本朗读器,请在这里输入想要朗读的文本。",
"language": "zh",
"duration": 11.321179138321995,
"segments": [
{
"start": 0.0,
"end": 9.98,
"text": "欢迎使用GDS文本朗读器,请在这里输入想要朗读的文本。",
"confidence": 0,
"words": [
{
"word": "欢",
"start": 0.0,
"end": 0.44,
"probability": 0.6587960124015808
},
{
"word": "迎",
"start": 0.44,
"end": 0.78,
"probability": 0.9997932314872742
},
{
"word": "使",
"start": 0.78,
"end": 1.16,
"probability": 0.9810769557952881
},
{
"word": "用",
"start": 1.16,
"end": 1.5,
"probability": 0.9999412298202515
},
{
"word": "G",
"start": 1.5,
"end": 1.84,
"probability": 0.5033477544784546
},
{
"word": "DS",
"start": 1.84,
"end": 2.3,
"probability": 0.9630266427993774
},
{
"word": "文",
"start": 2.3,
"end": 2.86,
"probability": 0.698738694190979
},
{
"word": "本",
"start": 2.86,
"end": 3.28,
"probability": 0.9940272569656372
},
{
"word": "朗",
"start": 3.28,
"end": 3.62,
"probability": 0.7624131739139557
},
{
"word": "读",
"start": 3.62,
"end": 3.88,
"probability": 0.985887736082077
},
{
"word": "器,",
"start": 3.88,
"end": 4.3,
"probability": 0.9958699345588684
},
{
"word": "请",
"start": 5.44,
"end": 6.06,
"probability": 0.9576152563095093
},
{
"word": "在",
"start": 6.06,
"end": 6.46,
"probability": 0.9904884099960327
},
{
"word": "这里",
"start": 6.46,
"end": 6.96,
"probability": 0.9948416352272034
},
{
"word": "输",
"start": 6.96,
"end": 7.42,
"probability": 0.9913595616817474
},
{
"word": "入",
"start": 7.42,
"end": 7.74,
"probability": 0.9998476505279541
},
{
"word": "想",
"start": 7.74,
"end": 8.04,
"probability": 0.9758610725402832
},
{
"word": "要",
"start": 8.04,
"end": 8.46,
"probability": 0.9975632429122925
},
{
"word": "朗",
"start": 8.46,
"end": 8.82,
"probability": 0.9854574501514435
},
{
"word": "读",
"start": 8.82,
"end": 9.14,
"probability": 0.9997773170471191
},
{
"word": "的",
"start": 9.14,
"end": 9.36,
"probability": 0.9987922310829163
},
{
"word": "文",
"start": 9.36,
"end": 9.64,
"probability": 0.9949055910110474
},
{
"word": "本。",
"start": 9.64,
"end": 9.98,
"probability": 0.9980852603912354
}
]
}
]
}
2. 转录后处理
处理转录后的内容,包括标点恢复、数字规范化等,可根据实际情况调整
import re from typing import List, Dict class TranscriptionPostProcessor: """ 转录后处理:标点恢复、数字规范化等 """ def __init__(self, language="zh"): self.language = language # 定义正则规则 self.patterns = { "numbers": { "zh": r"(\d+)", "en": r"(\d+)" }, "urls": r"https?://\S+|www\.\S+", "emails": r"\S+@\S+\.\S+" } # 数字转换规则(中文) self.number_map_zh = { '0': '零', '1': '一', '2': '二', '3': '三', '4': '四', '5': '五', '6': '六', '7': '七', '8': '八', '9': '九' } def process_transcription(self, text: str) -> str: """ 完整的后处理流程 """ # 1. 清理多余空格 text = self._clean_spaces(text) # 2. 标点标准化 text = self._normalize_punctuation(text) # 3. 数字规范化 if self.language == "zh": text = self._normalize_numbers_chinese(text) elif self.language == "en": text = self._normalize_numbers_english(text) # 4. 大写规范化(英文) if self.language == "en": text = self._normalize_capitalization(text) # 5. 分割过长的句子 text = self._split_long_sentences(text) return text def _clean_spaces(self, text: str) -> str: """清理多余空格""" # 去除首尾空格 text = text.strip() # 将多个空格合并为一个 text = re.sub(r'\s+', ' ', text) # 修复标点前的空格 text = re.sub(r'\s+([,.!?;:])', r'\1', text) return text def _normalize_punctuation(self, text: str) -> str: """标点符号标准化""" # 英文标点转中文标点(如果目标语言是中文) if self.language == "zh": punctuation_map = { ',': ',', '.': '。', '!': '!', '?': '?', ';': ';', ':': ':', '(': '(', ')': ')', '"': '「', "'": '『' } for eng_punc, zh_punc in punctuation_map.items(): text = text.replace(eng_punc, zh_punc) # 确保标点后有空格(英文) if self.language == "en": text = re.sub(r'([,.!?;:])([A-Za-z])', r'\1 \2', text) return text def _normalize_numbers_chinese(self, text: str) -> str: """中文数字规范化""" def number_to_chinese(match): num_str = match.group(1) # 简单转换:将每个数字转为中文 if len(num_str) == 1: return self.number_map_zh.get(num_str, num_str) elif len(num_str) == 4 and 1000 <= int(num_str) <= 9999: # 年份处理:2023 -> 二零二三 return ''.join(self.number_map_zh.get(d, d) for d in num_str) else: # 保持数字形式 return num_str # 替换数字 text = re.sub(self.patterns["numbers"][self.language], number_to_chinese, text) return text def _normalize_numbers_english(self, text: str) -> str: """英文数字规范化""" def number_to_words(match): num = int(match.group(1)) # 简单的数字转单词(0-99) if 0 <= num <= 99: words_0_19 = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen' ] tens = [ '', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety' ] if num < 20: return words_0_19[num] else: ten = tens[num // 10] one = words_0_19[num % 10] if num % 10 != 0 else '' return f"{ten}-{one}" if one else ten else: # 保持数字形式 return str(num) # 替换数字 text = re.sub(self.patterns["numbers"][self.language], number_to_words, text) return text def _split_long_sentences(self, text: str, max_length=50) -> str: """分割过长的句子""" sentences = re.split(r'([。!?.!?])', text) processed_sentences = [] for i in range(0, len(sentences), 2): if i + 1 < len(sentences): sentence = sentences[i] + sentences[i + 1] else: sentence = sentences[i] if len(sentence) > max_length: # 根据逗号分割 parts = re.split(r'([,,])', sentence) reconstructed = [] for j in range(0, len(parts), 2): if j + 1 < len(parts): part = parts[j] + parts[j + 1] else: part = parts[j] reconstructed.append(part) sentence = ''.join(reconstructed) processed_sentences.append(sentence) return ''.join(processed_sentences) # 使用后处理 post_processor = TranscriptionPostProcessor(language="zh") transcribed_text = "今天天气很好 我们去公园玩吧123 " processed_text = post_processor.process_transcription(transcribed_text) print(f"处理后: {processed_text}") # 输出: "今天天气很好,我们去公园玩吧一二三"
八、总结
OpenAI Whisper 是初学者入门语音转文本(ASR)的最优选择,兼具极强实用性与学习价值。学习上,建议遵循 “基础优先、循序渐进” 路径:先掌握核心概念(如模型尺寸、字错率),跑通最简转录代码,再逐一生理解析 model_size、language 等关键参数含义,避免一开始陷入复杂功能。重点吃透硬件匹配模型尺寸、强制指定语言、固定 temperature=0.0等基础技巧,这是提升准确性的核心。
实用性方面,可直接套用场景化配置:CPU 用户优先用 base 模型处理通用短音频,GPU 用户可选 medium 模型提升长音频/专业场景精度;噪声/口音场景调大 beam_size 与 best_of,专业领域可加初始提示词引导术语识别。我们无需追求极致参数,先实现稳定转写,再逐步扩展批量处理、说话人分离等功能,既能快速落地使用,也能系统掌握 ASR 核心逻辑。