抛弃Praat脚本?—— 使用 Parselmouth 实现语音数据处理

使用 Parselmouth 配合 Praat 实现语音数据处理

引言

在语音信号处理领域,Praat 是一个功能强大的工具,广泛用于语音录制、分析、标注和可视化。然而,Praat 的脚本语言虽然灵活,但对于习惯使用 Python 的开发者来说,可能不够直观和高效。能够使用Praat脚本处理数据的同学应该有此感悟,在处理的过程中,包括对文件,文本,字符串的一些操作,显然有很大的局限性。尤其如果对于一个习惯了使用Python等编程语言的小伙伴们,假定现在告诉你,有某某操作必须要用到这个Praat脚本,这个调研,调用的过程中无疑是痛苦的!

那么问题来了! 我们能够抛弃Praat脚本去处理语音数据吗? 当然,很多站队Praat的同学,要有此怀疑:最好还要处理的数据是我用Praat一样一样的!相信很多人尝试用librosa, pyworld, 之类的语音包,在一些数据的细节上,还是有很大的差异的。 请注意,刚才提到的一些语音包没什么问题,但是如果说 要保证和Praat提取的参数一模一样?! 这可能是有一些难度的,因为语音采样点是有一些随机性的,不同的方案去计算如共振峰,基频,肯定是有所差异的。当然,严格意义上来说,有一些计算的差异,实际上是没有太大影响的。

那么,本文要介绍的Parselmouth 是一个 Python 库,它提供了对 Praat 功能的直接访问,使得在 Python 环境中处理语音数据变得更加方便和强大。是的!你没有听错!它是直接去访问Praat的,在它的编译包里,是有Praat完整源代码的。该库包在github上的访问量已经有1.1K,考虑到Praat比较小众的保用人群,说明该项目还是比较受关注的。

本篇推荐Parselmouth另外一个重点是,Praat 的脚本语言虽然强大,但在处理复杂任务或与其他工具集成时可能显得不够灵活。Parselmouth 是一个 Python 库,它提供了对 Praat 功能的直接访问,使得用户可以在 Python 环境中使用 Praat 的强大功能,同时利用 Python 的灵活性和丰富的生态系统。

本文将介绍如何使用 Parselmouth 处理语音数据,并展示其比 Praat 脚本更方便、功能更强大的地方。我们将重点讨论声音读取、写入、分段、声音参数(如采样率)展示,以及提取基频、共振峰、音强、时长、MFCC 等主要参数。

警告!!!:学习使用本篇,必然要有一定的Python使用基础,最起码要知道Python程序如何使用。

1. Parselmouth 简介

在这里插入图片描述

Parselmouth 是一个 Python 库,它将 Praat 的功能封装为 Python 接口。通过 Parselmouth,你可以在 Python 中直接调用 Praat 的函数,而无需编写 Praat 脚本。Parselmouth 的优势包括:

  • 易用性:Python 语法简洁直观,适合快速开发和调试。Python 语法比 Praat 脚本更易读和易写。
  • Python 生态系统的支持:结合 Python 生态系统的强大功能(如 NumPy、SciPy、Matplotlib 等),可以轻松实现复杂的数据处理和分析。
  • 高效性:直接在 Python 中调用 Praat 函数,避免了频繁切换工具。
  • 灵活性:可以轻松集成到现有的 Python 项目中。

2. 项目地址:

github地址:https://github.com/YannickJadoul/Parselmouth
文档地址:https://parselmouth.readthedocs.io/en/stable/

3. 安装 Parselmouth

首先,需要安装 Parselmouth 库。可以通过 pip 安装,包只有4-5M左右,很小:

pip install praat-parselmouth

后续会推出一些Python的系列学习课程,敬请期待。如上安装可能是一种比较“无序”的工作方式,希望自己工作流更清晰的同学,可自行了解一些安装conda管理虚拟环境,以及配置一个专门处理praat数据的虚拟环境,再使用上面的安装,这样做到任何绑定环境,互不干涉。

4. 基本用法

4.1 加载音频文件

使用 Parselmouth 加载音频文件非常简单,如下所示,音频文件读取到sound对象之后,包括了音频基本的一些属性,采样率,通道数,时长,等;在写音频文件时,可以指定多种格式,与Praat保存音频时的选项同步;

