专业的编程技术博客社区

网站首页 > 博客文章 正文

语音特征与机器学习

baijin 2025-02-06 13:57:02 博客文章 12 ℃ 0 评论

声音的三要素

音色音高、音强

在生理学中,发声器官分为三块:肺、喉和声道。在发声机制里,肺的作用相当于动力源,将气流输送至喉部。喉将来自肺部的气流调制为周期脉冲或类似随机噪声的激励声源,并送入声道。声道包括口腔,鼻腔和咽腔。将来自喉部的气流进行频谱整形(润色),从而产生不同音色的声音。肺部是动力源,决定着声音三要素中的音强——动力强,声音响。喉部是调制器,决定着声音三要素中的音高——频率。声带有频谱润色作用,决定着声音三要素中的音色。

人体发出的声音和信号声音一样:噪声,周期性和冲击性的声音。shop如下图所示:

当我们读"sh"这个音时,可以将其视为是噪声性声音。
读到"o"音时,可以将其视为是周期性声音。
读到"p"时,即是冲击性声音了。
由此,就一般意义来说:人能发出三种类型的声音:噪声性、周期性和冲击性。很多声音都是这三种的混合组合而成。

傅里叶变换

傅里叶是法国著名的数学家和物理学家,主要的成就就是“热的传导理论”和“傅里叶变换”,傅里叶变化是指将满足某个条件的函数表示成三角函数,即正余弦函数及其微分形式的线性组合,我们知道三角函数的微分形式也是三角函数。傅里叶变换最开始也是为了研究热力学而诞生的。英文名为Fourier transform,现在傅里叶变化是分析信号的一种方法。

具体定义

狄里赫莱条件
1. 函数在任意有限区间内连续,或只有有限个第一类间断点(当t从左或右趋于这个间断点时,函数有有限的左极限和右极限)
2. 在一个周期内,函数有有限个极大值或极小值。
3. x(t)在单个周期内绝对可积

f(t)是t的周期函数,如果t满足狄里赫莱条件,则有下图傅立叶变换成立。称为积分运算f(t)的傅立叶变换。

傅立叶变换

傅立叶逆变换

有关傅里叶变换的一些备注

正弦曲线无法组合成一个带有棱角的信号。但是,我们可以用正弦曲线来非常逼近地表示它,逼近到两种表示方法不存在能量差别。用正弦曲线来代替原来的曲线而不用方波或三角波来表示的原因在于,分解信号的方法是无穷的,但分解信号的目的是为了更加简单地处理原来的信号。用正余弦来表示原信号会更加简单,因为正余弦拥有原信号所不具有的性质:正弦曲线保真度。一个正弦曲线信号输入后,输出的仍是正弦曲线,只有幅度和相位可能发生变化,但是频率和波的形状仍是一样的。且只有正弦曲线才拥有这样的性质,正因如此我们才不用方波或三角波来表示。大自然中很多现象可以抽象成一个线性时不变系统来研究,无论你用微分方程还是传递函数或者状态空间描述。线性时不变系统可以这样理解:输入输出信号满足线性关系,而且系统参数不随时间变换。对于大自然界的很多系统,一个正弦曲线信号输入后,输出的仍是正弦曲线,只有幅度和相位可能发生变化,但是频率和波的形状仍是一样的。当然,指数信号也是系统的特征向量,表示能量的衰减或积聚。自然界的衰减或者扩散现象大多是指数形式的,或者既有波动又有指数衰减,因此具有特征的基函数就由三角函数变成复指数函数。

时域与频域

什么是时域?从我们出生,我们看到的世界都以时间贯穿,股票的走势、人的身高、汽车的轨迹都会随着时间发生改变。这种以时间作为参照来观察动态世界的方法我们称其为时域分析。而我们也想当然的认为,世间万物都在随着时间不停的改变,并且永远不会静止下来。
什么是频域?频域(frequency domain)是描述信号在频率方面特性时用到的一种坐标系。用线性代数的语言就是装着正弦函数的空间。频域最重要的性质是:它不是真实的,而是一个数学构造。频域是一个遵循特定规则的数学范畴。正弦波是频域中唯一存在的波形,这是频域中最重要的规则,即正弦波是对频域的描述,因为时域中的任何波形都可用正弦波合成。

对于一个信号来说,信号强度随时间的变化规律就是时域特性,信号是由哪些单一频率的信号合成的就是频域特性

时域分析与频域分析是对信号的两个观察面。时域分析是以时间轴为坐标表示动态信号的关系;频域分析是把信号变为以频率轴为坐标表示出来。一般来说,时域的表示较为形象与直观,频域分析则更为简练,剖析问题更为深刻和方便。

公式推导可以参考
https://www.toutiao.com/i6814679880647574023/

