likes
comments
collection
share

玩转OpenAI-Whisper:语音识别一站式指南

作者站长头像
站长
· 阅读数 41

各种支持多语言语音识别的模型通常都是在 openai-whisper 基础上再开发的,例如更快、更精确的faster-whisper。

然而,这类高级模型往往要求更高的安装复杂性和硬件支持。 就平常使用而言,openai-whisper 的功能已经够用。虽然默认情况下,openai-whisper 的表现可能并不如人意,例如,它的内部音频片段固定为30秒,无法自外部调整;无法按字符制定时间戳;无法准确区分简体和繁体中文,中文语音在大多数情况下会被转化为繁体文字等等。

不过,大部分问题可以通过调整参数来解决。

本文将简要介绍相关参数。

本文主要内容有

  1. 环境准备(python ffmpeg)
  2. 简单来个语音识别
  3. 优化识别结果
  4. 逐字高亮字幕
  5. 完整代码
  6. 附:下载ffmpeg并设置环境变量

环境准备

AI模型使用,对环境有一定要求,当然要先满足咯,首先设备上必须已安装了python环境,建议使用 python3.10版本。如果还没有安装,请参考下方教程安装。

假设在D盘下有个文件夹 test,后续操作都将在此文件夹下。D:/test

1. 创建一个虚拟环境

这不是必须的,但建议如此,以避免和其他模块互相干扰。

打开 D:/test 文件夹, 地址栏输入 cmd 后回车,在打开的cmd窗口输入命令 python -m venv venv,按回车等待执行完毕。

玩转OpenAI-Whisper:语音识别一站式指南

2. 激活该环境

继续在cmd窗口中输入 .\venv\scripts\activate 激活环境,执行下 python --version,看看是不是显示 python 3.10 等版本信息。

玩转OpenAI-Whisper:语音识别一站式指南

3. 安装 openai-whisper 模块

cmd窗口中继续输入命令 pip install -U openai-whisper 回车, 等待执行完毕。

小提示:如果安装非常慢,建议设置pip镜像为 阿里云镜像, 在任意文件夹的地址栏中输入 %APPDATA%\pip\,然后回车,你应该能看到一个 pip.ini 文件,如果不存在,就新建,注意后缀是 .ini而不是.txt,然后用记事本打开,将里面内容替换为

[global]
index-url = https://mirrors.aliyun.com/pypi/simple/

[install]
trusted-host = mirrors.aliyun.com

简单来个语音识别

1. 加载模型

D:/test 文件夹下创建一个 start.py 文件,输入以下代码

import whisper

model = whisper.load_model("small")

玩转OpenAI-Whisper:语音识别一站式指南

如上图,openai 提供了从 tiny 到 large 不同尺寸的模型,尺寸越大识别精度越高,对于中文任务,建议至少使用 small 模型。

model = whisper.load_model("small") 代码中 small 即为模型名字,填写后将自动下载,当然如嫌慢,也可手动下载,下载地址如下。

不过此时代码需要修改下,以便能从你下载后pt文件保存的地址加载模型。 假设你将下载后的 small.pt模型放在了 D:/test/models/small.pt。代码应修改为

model = whisper.load_model("small",download_root="./models")