# 加载音频文件
sound = parselmouth.Sound(r'input_data\000001.wav')
# 获取音频信息
print("采样率:", sound.sampling_frequency)
print("时长:", sound.duration)
print("声道数:", sound.number_of_channels)
# 输出:
# 采样率: 48000.0
# 时长: 2.66
# 声道数: 1

# 保存为新的WAV文件
output_path = r"output_data\output_000001.wav"
sound.save(output_path, parselmouth.SoundFileFormat.WAV)  # WAV格式

4.2 提取音频特征

Parselmouth 提供了许多 Praat 中的功能,例如提取音频的基频(pitch)、强度(intensity)、共振峰(Formant)、梅尔谱(MFCC)等。下面的例子演示如何提取基频。

# 加载音频文件
sound = parselmouth.Sound(r'input_data\000001.wav')
# 提取基频(Pitch)
pitch = sound.to_pitch()

start_time = 0.4
end_time = 0.5

cur_pitch = parselmouth.praat.call(pitch, "Get value at time", 0.45, "Hertz", "linear")
min_pitch = parselmouth.praat.call(pitch, "Get minimum", start_time, end_time, "Hertz", "Parabolic")
max_pitch = parselmouth.praat.call(pitch, "Get maximum", start_time, end_time, "Hertz", "Parabolic")
mean_pitch = parselmouth.praat.call(pitch, "Get mean", start_time, end_time, "Hertz")
std_pitch = parselmouth.praat.call(pitch, "Get standard deviation", start_time, end_time, "Hertz")
print('基频:' + str(cur_pitch))
print('最小基频:' + str(min_pitch))
print('最大基频:' + str(max_pitch))
print('平均基频:' + str(mean_pitch))
print('基频标准差:' + str(std_pitch))
# 输出:
# 基频:264.3713282356899
# 最小基频:261.291939898309
# 最大基频:287.63474257443977
# 平均基频:269.15842989505103
# 基频标准差:8.733570821295842

pitch_values = pitch.selected_array['frequency']
print(pitch_values)

说明:加载了音频之后,通过sound.to_pitch() 这句命令实现将声音转换为pitch对象。这之后的操作,比如提取某一点的基频,提取最大,最小,平均等,细心的你应该会发现,这,真的和使用Praat脚本来操作是完全类似的!

我们来对比一下使用Praat脚本提取以上几个基频相关的数值的比较,当我们采用raw autocorrelation方法提取基频时,细心的你再次发现,使用Parselmouth提取的数值和Praat脚本几乎是一样的(可能不排除有小数精度的区别)。

Read from file: "input_data\000001.wav"
To Pitch (raw autocorrelation): 0, 75, 600, 15, "no", 0.03, 0.45, 0.01, 0.35, 0.14
Get value at time: 0.45, "Hertz", "linear"
# 264.3713282356899 Hz
Get minimum: 0.4, 0.5, "Hertz", "parabolic"
# 261.291939898309 Hz
Get maximum: 0.4, 0.5, "Hertz", "parabolic"
# 287.63474257443977 Hz
Get mean: 0.4, 0.5, "Hertz"
# 269.158429895051 Hz

至此,你应该完全确信,Parselmouth这个库,是完全调用的Praat的核心的计算函数的。至于提取音强,共振峰等其它参数,我们不再赘述。在本篇稍后公布的git仓库里,会陆续投放相应的程序代码。

补充说明一下,上面的Python代码里的这句,pitch_values = pitch.selected_array['frequency'], 相当于将pitch提取为一个独立的数组,可以直接保存为文件,或者变量在工程上去大规模处理或者训练模型。

4.3 可视化

Parselmouth 还支持将 Praat 的可视化功能集成到 Python 中:

import matplotlib.pyplot as plt
import numpy as np
import parselmouth

# 加载音频文件
sound = parselmouth.Sound(r'input_data\000001.wav')
# 提取基频(Pitch)
pitch = sound.to_pitch()
pitch_values = pitch.selected_array['frequency']

# 绘制波形图
plt.figure()
plt.plot(sound.xs(), sound.values.T)
plt.title("Waveform")
plt.xlabel("Time [s]")
plt.ylabel("Amplitude")
plt.show()

