
前面我们详细介绍了文本转语音的细节和实践,今天我们继续探讨一下语音转文本(ASR),初次接触,OpenAI Whisper 是最易上手、效果最均衡的开源大模型,它无需复杂的专业知识,一行代码就能实现多语言语音转写,且在噪声、口音、多语言场景下的表现远优于传统 ASR。
今天我们从基础概念入手,逐行拆解代码、详解核心参数,结合实际场景选择参数提升转录准确性,覆盖从零基础运行到精准适配场景的全流程,所有内容优先讲解基础点,确保我们都能理解、能举一反三的可用复用。

ASR,全称Automatic Speech Recognition,即自动语音识别,核心是把人类说话的音频信号转换成文字。日常用的微信语音转文字、会议纪要自动生成,本质都是 ASR 技术。
核心评价指标:字错率(WER),简单理解为 “转错的字数/总字数”,数值越低,转录越准确(比如 WER=5%,代表 100 个字里错 5 个)。
Whisper 是 OpenAI 开源的 ASR 大模型,新手只需记住 3 个核心特点:
Whisper 提供 5 种预训练模型尺寸,新手可简单理解为 “模型越大,越准但越慢、占内存越多”,各尺寸的基础属性如下(新手重点看 “适用场景”):
维度 | 传统 ASR(如 MFCC+HMM) | Whisper 大模型 |
|---|---|---|
语言支持 | 需单独训练单语言模型 | 原生支持 99 种语言,无需额外适配 |
噪声鲁棒性 | 噪声场景 WER 飙升至 30%+ | 80dB 噪声下 WER 仍 < 15% |
特征提取 | 手动设计声学特征(MFCC) | 端到端自学习特征,适配复杂音频 |
部署成本 | 需部署特征提取、解码等多模块 | 单模型即可完成从音频到文本的转换 |
转录音频内容,支持 WAV/MP3/M4A 等格式,实现音频内容解析输出
# 第一步:导入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']}")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.1. 逐行解析:
3.2. 常见问题:
提升转写准确性的第一步,必须要搞懂“模型加载”和“转录方法”的核心参数,参数选择的合理,准确性可直接大幅度提升。
模型加载方式 whisper.load_model(),仅 1 个核心参数
model.transcribe() 是实现转写的核心方法,重点先了解以下 6 个基础参数(按重要性排序):
4.2.1 audio 参数:
4.2.2 language 参数:
4.2.3 task 参数:
4.2.4 temperature 参数:
4.2.5 beam_size 参数:
4.2.6 best_of 参数:
整体参数示例:
# 优化中文转写准确性的基础配置
result = model.transcribe(
audio_path,
language="zh", # 强制指定中文,避免误判
temperature=0.0, # 固定结果,无随机错误
beam_size=8, # 增加候选数量,提升准确性
best_of=6 # 配合beam_size,选最优结果
)transcribe返回的result是一个字典,我们可以先从以下 3 个基础字段入手:
4.3.1 text 字段:
4.3.2 language 字段:
4.3.3 segments 字段:
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秒]: 请在这里输入想要朗读的文本。
流程细节说明:
这个流程提供了从音频到文本的完整转录路径,每个步骤都是构建高质量语音识别系统的重要组成部分。
提升转写准确性,无需复杂操作,按“先选模型→再调基础参数→最后适配场景”的步骤即可,每一步都基于基础操作。
模型尺寸是准确性的基础,尺寸越大,准度越高,我们需要按硬件选择即可,不同硬件对应的模型选择、准确性和转写速度如下:
选好模型后,先改 2 个最易上手的参数,可直接提升准确性:
若基础参数调整后仍需提升准确性,再调以下 2 个参数(平衡准度和速度),不同场景的参数取值和基础效果如下:
不同场景的参数最优组合不同,我们可以按以下配置调整后观察输出效果,持续的微调优化:
场景 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实现精细化转写,我们可以开启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秒处理多个音频文件,我们可以封装批量处理函数,避免重复操作,增加基础异常处理,防止单个文件报错中断:
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)识别“谁在什么时候说什么”,结合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%。进一步提升转写的准确性,可以对噪声音频先降噪,再进行转写操作,需要应用安装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")result_text = result["text"]
# 替换常见错字
corrected_text = result_text.replace("北惊", "北京").replace("百份之", "百分之")
print("校正后文本:", corrected_text)包括单文件转录、批量转录以及带说话人分离的转录
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 } ] } ] }
处理转录后的内容,包括标点恢复、数字规范化等,可根据实际情况调整
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 核心逻辑。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。