# 模型下载地址
{
    "tiny.en": "https://openaipublic.azureedge.net/main/whisper/models/d3dd57d32accea0b295c96e26691aa14d8822fac7d9d27d5dc00b4ca2826dd03/tiny.en.pt",
    "tiny": "https://openaipublic.azureedge.net/main/whisper/models/65147644a518d12f04e32d6f3b26facc3f8dd46e5390956a9424a650c0ce22b9/tiny.pt",
    "base.en": "https://openaipublic.azureedge.net/main/whisper/models/25a8566e1d0c1e2231d1c762132cd20e0f96a85d16145c3a00adf5d1ac670ead/base.en.pt",
    "base": "https://openaipublic.azureedge.net/main/whisper/models/ed3a0b6b1c0edf879ad9b11b1af5a0e6ab5db9205f891f668f8b0e6c6326e34e/base.pt",
    "small.en": "https://openaipublic.azureedge.net/main/whisper/models/f953ad0fd29cacd07d5a9eda5624af0f6bcf2258be67c92b79389873d91e0872/small.en.pt",
    "small": "https://openaipublic.azureedge.net/main/whisper/models/9ecf779972d90ba49c06d968637d720dd632c55bbf19d441fb42bf17a411e794/small.pt",
    "medium.en": "https://openaipublic.azureedge.net/main/whisper/models/d7440d1dc186f76616474e0ff0b3b6b879abc9d1a4926b7adfa41db2d497ab4f/medium.en.pt",
    "medium": "https://openaipublic.azureedge.net/main/whisper/models/345ae4da62f9b3d59415adc60127b97c714f32e89e936602e85993674d08dcb1/medium.pt",
    "large-v1": "https://openaipublic.azureedge.net/main/whisper/models/e4b87e7e0bf463eb8e6956e646f1e277e901512310def2c24bf0e11bd3c28e9a/large-v1.pt",
    "large-v2": "https://openaipublic.azureedge.net/main/whisper/models/81f7c96c852ee8fc832187b0132e569d6c3065a3252ed18e56effd0b6a73e524/large-v2.pt",
    "large-v3": "https://openaipublic.azureedge.net/main/whisper/models/e5b1a55b89c1367dacf97e3e19bfd829a01529dbfdeefa8caeb59b3f1b81dadb/large-v3.pt",
    "large": "https://openaipublic.azureedge.net/main/whisper/models/e5b1a55b89c1367dacf97e3e19bfd829a01529dbfdeefa8caeb59b3f1b81dadb/large-v3.pt",
}

加载wav音频文件开始识别

准备一个wav格式的音频,测试时建议不超过3分钟,以避免耗时太久。例如 300.wav 是一个时长300的音频

start.py 中继续输入下方代码

result = model.transcribe("./300.wav",language="zh")

这一行代码即可开始识别了。language是说话语音代码,中文是 “zh”,openai-whisper支持数十种语言的识别。

如何查看识别结果文字呢,输入以下代码

for segment in result['segments']:
    print(f'start:{segment["start"]},end:{segment["end"]},{segment["text"]}')


cmd窗口中执行 python start.py 将得到以下结果(执行方法下同,不再赘述)

识别结果

start:0.0,end:4.56,在古老星系中發現了有幾分子
start:4.56,end:6.96,我們離第三類接觸還有多遠
start:6.96,end:9.84,微博正式展開拍攝任務已經見滿周年
start:9.84,end:13.16,最近也傳過來許多過去難以拍攝到的照片
start:13.16,end:16.0,6月初天文學家在自然期刊上
start:16.0,end:17.84,發表了這張照片
start:17.84,end:19.2,在藍色核心外
start:19.2,end:21.64,環繞著一圈橘黃色的光芒
start:21.64,end:24.16,這是一個星系規模的甜甜圈
start:24.16,end:26.12,這是一個傳送門
start:26.12,end:28.36,這是外星文明的帶生環
start:28.36,end:30.12,其實這是一個很有趣的

可以看到,问题有2个,一是时间戳前后是连续的,没有区分静音间隔,虽然音频中本身每句话后面都是有停顿的;二是识别文字是繁体,本意希望它是简体。

优化识别结果

1. 简繁正确处理:先解决简繁问题,这可以通过简单的设置一个 prompt 来处理,只需要在 transcribe 方法中添加一个参数 initial_prompt ,修改代码如下

result = model.transcribe("./300.wav",language="zh",initial_prompt="请将结果转录为简体中文")

重新执行,结果如下


start:0.0,end:13.08,在骨老星系中发现了有机分子,我们离第三类接触还有多远。微博正式展开拍摄任务,已经见满周年,最近也传过来许多过去难以拍摄到的照片。
start:13.08,end:21.6,6月初,天文学家在自然期刊上发表了这张照片,在蓝色核心外还绕着一圈橘黄色的光芒。
start:21.6,end:28.0,这是一个新系规模的甜天圈,这是一个传送门,这是外星文明的代生环。
start:28.0,end:30.0,其实,这是一个很有境。

已经正确转录为了简体中文,然而断句效果很糟糕,时间开始发声并不是从 0秒 开始,语句之间也有静音间隔,不应该时间戳前后相连。