# 绘制基频图
plt.figure()
plt.plot(pitch.xs(), pitch_values, 'o', markersize=2)
plt.title("Pitch")
plt.xlabel("Time [s]")
plt.ylabel("Pitch [Hz]")
plt.show()

以上两段代码的作图结果为,具体的作图细节,以及如何使用Python里的matplotlib这个丰富的作图库,在此不展开。

在这里插入图片描述

在这里插入图片描述

5. 高级用法

5.1 批量处理文件

熟练使用Python的伙伴们,对于Python在批量处理文件的便利毋须多言。通过与Parselmouth的结合, 可以方便地批量处理多个音频文件,以下这段代码读取一个目录所有的音频,将它们的基频文件批量保存:

import os
import numpy as np
import parselmouth

# 定义音频文件目录
audio_dir = "input_data"

# 遍历目录中的音频文件
for filename in os.listdir(audio_dir):
    if filename.endswith(".wav"):
        filepath = os.path.join(audio_dir, filename)
        sound = parselmouth.Sound(filepath)

        # 进行一些处理,例如提取基频
        pitch = sound.to_pitch()
        pitch_values = pitch.selected_array['frequency']

        # 保存结果
        output_path = os.path.join("output", f"{filename}_pitch.txt")
        np.savetxt(output_path, pitch_values, fmt='%.9f')

5.2 标注文件TextGrid的批量处理

同样的道理,既然Parselmouth能方便的直接处理音频,也可以直接处理我们的标注文件,TextGrid,以下是一个例子,读取一个目录里所有TextGrid文件,提取第一层(音素层)的所有音素信息,以及时长:

import os
import parselmouth
import pandas as pd

input_data_dir = r"input_data"
output_result_file = r'result.csv'

results = []
for filename in os.listdir(input_data_dir):
    if filename.endswith(".TextGrid"):
        logger.info(filename)
        textgrid_path = os.path.join(input_data_dir, filename.replace(".wav", ".TextGrid"))
        textgrid = parselmouth.praat.call("Read from file", textgrid_path)
        num_intervals = parselmouth.praat.call(textgrid, "Get number of intervals", 1)
        
        # 遍历每个音素
        for interval in range(1, num_intervals + 1):
            phoneme = parselmouth.praat.call(textgrid, "Get label of interval", 1, interval).strip()
            start_time = parselmouth.praat.call(textgrid, "Get start point", 1, interval)
            end_time = parselmouth.praat.call(textgrid, "Get end point", 1, interval)
            duration = end_time - start_time
            
            results.append({
                "file_name": filename,
                "phoneme": phoneme,
                "duration": '{:.3f}'.format(duration)
            })

# 将结果保存到CSV文件
input_data = pd.DataFrame(results)
input_data.to_csv(output_result_file, index=False)

运行之后,我们得到以下结果:
在这里插入图片描述

很清楚,在实际处理TextGrid的过程中,并没有引入新的函数,或者自定义的一些操作,就是直接调用了Praat里使用的如Read from fileGet label of intervalGet start point等;

在这个脚本中,有意结合了pandas处理了结果,这也是Python在数据分析处理上的巨大优势。比如我们想对上面的操作加一个条件:如果我只想提取元音部分的音素信息,假如这个条件在Praat脚本上实现,在匹配元音的时候,有用过的伙伴应该记得,将音素与元音列表循环验证,这真的不是Praat脚本擅长的。现在在这个例子中,由于使用了pandas,只需要一句话,即可以完成这个操作。

补充后的脚本为:

import os
import parselmouth
import pandas as pd

input_data_dir = r"input_data"
output_result_file = r'result.csv'

vowels = ['a', 'e', 'ai', 'ei'] # 定义元音列表,可通过读文件实现

results = []
for filename in os.listdir(input_data_dir):
    if filename.endswith(".TextGrid"):
        logger.info(filename)
        textgrid_path = os.path.join(input_data_dir, filename.replace(".wav", ".TextGrid"))
        textgrid = parselmouth.praat.call("Read from file", textgrid_path)
        num_intervals = parselmouth.praat.call(textgrid, "Get number of intervals", 1)
        
        # 遍历每个音素
        for interval in range(1, num_intervals + 1):
            phoneme = parselmouth.praat.call(textgrid, "Get label of interval", 1, interval).strip()
            start_time = parselmouth.praat.call(textgrid, "Get start point", 1, interval)
            end_time = parselmouth.praat.call(textgrid, "Get end point", 1, interval)
            duration = end_time - start_time
            
            results.append({
                "file_name": filename,
                "phoneme": phoneme,
                "duration": '{:.3f}'.format(duration)
            })