线性预测分析(LPC)

主要的思想是基本思想是一个语音的取样可用过去若干语音取样的线性组合来逼近。 通过使得实际语音取样与LPC取样间差值的平方和最小,即进行LMS逼近,可决定唯一的一组预测系数。而他们就是线性组合中的加权系数。LPC用于语音信号处理,不仅有预测功能,而且提供了一个非常好的声道模型。

梅尔频率倒谱系数(MFCC)

MFCC公式如下

这个公式相当于对信号频率做了一个非线性的处理。

这个非线性处理起突出低频,抑制高频的作用。
在音频信号处理里面,我们为什么需要MFCC呢?经过傅里叶变换的频率信息不够么?

这是因为——人耳对不同频率的声波有不同的听觉敏感度。从200HZ到5000HZ的语音信号对语音的清晰度影响比较大。频率较低的声音在内耳蜗基底膜上行波传递的距离大于频率较高的声音。根据掩蔽效应(两个响度不等的声音作用于人耳时,响度较高的频率成分会影响到响度较低的频率成分),对于人耳,低音容易掩蔽高音,高音掩蔽低音较困难。较之高频,人耳更喜欢低频。

MFCC如何提取

预加重

语音信号通过一个高通滤波器

μ值一般取0.9-1.0之间,用以提升高频信息

分帧

因为音频信号是非平稳的,但很多音频处理技术都是基于概率模型进行的,则需要对信号有一个要求:信号是平稳信号。否则其均值方差等统计量没有意义了。

加窗

为了避免频谱泄露。

频域转换

将上述加窗后的短时时域信号转换到频域。

梅尔刻度滤波器过滤

将信号进行一个平滑,分成几个子带。一般有两种,三角带通滤波器

还有一种是等高度的梅尔滤波

低频分辨率高,高频分辨率低。三角滤波会对高频信息的幅度进行一个衰减。至于三角滤波还是等高梅尔滤波,看实际研究的需要,如果需要子带之间的相对值大小,则衰减有意义,如果不需要子带间的相对大小,则衰减影响不大。

对数能量

计算每个滤波器组输出的对数能量,即子带能量

DCT变换

经DCT变换得到MFCC系数

python提取wav的MFCC

# 音频读取
wav_path = "./yyai/sound/0a0b46ae_nohash_0.wav"
fs, wavsignal = wavfile.read(wav_path)
print(wavsignal.shape)
mfcc_feature = pmfcc(wavsignal, fs)
print (mfcc_feature.shape)

一个简单的python 语音训练

# facescore.py
## Django Server
# from django.shortcuts import render
# from django.contrib.auth.models import User, Group
# from rest_framework import viewsets
# from yyai import serializers
# from django.http import HttpResponse, JsonResponse
# from django.views.decorators.csrf import csrf_exempt
# from rest_framework.decorators import action

## Keras
import keras
import tensorflow as tf
from keras.models import Sequential
from keras.layers import Dense
from keras.models import load_model
from keras.backend.tensorflow_backend import set_session

## os
import scipy.io.wavfile as wavfile
from python_speech_features import mfcc as pmfcc
import numpy as np
import matplotlib.pyplot as plt
import os
import os.path
import random
import gc

# 模型加载
# keras.backend.clear_session()
# model = load_model('./yyai/models/test.h5') 
# weight = model.layers[0].get_weights()

# Create your views here.
# @csrf_exempt
def predict_score(request):
    res = {
        "host": request.get_host(),
        "port": request.get_port(),
        "get_full_path": request.get_full_path(),
        "body": request.GET
    }
    x = float(request.GET['value'])
    v = {"value": weight[0][0][0] * x + weight[1][0]}
    return JsonResponse(v, safe = False)

def scan_files(path, ext):
    # course_resources
    all_files = []
    for root, dirs, files in os.walk(path, topdown = False):
        for name in files:
            if name.find(ext) != -1:
                all_files.append(os.path.join(root, name))
        for name in dirs:
            if name.find(ext) != -1:
                all_files.append(os.path.join(root, name))
    return all_files

def text_save(filename, data):
    file = open(filename, 'w')
    for i in range(len(data)):
        s = str(data[i]).replace('[', '').replace(']', '')
        s = s.replace("'", '').replace(',', '') +'\n'
        file.write(s)
    file.close()