添加 initial_prompt 对断句也有不利影响,从上面2个不同结果也能看出,虽然按照 prompt 将结果转录为中文了,但可能导致过长的语句,比如第一句13.08秒。

2. 如何精确确定开始发声时间,并区分语句之间的静音片段呢?。

transcribe 方法中有个参数 word_timestamps,默认是 False,改为 True 试试,修改代码

model.transcribe("./300.wav",language="zh",initial_prompt="请将结果转录为简体中文",word_timestamps=True)


for segment in result['segments']:
    print(segment)

打印发现,每个 segment 中多了一个 words 列表,该列表中每个元素都是一个字符的字典对象,标记该字符起始时间戳。

'words': [
{'word': '在', 'start': 1.4800000000000006, 'end': 1.84, 'probability': 0.8597160577774048}
...

如上 “在” 这个文字 1.48s 时开始发声,1.84s 时结束。据此修改代码


for segment in result['segments']:
    print(f'start:{segment["words"][0]["start"]},end:{segment["words"][-1]["end"]},{segment["text"]}')
        

重新执行识别结果

start:1.48,end:12.78,在骨老星系中发现了有机分子,我们离第三类接触还有多远。微博正式展开拍摄任务,已经见满周年,最近也传过来许多过去难以
拍摄到的照片。
start:13.3,end:21.28,6月初,天文学家在自然期刊上发表了这张照片,在蓝色核心外还绕着一圈橘黄色的光芒。
start:22.05,end:27.94,这是一个新系规模的甜天圈,这是一个传送门,这是外星文明的代生环。
start:28.32,end:29.98,其实,这是一个很有趣。

可以看到开始发声时刻不再是0,而是实际的 1.48 秒,语句之间时间戳也不再前后相连,而是空出来了静音区间。

到此解决了静音区间和精确发声时刻问题。接下来处理断句问题。

3. 更精确的断句处理: 从之前的结果可以看到,prompt 不止对结果有正向作用,还可能有反向功能,比如断句变的糟糕。当然可以通过修改不同的 prompt 来修正。例如

result = model.transcribe("./30.wav",language="zh",word_timestamps=True,initial_prompt="生当作人杰,死亦为鬼雄。")

结果

for segment in result["segments"]:
    print(f'start:{segment["start"]},end:{segment["end"]},{segment["text"]}')
start:1.4800000000000006,end:12.78,在古老星系中发现了有机分子,我们离第三类接触还有多远。微博正式展开拍摄任务,已经见满周年,最近也传过来许多过去难以拍摄到的照片。
start:13.3,end:21.28,6月初,天文学家在自然期刊上发表了这张照片,在蓝色核心外还绕着一圈橘黄色的光芒。
start:21.66,end:25.38,这是一个星系规模的甜天圈,这是一个传送门。
start:25.919999999999998,end:29.98,这是外星文明的代生环,其实这是一个很有趣。

然而仅调整 initial_prompt 效果仍然不太理想,也不太稳定,毕竟prompt只是给模型一个提示,模型不一定完全遵守。

有没有更好的断句方法,当然不是指VAD,那个相对较为复杂了,暂不考虑。

openai-whisper 源码的 cli 方法体里,发现了这几个参数

max_line_width:断开行之前,行中的最大字符数
max_line_count:一段中的最大行数
max_words_per_line:每行中的最大单词数

这几个参数要生效,前提是必须将 word_timestamps 设为True,其中 max_line_widthmax_words_per_line 互斥,也即,设置了 max_line_widthmax_words_per_line 将无效。

对于中文语音识别来说,只需要关注前2个参数,max_line_widthmax_line_count

从前边结果我们看到,第一行字幕用了13秒,字符个数达到63个,如果在视频中作为字幕显示,将至少占据半个屏幕高度,这显然不可接受。

设置下 max_line_width=24,意思为一行最长不得超过24个字符,同时将 max_line_count=2,即一条字幕最多包含2行文字。

这里获取结果的写法需要修改下,因为这3个参数是用于cli模式的,不可直接传入 transcribe 中,start.py中将代码修改如下:

import whisper
from whisper.utils import get_writer

result = model.transcribe("./30.wav",language="zh",word_timestamps=True,initial_prompt="请转录为简体中文。")

srt_writer = get_writer("srt", "./")

srt_writer(result, "30.wav", {"max_line_count":2,"max_line_width":24})

srt_writer = get_writer("srt", "./") 代码涵义是将在当前目录 ./ 下创建一个srt字幕文件

srt_writer(result, "30.wav", {"max_line_count":2,"max_line_width":24})

字幕文件名字将由 30.wav 来确定,即 30.srt,第三个参数是是关键,用来指定字幕断句,每条字幕最多2行文字,每行文字最多24个字符。

结果srt内容如下

1
00:00:01,440 --> 00:00:10,560
在骨老星系中发现了有机分子。我们离第三类接触还有
多远。微博正式展开拍摄任务已经见满周年,最近也传

2
00:00:10,560 --> 00:00:20,080
过来许多过去难以拍摄到的照片。6月初天文学家在自
然期刊上发表了这张照片。在蓝色核心外,还绕着一圈

3
00:00:20,080 --> 00:00:29,660
橘黄色的光芒。这是一个星系规模的甜天圈。这是一个
传送门。这是外星文明的代生环。其实这是一个很有

4
00:00:29,660 --> 00:00:29,980
机。


可以看到,效果符合预期,但两行还是太多,继续调整 max_line_count 为1

srt_writer(result, "30.wav", {"max_line_count":1,"max_line_width":24})

结果

1
00:00:01,440 --> 00:00:06,080
在骨老星系中发现了有机分子。我们离第三类接触还有

2
00:00:06,080 --> 00:00:10,560
多远。微博正式展开拍摄任务已经见满周年,最近也传

3
00:00:10,560 --> 00:00:15,120
过来许多过去难以拍摄到的照片。6月初天文学家在自

4
00:00:15,120 --> 00:00:20,080
然期刊上发表了这张照片。在蓝色核心外,还绕着一圈

5
00:00:20,080 --> 00:00:24,740
橘黄色的光芒。这是一个星系规模的甜天圈。这是一个

6
00:00:24,740 --> 00:00:29,660
传送门。这是外星文明的代生环。其实这是一个很有

7
00:00:29,660 --> 00:00:29,980
机。


到此解决了 “简繁”、“精确发声起始时刻”、“静音片段区分”、“断句过长” 这几个问题,涉及到的参数有 word_timestamps/initial_prompt/max_line_count/max_line_width/max_words_per_line

逐字高亮字幕能实现吗

有些比较高级的字幕效果,比如发声到哪个字,哪个字就高亮显示,使用 openai-whisper 可以很容易实现,仅需要添加一个参数

srt_writer(result, "30.wav", {"max_line_count":2,"max_line_width":24,"highlight_words":True})

完整代码

import whisper
from whisper.utils import get_writer
model = whisper.load_model("small")
result = model.transcribe("./30.wav",
                          language="zh",
                          word_timestamps=True,
                          initial_prompt="请转录为简体中文。")
srt_writer = get_writer("srt", "./")
srt_writer(result,  "30.wav",{"max_line_count":1,"max_line_width":24,'highlight_words':True})

print(open('./30.srt','r',encoding='utf-8').read())

是否还有提升空间

示例中均是使用small模型,改用更大模型,比如 large,效果自然会进一步提升。在模型不变的情况下也可以transcribe方法中尝试调整更多参数,比如

temperature
logprob_threshold
no_speech_threshold
condition_on_previous_text

具体可查看源文件 venv/Lib/site-packages/whisper/transcribe.py

附1:下载ffmpeg 并设置环境变量

ffmpeg windows版本下载地址 www.gyan.dev/ffmpeg/buil…

下载后解压到某个路径下,然后进入bin目录,你会看到3个exe文件。将他们添加到 Path 环境变量中。

添加方法:按住 Win键+Pause键(右上角), 打开的窗口中依次点击--高级系统设置--环境变量--系统变量--找到 Path这行双击

玩转OpenAI-Whisper:语音识别一站式指南

再依次点击 新建--浏览,找到bin目录 确定。

玩转OpenAI-Whisper:语音识别一站式指南

玩转OpenAI-Whisper:语音识别一站式指南

附2:openai-whisper 开源地址 github.com/openai/whis…

转载自:https://juejin.cn/post/7383957603016998927
评论
请登录