# 将结果保存到CSV文件
input_data = pd.DataFrame(results)
input_data = input_data[input_data['phoneme'].str.replace(r'\d+$', '', regex=True).isin(vowels)] # 过滤元音,考虑到了声调
input_data.to_csv(output_result_file, index=False)

为了演示,只是定义了几个元音。运行之后,结果如下:
在这里插入图片描述

5.3 与 Praat 脚本集成

看到这里,相信对于我们计划结合praat去处理的工作都可以开展。但是如果我们考虑这样的情况,在前期Praat脚本已经积累了比较多的工作,包括可能比较复杂的脚本,我们用Python全部重新写一遍,这可能也会增加额外的工作量,是否在原来工作的基础上,由Parselmouth直接调用呢?答案是肯定的。

Parselmouth 允许直接调用 Praat 脚本,从而实现更复杂的功能。而且调用的方式也非常简单。我们以我们公众号之前的一个脚本为例Praat脚本-010 | 提取时长和共振峰。我们直接调用这个脚本。当然要注意到这个脚本有4个参数,分别是:输入音频目录,输入TextGrid目录,输入TextGrid要提取层级,输出的结果。那么我们在Parselmouth调用的时候,要传输这4个参数给Praat脚本。以下是程序片段示例:

# 定义参数
input_wav_dir = r"input_data/"  # WAV 文件目录(确保以 / 或 \ 结尾)
input_textgrid_dir = r"input_data/"  # TextGrid 文件目录
reference_tier = 1  # 目标 Tier 编号
output_file = "result_duration_formant.txt"  # 输出文件路径

# 调用 Praat 脚本
result = parselmouth.praat.run_file("Get_Duration_and_Formant.Praat", input_wav_dir, input_textgrid_dir, str(reference_tier), output_file)

说明:为了显示的知道运行的情况,我们在Praat脚本里加了一句, writeInfoLine: fileName$,非常丝滑的是,当你在Python运行这个脚本的时候,终端里也会显示运行文件的列表,如下图。美中不足的是,虽然脚本正常运行了,但是最后仍然会显示有一个PraatError,并且提示脚本没有正确运行,笔者查看了一下,无妨大碍。结果是正常显示的。这个可能是Parselmouth的一个bug。
在这里插入图片描述

5.4 Python生态系统便利

以上几个示例程序中,笔者也有意结合了如pandas, numpy等非常强大和优秀的Python 的数据处理库,可以轻松实现自定义分析。总之,能够从Python直接去调用Praat处理,并且不需要引入第3方的工具,或者接口,这对于结合Praat标注,处理,工程批量的同学,将是一个非常大的便利。

6. 相关代码地址

本文所提及所有脚本、代码均可到项目, Python-Data-Innovation学习系列 获取。建立通过 git clone的方式将项目整体拉取下来。 本文所在的项目目录是: https://github.com/feelins/Python-Data-Innovation/tree/main/Part-05/P05_008_parselmouth,欢迎下载使用,和交流 。

7. 总结

Parselmouth 为 Python 用户提供了一个强大的工具,使得他们可以在 Python 环境中利用 Praat 的功能进行语音数据处理,使得语音数据处理更加方便和高效。通过 Parselmouth,用户可以轻松地加载音频文件、提取特征、可视化数据,以及提取基频、共振峰、音强、时长、MFCC 等主要参数。并与 Praat 脚本集成,实现复杂的语音分析任务。无论是批量处理文件还是自定义分析,Parselmouth 都提供了灵活且高效的解决方案。

关注

在这里插入图片描述

版权说明

1、版权归本公众号“极地语音工作室”,原名“语音处理小站”所有;

2、未经本站或者作者允许, 不得任意转载本文内容,否则将视为侵权;

3、转载或者引用本文内容请注明来源及原作者;

4、对于不遵守此声明或者其他违法使用本站内容者,本人依法保留追究权等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

极地语音工作室

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值