def data_preprocessing():
    # 生成数据集合
    index = 1
    sds = scan_files('sounds', "wav")
    for path in sds:
        paths = path.split("/")
        # labels
        label = paths[1]
        fs, wavsignal = wavfile.read(path)
        mfcc_feature = pmfcc(wavsignal, fs).T
        # 合并数据
        data = np.array([])
        for dat in mfcc_feature:
            data = np.hstack((data, dat))
        data = abs(data)
        # 所有数据保留两位
        for i in range(len(data)):
            data[i] = round(data[i], 2)

        # 绘制
        # X = np.linspace(1, len(data) + 1, len(data))
        # Y = data
        # plt.plot(X, Y)
        # plt.savefig('plot.png')
        # plt.clf()

        # 存储数据文件
        if not os.path.isdir(label):
            os.mkdir(label)
        text_save((label + '/' + paths[2]).replace('.wav', ''), data)
        print("完成[" + str(index) + "/" + str(len(sds)) + "]")
        index = index + 1
        break;

# data_preprocessing()

# 制作数据集即可解读的DAT文件
def make_dataset():
    sign = np.zeros((1, 30))
    # 制作标签
    fp = open('train/labels', 'r')
    content = fp.read()
    fp.close()
    lines = content.split("\n")
    label_keys = []
    label_values = []
    for line in lines:
        item = line.split(" ")
        label_keys.append(item[1])
        label_values.append(item[0])

    # print(label_keys.index("bed"))
    # 每个类别一个文件
    files = scan_files('train', "nohash")
    index = 1
    for filename in files:
        label_key = (filename.split('/'))[1]
        label = int(label_keys.index(label_key))
        fp_dat = open("dat/" + label_key + '.dat', 'a')
        if sign[0][label - 1] < 20:
            sign[0][label - 1] = sign[0][label - 1] + 1
            fp = open(filename, 'r')
            content = fp.read()
            fp.close()
            lines = content.split('\n')
            data = ""
            for value in lines:
                data = data + value + ","
            data = data.strip(',')
            data = data + " " + str(label) + "\n"
            # 写入到一个文件里
            fp_dat.write(data)
            # 
            print("write " + str(index) + " !!")
        index = index + 1
        fp_dat.close()
# make_dataset()

# 加载数据集
def load_dataset():
    gc.disable();
    files = scan_files('dat', "dat")
    X_data = np.zeros((1, 1288))
    Y_data = np.array([])
    index = 1
    for filename in files:
        label_key = (filename.split('/'))[1]
        label_key = label_key.replace(".dat", "")
        fp = open(filename, 'r')
        content = fp.read()
        fp.close()
        # 数据加载内存,为训练做准备
        # X_train, Y_train, X_test, Y_test
        # 1288
        data = content.split("\n")
        for item_data in data:
            values = item_data.split(" ")
            if len(values[0].split(",")) > 100:
                v = values[0].split(",")
                if len(v) < 1288:
                    v = np.append(v, np.zeros((1, 1288 - len(v))))
                else:
                    v = v[:1288]
                X_data = np.row_stack((X_data, v))
                Y_data = np.append(Y_data, values[1])
                # numpy.delete
                if X_data.shape[0] > Y_data.shape[0]:
                    X_data = np.delete(X_data, 0, 0)
                print("===================")
                print(X_data.shape)
                index = index + 1
    # 生成数据集并打乱顺序
    All_data = list(zip(X_data, Y_data))
    random.shuffle(All_data)
    X_data[:], Y_data[:] = zip(*All_data)

    # 选择训练集和测试集 (7:3) 方式
    train_num = int(len(X_data) * 0.7)
    X_train = X_data[:train_num]
    Y_train = Y_data[:train_num]
    X_test = X_data[train_num:]
    Y_test = Y_data[train_num:]
    gc.enable();
    return X_train, Y_train, X_test, Y_test

# 训练过程
def train(X_train, Y_train, X_test, Y_test):

    num_classes = 30
    Y_train = keras.utils.to_categorical(Y_train, num_classes)
    Y_test = keras.utils.to_categorical(Y_test, num_classes)

    model = Sequential()
    model.add(Dense(512, activation = 'relu', input_shape = (1288, ))) # MFCC
    model.add(Dense(256, activation = 'relu'))
    model.add(Dense(64, activation = 'relu'))
    model.add(Dense(num_classes, activation = 'softmax'))
    model.compile(
        loss = keras.losses.categorical_crossentropy, 
        optimizer = keras.optimizers.Adadelta(), 
        metrics = ['accuracy']
    )
    model.fit(
        X_train, 
        Y_train, 
        batch_size = 124, 
        epochs = 5, 
        verbose = 1, 
        validation_data = (X_test, Y_test)
    )
    # 进行评估
    score = model.evaluate(X_test, Y_test, verbose = 0)
    print('Test loss:', score[0])
    print('Test accuracy:', score[1])

X_train, Y_train, X_test, Y_test = load_dataset()
train(X_train, Y_train, X_test, Y_test)

# 预测
def preict_wav(request):
    # TODO
    print("TODO")

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表