草庐IT

基于 librosa 的 LFCC 和 CQCC 特征提取

林书芹 2023-12-03 原文

本文实现了基于 librosa 的 LFCC 和 CQCC 特征提取,主要参考 librosa 中 MFCC 特征提取的过程,同时使用 torchaudio 来验证 LFCC 的正确性,使用 matlab 来验证 CQCC 的正确性。

LFCC

原理

LFCC 和 MFCC 的区别就是 fliter bank 的不同,MFCC 用的是 mel freq 的滤波器组,而 LFCC 用的是频率为线性分布的滤波器组,因此只要改变 MFCC 中滤波器组的获得方式保持其他代码不变即可。

实现

基于 librosa 库实现的 LFCC 如下:

import warnings
import numpy as np
import librosa
import scipy


def linear(sr, n_fft, n_filters=128, fmin=0.0, fmax=None, dtype=np.float32):

    if fmax is None:
        fmax = float(sr) / 2
    # Initialize the weights
    n_filters = int(n_filters)
    weights = np.zeros((n_filters, int(1 + n_fft // 2)), dtype=dtype)

    # Center freqs of each FFT bin
    fftfreqs = librosa.fft_frequencies(sr=sr, n_fft=n_fft)

    # 'Center freqs' of liner bands - uniformly spaced between limits
    # * Need to validate
    linear_f = np.linspace(fmin, fmax, n_filters + 2)

    fdiff = np.diff(linear_f)
    ramps = np.subtract.outer(linear_f, fftfreqs)

    for i in range(n_filters):
        # lower and upper slopes for all bins
        lower = -ramps[i] / fdiff[i]
        upper = ramps[i + 2] / fdiff[i + 1]

        # .. then intersect them with each other and zero
        weights[i] = np.maximum(0, np.minimum(lower, upper))

    # Only check weights if f_mel[0] is positive
    if not np.all((linear_f[:-2] == 0) | (weights.max(axis=1) > 0)):
        # This means we have an empty channel somewhere
        warnings.warn(
            "Empty filters detected in linear frequency basis. "
            "Some channels will produce empty responses. "
            "Try increasing your sampling rate (and fmax) or "
            "reducing n_filters.",
            stacklevel=2,
        )

    return weights


def linear_spec(
    y=None,
        sr=22050,
        S=None,
        n_fft=2048,
        hop_length=512,
        win_length=None,
        window='hann',
        center=True,
        pad_mode='constant',
        power=2.0,
        **kwargs
):
    if S is not None:
        # Infer n_fft from spectrogram shape, but only if it mismatches
        if n_fft // 2 + 1 != S.shape[-2]:
            n_fft = 2 * (S.shape[-2] - 1)
    else:
        S = (
            np.abs(
                librosa.stft(
                    y,
                    n_fft=n_fft,
                    hop_length=hop_length,
                    win_length=win_length,
                    center=center,
                    window=window,
                    pad_mode=pad_mode,
                )
            )
            ** power
        )
    # Build a linear filter
    linear_basis = linear(sr=sr, n_fft=n_fft, **kwargs)

    return np.einsum("...ft,mf->...mt", S, linear_basis, optimize=True)


def expand_to(x, *, ndim, axes):
    try:
        axes = tuple(axes)
    except TypeError:
        axes = tuple([axes])

    if len(axes) != x.ndim:
        raise Exception(
            "Shape mismatch between axes={} and input x.shape={}".format(
                axes, x.shape)
        )

    if ndim < x.ndim:
        raise Exception(
            "Cannot expand x.shape={} to fewer dimensions ndim={}".format(
                x.shape, ndim)
        )

    shape = [1] * ndim
    for i, axi in enumerate(axes):
        shape[axi] = x.shape[i]

    return x.reshape(shape)


def lfcc(y=None,
         sr=22050,
         S=None,
         n_lfcc=20,
         dct_type=2,
         norm='ortho',
         lifter=0,
         **kwargs):
    if S is None:
        S = librosa.power_to_db(linear_spec(y=y, sr=sr, **kwargs))

    M = scipy.fftpack.dct(S, axis=-2, type=dct_type,
                          norm=norm)[..., :n_lfcc, :]

    if lifter > 0:
        # shape lifter for broadcasting
        LI = np.sin(np.pi * np.arange(1, 1 + n_lfcc, dtype=M.dtype) / lifter)
        LI = expand_to(LI, ndim=S.ndim, axes=-2)

        M *= 1 + (lifter / 2) * LI
        return M
    elif lifter == 0:
        return M
    else:
        raise Exception(
            "LFCC lifter={} must be a non-negative number".format(lifter)
        )

用 torchaudio 现有的函数进行验证:

import torchaudio
import librosa
from torchaudio.transforms import LFCC
path = ''

# torchaudio
waveform, sample_rate = torchaudio.load(full_path)
print(waveform.shape)
lfcc_transform = LFCC(
    sample_rate=sample_rate,
    n_lfcc=13,
    n_filter=128,
    speckwargs={"n_fft": 512, "hop_length": 160, },
)
torch_lfcc = lfcc_transform(waveform)
print(torch_lfcc[0, :, 0])
# librosa
wave, sr = librosa.load(full_path, sr=16000)
print(wave.shape)
librosa_lfcc = lfcc(
    y=wave, sr=sr, n_lfcc=13, n_fft=512, hop_length=240, n_filters=128, pad_mode='reflect')
print(librosa_lfcc[:, 0])

运行结果为:

两者得到的结果基本一致。

CQCC

原理

STFT 和 CQT 可以看成是时间信号频域分析的两个同级的方法。

在 STFT 中,从长的序列中提取固定长度的片段与窗函数相乘后进行 FFT,然后重复应用滑动窗口得到最终的谱图。

STFT 实际上是一个滤波器组,定义 Q 因子为滤波器的中心频率和滤波器带宽之比: Q = f k δ f Q=\frac{f_k}{\delta f} Q=δffk在STFT 中,每个滤波器的带宽是恒定的,当频率从低频到高频时,Q 因子增加。

但是人类的感知系统的 Q 因子在 500Hz-20KHz 之间近似常数,因此,至少从感知角度来看,STFT对于语音信号的时频分析可能并不理想。

于是提出 CQT 变换,第一种算法由Youngberg和Boll于1978年提出的(Youngberg-Boll),而另一种算法则是由 Kashima 和MontReynaud-Kashima在1986年提出(Mont Reynaud)。在这些方法中,倍频程(octaves)是几何分布的,如下图:

CQT 计算

离散时域信号 x ( n ) x(n) x(n) 的 CQT 计算如下:
X C Q ( k , n ) = ∑ j = n − ⌊ N k / 2 ⌋ n + ⌊ N k / 2 ⌋ x ( j ) a k ∗ ( j − n + N k / 2 ) X^{C Q}(k, n)=\sum_{j=n-\left\lfloor N_k / 2\right\rfloor}^{n+\left\lfloor N_k / 2\right\rfloor} x(j) a_k^*\left(j-n+N_k / 2\right) XCQ(k,n)=j=nNk/2n+Nk/2x(j)ak(jn+Nk/2)
其中, k = 1 , 2 , ⋯   , K k = 1,2,\cdots, K k=1,2,,K 为频率索引, a k ∗ ( n ) a_k^*(n) ak(n) a k ( n ) a_k(n) ak(n) 的复共轭, N k N_k Nk 为可变的窗口大小, ⌊ ⋅ ⌋ \left\lfloor \cdot \right\rfloor 代表向下取整,基函数 a k ( n ) a_k(n) ak(n) 定义如下:
a k ( n ) = 1 C ( n N k ) exp ⁡ [ i ( 2 π n f k f s + Φ k ) ] a_k(n)=\frac{1}{C}\left(\frac{n}{N_k}\right) \exp \left[i\left(2 \pi n \frac{f_k}{f_s}+\Phi_k\right)\right] ak(n)=C1(Nkn)exp[i(2πnfsfk+Φk)]
其中, f k f_k fk 是第 k k k 个频带的中心频率, f s f_s fs 为采样率, w ( t ) w(t) w(t) 为窗函数, Φ k \Phi_k Φk 为相位偏置,缩放因子 C C C 由下式给出:
C = ∑ l = − ⌊ N k / 2 ⌋ ⌊ N k / 2 ⌋ w ( l + N k / 2 N k ) C=\sum_{l=-\left\lfloor N_k / 2\right\rfloor}^{\left\lfloor N_k / 2\right\rfloor} w\left(\frac{l+N_k / 2}{N_k}\right) C=l=Nk/2Nk/2w(Nkl+Nk/2)
为了满足恒Q变换,中心频率必须满足:
f k = f 1 2 k − 1 B f_k=f_1 2^{\frac{k-1}{B}} fk=f12Bk1
其中, f 1 f_1 f1 是最低频率带的中心频率, B B B 确定每个倍频程的频带数。

例如,假设 B = 1 B=1 B=1,则 f k = f 1 2 k − 1 f_k = f_1 2^{k-1} fk=f12k1 ,取 f s = 8000 H z , f 1 = 500 H z f_s=8000Hz, f_1 = 500Hz fs=8000Hz,f1=500Hz,则 f 2 = 1000 H z , f 3 = 2000 H z , f 4 = 4000 H z , f 5 = 8000 H z f_2=1000Hz, f_3=2000Hz, f_4=4000Hz, f_5=8000Hz f2=1000Hz,f3=2000Hz,f4=4000Hz,f5=8000Hz,不能再大了。而在DFT中,这几个频率呈线性变化。

则 Q 因子计算如下:
Q = f k f k + 1 − f k = ( 2 1 / B − 1 ) − 1 Q=\frac{f_k}{f_{k+1}-f_k}=\left(2^{1 / B}-1\right)^{-1} Q=fk+1fkfk=(21/B1)1
同时窗函数长度 N k N_k Nk 满足:
N k = f s f k Q N_k=\frac{f_s}{f_k} Q Nk=fkfsQ

CQCC 提取

首先 MFCC 计算如下:
MFCC ⁡ ( q ) = ∑ m = 1 M log ⁡ [ M F ( m ) ] cos ⁡ [ q ( m − 1 2 ) π M ] \operatorname{MFCC}(q)=\sum_{m=1}^M \log [M F(m)] \cos \left[\frac{q\left(m-\frac{1}{2}\right) \pi}{M}\right] MFCC(q)=m=1Mlog[MF(m)]cos[Mq(m21)π]
其中,Mel 谱计算如下:
M F ( m ) = ∑ k = 1 K ∣ X D F T ( k ) ∣ 2 H m ( k ) M F(m)=\sum_{k=1}^K\left|X^{D F T}(k)\right|^2 H_m(k) MF(m)=k=1K XDFT(k) 2Hm(k)
其中, k k k 是DFT之后的频率索引系数, H m ( k ) H_m(k) Hm(k) 是第 m m m 个Mel 尺度下的三角加权函数带通滤波器。这里, M M M 代表滤波器的个数( M F ( m ) M F(m) MF(m) M M M 点的序列), q q q 代表离散余弦变换的点数。

倒谱分析不能直接被用于 CQT,因为 X C Q ( k ) X^{C Q}(k) XCQ(k) 和 DCT 的余弦函数的尺度不同(一个是几何一个是线性)。可以通过将几何空间转换为线性空间来解决这个问题。

由于在 X C Q ( k ) X^{C Q}(k) XCQ(k) 中, k k k 个频带几何分布,信号重构的过程可以看成是前 k k k 个频带(低频部分)进行下采样,后 K − k K-k Kk 个频带(高频部分)进行上采样得到的,将第 k k k 个频带的中心频率 f k f_k fk 和 第一个频带的中心频率 f 1 = f m i n f_1=f_{min} f1=fmin 的频率差记为:
Δ f k ↔ 1 = f k − f 1 = f 1 ( 2 k − 1 B − 1 ) \Delta f^{k \leftrightarrow 1}=f_k-f_1=f_1\left(2^{\frac{k-1}{B}}-1\right) Δfk1=fkf1=f1(2Bk11)
其中, k = 1 , 2 , ⋯   , K k = 1,2,\cdots, K k=1,2,,K 为频率索引,距离 Δ f k ↔ 1 \Delta f^{k \leftrightarrow 1} Δfk1 k k k 的递增函数。我们以周期 T l T_l Tl 进行重采样,这等效于确定一个关于 k l k_l kl 的函数使得:
T l = Δ f k l ↔ 1 T_l=\Delta f^{k_l \leftrightarrow 1} Tl=Δfkl1
以下关注第一个倍频程,通过将第一个倍频程以周期 T l T_l Tl 进行 d d d 等分,解得 k l k_l kl 为:
f 1 d = f 1 ( 2 k l − 1 B − 1 ) → k l = B log ⁡ 2 ( 1 + 1 d ) \frac{f_1}{d}=f_1\left(2^{\frac{k_l-1}{B}}-1\right) \rightarrow k_l=B \log _2\left(1+\frac{1}{d}\right) df1=f1(2Bkl11)kl=Blog2(1+d1)
新的频率计算为:
F l = 1 T l = [ f 1 ( 2 k l − 1 B − 1 ) ] − 1 F_l=\frac{1}{T_l}=\left[f_1\left(2^{\frac{k_l-1}{B}}-1\right)\right]^{-1} Fl=Tl1=[f1(2Bkl11)]1
因此,在第一个倍频程中有 d d d 个均匀采样,第二个倍频程中有 2 d 2d 2d 个,第 j − 1 j-1 j1 个倍频程中有 2 j d 2^jd 2jd 个。

信号重构方法采用了多相抗混叠滤波器和样条插值方法,以均匀的采样率 F l F_l Fl 对信号进行重新采样。

最后,CQCC 系数计算如下:
C Q C C ( p ) = ∑ l = 1 L log ⁡ ∣ X C Q ( l ) ∣ 2 cos ⁡ [ p ( l − 1 2 ) π L ] C Q C C(p)=\sum_{l=1}^L \log \left|X^{C Q}(l)\right|^2 \cos \left[\frac{p\left(l-\frac{1}{2}\right) \pi}{L}\right] CQCC(p)=l=1Llog XCQ(l) 2cos[Lp(l21)π]
其中, p = 0 , 1 , ⋯   , L − 1 p=0,1,\cdots,L-1 p=0,1,,L1 是重采样之后得频带索引。

我理解是,比如说设置 d = 5 d=5 d=5 ,频率 500 − 1000 H z 500-1000Hz 5001000Hz 之间采样就可以分成五个 500 , 600 , 700 , 800 , 900 500,600,700,800,900 500,600,700,800,900,而频率 1000 − 2000 H z 1000-2000Hz 10002000Hz 之间采样分成 2 d = 10 2d=10 2d=10 个频带,即 1000 , 1100 , 1200 , ⋯   , 1900 1000,1100,1200,\cdots,1900 1000,1100,1200,,1900,同理频率范围在 2000 − 4000 H z 2000-4000Hz 20004000Hz 之间的频率可以分成 2 2 ∗ 5 = 20 2^2*5=20 225=20 个频带,以此类推。这样虽然两两中心频率之间是几何间隔,但是采样的数量也是呈几何增长,所以最终重采样得到的频率还是线性的,从而可以进行 DCT 变换。

有关基于 librosa 的 LFCC 和 CQCC 特征提取的更多相关文章

  1. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  2. 叮咚买菜基于 Apache Doris 统一 OLAP 引擎的应用实践 - 2

    导读:随着叮咚买菜业务的发展,不同的业务场景对数据分析提出了不同的需求,他们希望引入一款实时OLAP数据库,构建一个灵活的多维实时查询和分析的平台,统一数据的接入和查询方案,解决各业务线对数据高效实时查询和精细化运营的需求。经过调研选型,最终引入ApacheDoris作为最终的OLAP分析引擎,Doris作为核心的OLAP引擎支持复杂地分析操作、提供多维的数据视图,在叮咚买菜数十个业务场景中广泛应用。作者|叮咚买菜资深数据工程师韩青叮咚买菜创立于2017年5月,是一家专注美好食物的创业公司。叮咚买菜专注吃的事业,为满足更多人“想吃什么”而努力,通过美好食材的供应、美好滋味的开发以及美食品牌的孵

  3. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  4. kvm虚拟机安装centos7基于ubuntu20.04系统 - 2

    需求:要创建虚拟机,就需要给他提供一个虚拟的磁盘,我们就在/opt目录下创建一个10G大小的raw格式的虚拟磁盘CentOS-7-x86_64.raw命令格式:qemu-imgcreate-f磁盘格式磁盘名称磁盘大小qemu-imgcreate-f磁盘格式-o?1.创建磁盘qemu-imgcreate-fraw/opt/CentOS-7-x86_64.raw10G执行效果#ls/opt/CentOS-7-x86_64.raw2.安装虚拟机使用virt-install命令,基于我们提供的系统镜像和虚拟磁盘来创建一个虚拟机,另外在创建虚拟机之前,提前打开vnc客户端,在创建虚拟机的时候,通过vnc

  5. ruby-on-rails - Rails - 从命名路由中提取 HTTP 动词 - 2

    Rails中有没有一种方法可以提取与路由关联的HTTP动词?例如,给定这样的路线:将“users”匹配到:“users#show”,通过:[:get,:post]我能实现这样的目标吗?users_path.respond_to?(:get)(显然#respond_to不是正确的方法)我最接近的是通过执行以下操作,但它似乎并不令人满意。Rails.application.routes.routes.named_routes["users"].constraints[:request_method]#=>/^GET$/对于上下文,我有一个设置cookie然后执行redirect_to:ba

  6. ruby-on-rails - Ruby - 如何从 ruby​​ 上的 .pfx 文件中提取公钥、rsa 私钥和 CA key - 2

    我有一个.pfx格式的证书,我需要使用ruby​​提取公共(public)、私有(private)和CA证书。使用shell我可以这样做:#ExtractPublicKey(askforpassword)opensslpkcs12-infile.pfx-outfile_public.pem-clcerts-nokeys#ExtractCertificateAuthorityKey(askforpassword)opensslpkcs12-infile.pfx-outfile_ca.pem-cacerts-nokeys#ExtractPrivateKey(askforpassword)o

  7. ruby - cucumber 特征和步骤定义 - 2

    我是Cucumber测试的新手。我创建了两个特征文件:events.featurepartner.feature并将我的步骤定义放在step_definitions文件夹中:./step_definitions/events.rbpartner.rbCucumber似乎在所有.rb文件中查找步骤信息。有没有办法限制该功能查看特定的步骤定义文件?我之所以要这样做,是因为即使我使用了--guess标志,我也会遇到不明确的匹配错误。我之所以要这样做,有以下几个原因。我正在测试CMS,并希望在不同的功能中测试每种不同的内容类型(事件和合作伙伴)。事件.特征Feature:AddpartnerA

  8. ruby - 如何在ruby中提取方括号内的内容 - 2

    我正在尝试提取方括号内的内容。到目前为止,我一直在使用它,它有效,但我想知道我是否可以直接在正则表达式中使用某些东西,而不是使用这个删除功能。a="Thisissuchagreatday[coolawesome]"a[/\[.*?\]/].delete('[]')#=>"coolawesome" 最佳答案 差不多。a="Thisissuchagreatday[coolawesome]"a[/\[(.*?)\]/,1]#=>"coolawesome"a[/(?"coolawesome"第一个依赖于提取组而不是完全匹配;第二个利用前瞻和

  9. ruby-on-rails - (Ruby,Rails) 基于角色的身份验证和用户管理...? - 2

    我正在寻找用于Rails的优质管理插件。似乎大多数现有的插件/gem(例如“restful_authentication”、“acts_as_authenticated”)都围绕着self注册等展开。但是,我正在寻找一种功能齐全的基于管理/管理角色的解决方案——但不是简单地附加到另一个非基于角色的解决方案。如果我找不到,我想我会自己动手......只是不想重新发明轮子。 最佳答案 RyanBates最近做了两个关于授权的railscast(注意身份验证和授权之间的区别;身份验证检查用户是否如她所说的那样,授权检查用户是否有权访问资源

  10. ruby - 在 Rakefile 中动态生成 Rake 测试任务(基于现有的测试文件) - 2

    我正在根据Rakefile中的现有测试文件动态生成测试任务。假设您有各种以模式命名的单元测试文件test_.rb.所以我正在做的是创建一个以“测试”命名空间内的文件名命名的任务。使用下面的代码,我可以用raketest:调用所有测试require'rake/testtask'task:default=>'test:all'namespace:testdodesc"Runalltests"Rake::TestTask.new(:all)do|t|t.test_files=FileList['test_*.rb']endFileList['test_*.rb'].eachdo|task|n

随机推荐