一、前言
前面我们详细介绍了文本转语音的细节和实践,今天我们继续探讨一下语音转文本(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. 示例代码
python
# 第一步:导入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。
整体参数示例:
python
# 优化中文转写准确性的基础配置
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输出示例:
python
# 在上方代码的函数中添加以下代码,打印段落时间戳
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:通用安静短音频(如语音消息、录音笔记)
python
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:噪声 / 口音重音频(如户外录音、方言口音)
python
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 分钟)
python
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,获取每个单词的精准时间戳(如制作字幕):
python
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
输出示例:
python
[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. 批量转录
处理多个音频文件,我们可以封装批量处理函数,避免重复操作,增加基础异常处理,防止单个文件报错中断:
python
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实现"说话人 + 文本 + 时间戳"的转写
python
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)
输出示例:
python
[0.0秒 - 5.0秒] SPEAKER_00: 今天我们讨论一下项目进度。
[5.0秒 - 10.0秒] SPEAKER_01: 我这边的模块已经完成了80%。
六、辅助优化
1. 音频预处理(降噪)
进一步提升转写的准确性,可以对噪声音频先降噪,再进行转写操作,需要应用安装noisereduce库。
python
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. 简单后处理(校正错字)
python
result_text = result["text"]
# 替换常见错字
corrected_text = result_text.replace("北惊", "北京").replace("百份之", "百分之")
print("校正后文本:", corrected_text)
七、应用示例
1. 完整的应用实例
包括单文件转录、批量转录以及带说话人分离的转录
python
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. 转录后处理
处理转录后的内容,包括标点恢复、数字规范化等,可根据实际情况调整
python
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 核心逻辑。