强制对齐音频与转录文本 - MFA的安装及使用
MFA是基于Kaldi的强制对齐工具。它可以将音频信号中的每个声音片段与相应的文本对齐,以确定每个词或音素的开始和结束时间。
其中,Kaldi是目前最流行的开源自动语音识别Automatic speech recognition (ASR)工具箱,包含了几乎所有目前主流ASR系统用到的算法,代码使用C++编写。具体的介绍可参考Kaldi官网,也可以参考Kaldi Tutorial文档。
本文想要实现的功能是,将一段音频与音频对应的文本在单词级别进行对齐,并进行可视化。
MFA安装
安装环境: Mac OS, M1芯片。
安装命令:
$ conda create -n aligner -c conda-forge montreal-forced-aligner
执行上面命令后,查看即将安装的package列表,如果存在Kaldi,因此无需再单独安装。
如果命令执行成功,会创建一个名为aligner的conda环境,后续的对齐操作要在这个环境中进行。
更新MFA:
执行下面命令,可将MFA更新到最新版本。
$ conda update -c conda-forge montreal-forced-aligner kalpy kaldi=*=cpu* --update-deps
激活环境:
$ conda activate aligner
下载模型:
在网页中找到需要的模型及字典后,可以在Terminal使用命令行下载:
# 下载中文模型
$ mfa models download acoustic mandarin_mfa
# 下载中文字典
$ mfa model download dictionary mandarin_china_mfa
其他依赖包:
由于使用MFA对齐需要其他一些依赖包,比如spacy,用来进行tokenization。
执行下面命令进行安装:
# 安装
$ conda install spacy
# 安装英文语言模型
$ python -m spacy download en_core_web_sm
# 安装中文语言模型
$ python -m spacy download en_core_web_sm
# 中文 tokenization
$ pip install spacy-pkuseg dragonmapper hanziconv
使用MFA对齐语音及文本
执行对齐命令:
$ mfa align ./data mandarin_china_mfa mandarin_mfa ./data/result
./data路径下文件结构如下:
-data
-speaker1 # 第一个音频及文本所在的目录
-speaker1.wav # 第一个音频
-speaker1.txt # 第一个音频的转录文本
-speaker2 # 第二个音频及文本所在的目录
-speaker2.wav
-speaker2.txt
-result # 存放生成的TextGrid文件,对齐命令执行后生成下面文件
-speaker1
-speaker1.TextGrid
-speaker2
-speaker2.TextGrid
上述命令会将./data目录下的音频文件和文本文件中的内容对齐,生成.TextGrid文件。
.TextGrid文件是Praat软件用来存储音频标注的文件格式,这些标注通常用于表示音素、单词、短语或其他语音成分的时间边界。
比如生成的.TextGrid会包含如下内容:
1. 总时间范围:
xmin = 0
xmax = 3.584
这表示整个TextGrid文件覆盖从0秒到3.584秒的时间范围。
2. 第一个IntervalTier (“words”):
class = "IntervalTier"
name = "words" # 单词或短语
xmin = 0
xmax = 3.584
intervals: size = 6:这个层级包含6个时间间隔。
每个间隔有一个开始时间(xmin),结束时间(xmax),以及标注文本(text)。
3. 第二个IntervalTier (“phones”):
class = "IntervalTier" #音素
name = "phones"
xmin = 0
xmax = 3.584
intervals: size = 16:这个层级包含16个时间间隔。
每个间隔有一个开始时间(xmin),结束时间(xmax),以及标注文本(text)。
对齐结果可视化
下面是将其中一个.TextGrid文件可视化,得到的每个单词或短语对应的时间-频率图:
代码如下:
import parselmouth
import matplotlib.pyplot as plt
import numpy as np
import tgt
from matplotlib.font_manager import FontProperties
import pandas as pd
# 设置中文字体
font = FontProperties(fname='/System/Library/Fonts/STHeiti Medium.ttc')
# 设置支持IPA字符的字体
ipa_font = FontProperties(fname='/Users/xxx/Library/Fonts/DejaVuSans.ttf') # 确保路径正确
def remove_outliers_and_zeroes(pitch_values, threshold=3):
# 将音高值为0的点设置为NaN
pitch_values = np.where(pitch_values != 0, pitch_values, np.nan)
# 计算音高值的均值和标准差
mean = np.nanmean(pitch_values)
std = np.nanstd(pitch_values)
# 计算Z-score
z_scores = (pitch_values - mean) / std
# 将超过阈值的音高值设置为NaN
pitch_values = np.where(np.abs(z_scores) < threshold, pitch_values, np.nan)
return pitch_values
def plot_alignment(audio_path, textgrid_path, title):
# 读取音频文件
sound = parselmouth.Sound(audio_path)
# 读取TextGrid文件
tg = tgt.io.read_textgrid(textgrid_path)
# 提取音高信息
pitch = sound.to_pitch()
pitch_values = pitch.selected_array['frequency']
pitch_times = pitch.xs()
# 去除离群值和音高值为0的点
pitch_values = remove_outliers_and_zeroes(pitch_values)
# 找到名为'words'和'phones'的tier
words_tier = tg.get_tier_by_name('words')
phones_tier = tg.get_tier_by_name('phones')
# 绘制音高轮廓
plt.figure(figsize=(14, 8))
plt.plot(pitch_times, pitch_values, '-', markersize=5, color='b', label="Pitch")
# 从TextGrid中提取单词间隔并绘制
for interval in words_tier.intervals:
plt.axvline(x=interval.start_time, color='k', linestyle='--')
plt.text((interval.start_time + interval.end_time) / 2, np.nanmax(pitch_values) * 0.9, interval.text,
horizontalalignment='center', verticalalignment='center', color='red', fontsize=12, fontproperties=font)
# 从TextGrid中提取音素间隔并绘制
for interval in phones_tier.intervals:
plt.axvline(x=interval.start_time, color='gray', linestyle=':')
plt.text((interval.start_time + interval.end_time) / 2, np.nanmax(pitch_values) * 0.8, interval.text,
horizontalalignment='center', verticalalignment='center', color='green', fontsize=8, fontproperties=ipa_font)
plt.grid(True)
plt.title(title, fontproperties=font)
plt.xlabel("Time (s)", fontproperties=font)
plt.ylabel("Pitch (Hz)", fontproperties=font)
plt.legend(prop=font)
plt.show()
plot_alignment("data/speaker1/speaker1.wav", "data/result/speaker1/speaker1.TextGrid", "Speaker 1 Alignment")