语音信号处理 | Python实现端点检测

2023-05-16

由于项目需要,我要使用Python对语音进行端点检测,在之前的博客使用短时能量和谱质心特征进行端点检测中,我使用MATLAB实现了一个语音端点检测算法,下面我将使用Python重新实现这个这个算法,并将其封装到VAD类中,如下是运行结果:

软件环境

Python3.8、scipy、pyaudio、matplotlib

程序

matlab程序转换到python还是挺容易的,VAD.py程序如下:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

import numpy as np
import sys
from collections import deque
import matplotlib.pyplot as plt
import scipy.signal
import pyaudio
import struct as st

def ShortTimeEnergy(signal, windowLength, step):
    """
    计算短时能量
    Parameters
    ----------
    signal : 原始信号.
    windowLength : 帧长.
    step : 帧移.
    
    Returns
    -------
    E : 每一帧的能量.
    """
    signal = signal / np.max(signal) # 归一化
    curPos = 0
    L = len(signal)
    numOfFrames  = np.asarray(np.floor((L-windowLength)/step) + 1, dtype=int)
    E = np.zeros((numOfFrames, 1))
    for i in range(numOfFrames):
        window = signal[int(curPos):int(curPos+windowLength-1)];
        E[i] = (1/(windowLength)) * np.sum(np.abs(window**2));
        curPos = curPos + step;
    return E

def SpectralCentroid(signal,windowLength, step, fs):
    """
    计算谱质心
    Parameters
    ----------
    signal : 原始信号.
    windowLength : 帧长.
    step : 帧移.
    fs : 采样率.

    Returns
    -------
    C : 每一帧的谱质心.
    """
    signal = signal / np.max(signal) # 归一化
    curPos = 0
    L = len(signal)
    numOfFrames  = np.asarray(np.floor((L - windowLength) / step) + 1, dtype=int)
    H = np.hamming(windowLength)
    m = ((fs / (2 * windowLength)) * np.arange(1, windowLength, 1)).T
    C = np.zeros((numOfFrames, 1))
    for i in range(numOfFrames):
        window = H * (signal[int(curPos) : int(curPos + windowLength)])
        FFT = np.abs(np.fft.fft(window, 2 * int(windowLength)))
        FFT = FFT[1 : windowLength]
        FFT = FFT / np.max(FFT)
        C[i] = np.sum(m * FFT) / np.sum(FFT)
        if np.sum(window**2) < 0.010:
            C[i] = 0.0
        curPos = curPos + step;
    C = C / (fs/2)
    return C

def findMaxima(f, step):
    """
    寻找局部最大值
    Parameters
    ----------
    f : 输入序列.
    step : 搜寻窗长.

    Returns
    -------
    Maxima : 最大值索引 最大值
    countMaxima : 最大值的数量
    """
    ## STEP 1: 寻找最大值
    countMaxima = 0
    Maxima = []
    for i in range(len(f) - step - 1): # 对于序列中的每一个元素:
        if i >= step:
            if (np.mean(f[i - step : i]) < f[i]) and (np.mean(f[i + 1 : i + step + 1]) < f[i]): 
                # IF the current element is larger than its neighbors (2*step window)
                # --> keep maximum:
                countMaxima = countMaxima + 1
                Maxima.append([i, f[i]])
        else:
            if (np.mean(f[0 : i + 1]) <= f[i]) and (np.mean(f[i + 1 : i + step + 1]) < f[i]):
                # IF the current element is larger than its neighbors (2*step window)
                # --> keep maximum:
                countMaxima = countMaxima + 1
                Maxima.append([i, f[i]])

    ## STEP 2: 对最大值进行进一步处理
    MaximaNew = []
    countNewMaxima = 0
    i = 0
    while i < countMaxima:
        # get current maximum:
        
        curMaxima = Maxima[i][0]
        curMavVal = Maxima[i][1]

        tempMax = [Maxima[i][0]]
        tempVals = [Maxima[i][1]]
        i = i + 1

        # search for "neighbourh maxima":
        while (i < countMaxima) and (Maxima[i][0] - tempMax[len(tempMax) - 1] < step / 2):
            
            tempMax.append(Maxima[i][0])
            tempVals.append(Maxima[i][1])
            i = i + 1
            
        MM = np.max(tempVals)
        MI = np.argmax(tempVals) 
        if MM > 0.02 * np.mean(f): # if the current maximum is "large" enough:
            # keep the maximum of all maxima in the region:
            MaximaNew.append([tempMax[MI], f[tempMax[MI]]])
            countNewMaxima = countNewMaxima + 1   # add maxima
    Maxima = MaximaNew
    countMaxima = countNewMaxima
    
    return Maxima, countMaxima

def VAD(signal, fs):
    win = 0.05
    step = 0.05
    Eor = ShortTimeEnergy(signal, int(win * fs), int(step * fs));
    Cor = SpectralCentroid(signal, int(win * fs), int(step * fs), fs);
    E = scipy.signal.medfilt(Eor[:, 0], 5)
    E = scipy.signal.medfilt(E, 5)
    C = scipy.signal.medfilt(Cor[:, 0], 5)
    C = scipy.signal.medfilt(C, 5)
    
    E_mean = np.mean(E);
    Z_mean = np.mean(C);
    Weight = 100 # 阈值估计的参数
    # 寻找短时能量的阈值
    Hist = np.histogram(E, bins=10) # 计算直方图
    HistE = Hist[0]
    X_E = Hist[1]
    MaximaE, countMaximaE = findMaxima(HistE, 3) # 寻找直方图的局部最大值
    if len(MaximaE) >= 2: # 如果找到了两个以上局部最大值
        T_E = (Weight*X_E[MaximaE[0][0]] + X_E[MaximaE[1][0]]) / (Weight + 1)
    else:
        T_E = E_mean / 2
    
    # 寻找谱质心的阈值
    Hist = np.histogram(C, bins=10)
    HistC = Hist[0]
    X_C = Hist[1]
    MaximaC, countMaximaC = findMaxima(HistC, 3)
    if len(MaximaC)>=2:
        T_C = (Weight*X_C[MaximaC[0][0]]+X_C[MaximaC[1][0]]) / (Weight+1)
    else:
        T_C = Z_mean / 2
    
    # 阈值判断
    Flags1 = (E>=T_E)
    Flags2 = (C>=T_C)
    flags = np.array(Flags1 & Flags2, dtype=int)
    
    ## 提取语音片段
    count = 1
    segments = []
    while count < len(flags): # 当还有未处理的帧时
        # 初始化
        curX = []
        countTemp = 1
        while ((flags[count - 1] == 1) and (count < len(flags))):
            if countTemp == 1: # 如果是该语音段的第一帧
                Limit1 = np.round((count-1)*step*fs)+1 # 设置该语音段的开始边界
                if Limit1 < 1:
                    Limit1 = 1
            count = count + 1 		# 计数器加一
            countTemp = countTemp + 1	# 当前语音段的计数器加一
            
        if countTemp > 1: # 如果当前循环中有语音段
            Limit2 = np.round((count - 1) * step * fs) # 设置该语音段的结束边界
            if Limit2 > len(signal):
                Limit2 = len(signal)
            # 将该语音段的首尾位置加入到segments的最后一行
            segments.append([int(Limit1), int(Limit2)])
        count = count + 1
        
    # 合并重叠的语音段
    for i in range(len(segments) - 1): # 对每一个语音段进行处理
        if segments[i][1] >= segments[i + 1][0]:
            segments[i][1] = segments[i + 1][1]
            segments[i + 1, :] = []
            i = 1

    return segments

if __name__ == "__main__":
    CHUNK = 1600
    FORMAT = pyaudio.paInt16
    CHANNELS = 1 # 通道数
    RATE = 16000 # 采样率
    RECORD_SECONDS = 3 # 时长
    p = pyaudio.PyAudio()
    stream = p.open(format=FORMAT,
                    channels=CHANNELS,
                    rate=RATE,
                    input=True,
                    frames_per_buffer=CHUNK)
    frames = [] # 音频缓存
    while True:
        data = stream.read(CHUNK)
        frames.append(data)
        if(len(frames) > RECORD_SECONDS * RATE / CHUNK):
            del frames[0]
        datas = b''
        for i in range(len(frames)):
            datas = datas + frames[i]
        if len(datas) == RECORD_SECONDS * RATE * 2:
            fmt = "<" + str(RECORD_SECONDS * RATE) + "h"
            signal = np.array(st.unpack(fmt, bytes(datas))) # 字节流转换为int16数组
            segments = VAD(signal, RATE) # 端点检测
            # 可视化
            index = 0
            for seg in segments:
                if index < seg[0]:
                    x = np.linspace(index, seg[0], seg[0] - index, endpoint=True, dtype=int)
                    y = signal[index:seg[0]]
                    plt.plot(x, y, 'g', alpha=1)
                x = np.linspace(seg[0], seg[1], seg[1] - seg[0], endpoint=True, dtype=int)
                y = signal[seg[0]:seg[1]]
                plt.plot(x, y, 'r', alpha=1)
                index = seg[1]            
            x = np.linspace(index, len(signal), len(signal) - index, endpoint=True, dtype=int)
            y = signal[index:len(signal)]
            plt.plot(x, y, 'g', alpha=1)
            plt.ylim((-32768, 32767))
            plt.show()
    

运行结果

下面是语音“语音信号处理”的端点检测结果:
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

语音信号处理 | Python实现端点检测 的相关文章

  • SLAM会议笔记(五)LLOAM

    LLOAM LiDAR Odometry and Mapping with Loop closure Detection Based Correction Abstract 在LOAM的基础上加入了回环检测 xff0c 实现了雷达SLAM的
  • C++基础:第三章 对象与基本类型

    文章目录 第3章 对象与基本类型第1节 初始化 赋值语句第2节 类型详述类型描述类型种类划分与类型相关的标准未定义部分变量 第3节 复合类型 xff1a 从指针到引用指针指针特点相关操作符指针的定义 96 void 96 指针指针的指针指针
  • C++基础:第四章 数组、vector和字符串

    文章目录 第4章 数组 vector和字符串第1节 数组注意事项数组的复杂声明数组中元素的访问数组到指针的隐式转换获得s指向数组开头和结尾的指针数组的其他操作C字符串多维数组 第2节 vector构造和初始化元素的索引和遍历迭代器itera
  • C++基础:第五章 表达式基础与详述

    第五章 表达式基础与详述 第1节 基础 xff1a 引入 表达式由一到多个操作数组成 xff0c 可以求值并 xff08 通常会 xff09 返回求值结果 xff08 函数的调用是一种表达式 xff0c 有时函数不会返回求值结果 xff09
  • C++基础:第六章 语句

    第六章 语句 第1节 语句基础 常见类别 xff1a 表达式语句 xff0c 求值后丢弃 2 43 3 空语句复合语句 xff0c 用大括号 xff0c 形成独立的域 顺序语句 按先后顺序执行实际执行顺序可能产生变换 xff08 编译器优化
  • C++基础:第七章 函数

    第七章 函数 第1节 函数基础 栈帧结构 函数的外部链接 第2节 函数详解 传值 传址 传引用 传参数时的类型退化 xff0c 传数组时函数形参退化成指针 xff0c 所以形参不要写数组个数 多维数组作为函数参数时 void fun int
  • C++基础:第八章 深入IO

    第八章 深入IO 第1节 序 第2节 IOStream概述 流式IO而非记录IO 处理的主要问题 表示形式的变化 xff1a 使用格式化 解析在数据的内部表示与字符序列之间切换与外部设备的通信 xff1a 针对不同的外部设备引入不同的处理逻
  • 在vscode终端安装vue构建工具vite

    首先确保已安装npm 第一步 xff1a 全局安装yarn 0 打开cmd xff08 windows 43 R xff09 1 输入安装命令 npm install g yarn 2 如果能看到版本号 xff0c 则安装成功 yarn v
  • cmake相关:sudo make install后的卸载

    sudo make install后的卸载 我们知道linux中一般的编译一个package的顺序是 span class token function git span clone package git span class token
  • 提取rosbag中的图像话题存为本地图像

    新建存放图片文件夹 首先运行ros master roscore 在目标文件夹目录下运行 rosrun image view extract images sec per frame 61 0 05 image 61 lt ROSIMAGE
  • matlab循环读取文件

    一般情况下 xff0c 假如我要读取一个名为a txt的文件 xff0c 只需要利用下面的语句 xff1a a span class token operator 61 span span class token function load
  • 使用OpenMVG获取相机位姿的方法

    在生成sfm data bin文件后 xff0c 在文件目录下执行 openMVG main ConvertSfM DataFormat binary span class token operator span i yoursfm dat
  • Ubuntu修改文件夹下面所有文件权限的方法

    ubuntu修改文件夹下所有文件的权限 命令为 xff1a sudo chmod span class token operator span R 777 filename filename为要修改的文件夹名字 R应该是表示递归修改file
  • 写出对js事件,事件流,事件对象的理解

    事件 JavaScript 使我们有能力创建动态页面 事件是可以被 JavaScript 侦测到的行为 网页中的每个元素都可以产生某些可以触发 JavaScript 函数的事件 比方说 xff0c 我们可以在用户点击某按钮时产生一个 onC
  • UDP实时图像传输

    写在前面 首先问个问题 xff0c 为什么要用UDP传输图像 xff0c 而不是TCP xff1f TCP是我们经常使用的通信协议 xff0c 从认识它的第一天起 xff0c 就应该知道 xff0c 它非常稳 xff0c 丢包率超低 但是一
  • 机器学习 | 使用k-近邻算法实现手写识别系统

    KNN概述 k 近邻算法就是通过计算不同特征值之间的距离来进行分类的算法 假设我们现在有一个样本集 xff0c 每个样本都有几个特征用来描述这个样本 xff0c 以及一个它所属分类的标签 当我们拿到一个没有标签的样本时 xff0c 该如何判
  • Windows下如何查看一个process内有哪些thread

    从https docs microsoft com en us sysinternals downloads pslist下载PsTools xff0c 解压后找到pslist exe并移动到C盘任一目录下 xff0c 使用说明都在Psto
  • 机器人路径规划之动态窗口法

    动态窗口法 Dynamic Window Approach 概述 DWA是一种基于速度的局部规划器 xff0c 可计算达到目标所需的机器人的最佳无碰撞速度 程序实现 DWA算法主要分三步 xff1a 计算动态窗口计算最优 v
  • cso(布谷鸟)算法优化神经网络参数

    之前写了一篇pso工程上使用方法 xff0c 这一篇使用布谷鸟算法 xff0c 相关的原理也比较多的介绍了 目前实验结果还是pso快一点 一 布谷鸟算法介绍 布谷鸟搜索算法 xff0c 是 由剑 桥 大 学YANG等在文献 中提出的一种群智
  • 多线程之线程安全(Thread Safety)

    什么是线程安全 Thread Safety xff1f 怎样才能做到线程安全 xff1f 线程安全 线程安全指某个函数 函数库在多线程环境中被调用时 xff0c 能够正确地处理多个线程之间的共享变量 xff0c 使程序功能正确完成 数据类型

随机推荐

  • 多线程之简易注册验证程序

    问题描述 使用VC2010或以上版本编写一个多线程注册验证程序 xff0c 要求 xff1a 通过对话框输入若干人的学号和姓名 xff0c 并存入列表中作为注册记录 用户输入一个学号 xff0c 程序能通过多线程的方式与注册记录比对来验证其
  • 多线程之基于积分法与欧拉恒等式法的圆周率计算及OMP优化

    文章目录 一 问题描述二 积分法算法推导编程实现OMP优化 三 欧拉恒等式算法推导编程实现前期准备加法减法乘法除法 算法实现 OMP优化 四 总结积分法与欧拉恒等式法的对比OMP实现方式的对比 一 问题描述 分别采用积分法和欧拉恒等式计算
  • 语音信号处理 | 基于Hilbert-Huang变换的基音检测方法

    HHT原理 Hiibert Huang变换是由Huang等人于1998年提出来的一种信号分析方法 xff0c 它主要由两个部分组成 经验模型分解 Empirical Mode Decomposition EMD 和希尔伯特变换 xff08
  • 机器学习 | 使用TensorFlow搭建神经网络实现鸢尾花分类

    鸢尾花分类问题是机器学习领域一个非常经典的问题 xff0c 本文将利用神经网络来实现鸢尾花分类 实验环境 xff1a Windows10 TensorFlow2 0 Spyder 参考资料 xff1a 人工智能实践 xff1a Tensor
  • 语音信号处理 | 基于卡尔曼滤波的语音增强算法

    文章目录 1 概述2 卡尔曼滤波原理被估计的信号离散卡尔曼滤波算法参数选择 3 基于卡尔曼滤波的语音增强算法语音模型分析参数确定 4 程序实现语音数据的导入 加噪与分帧卡尔曼滤波器参数初始化卡尔曼滤波过程结果可视化 5 运行结果与结果分析运
  • UDP实时图像传输进阶篇——1080P视频传输

    在UDP实时图像传输一文中 xff0c 介绍了如何使用UDP来实现图像的实时传输 xff0c 并使用C 进行了发送端和接收端的搭建 但是文中的方法是对整张图片进行JPEG压缩 xff0c 并通过UDP一次性地发送到接收端 xff0c 由于一
  • 机器人路径规划之Dijkstra算法

    在机器人路径规划之动态窗口法文中 xff0c 介绍了一种局部路径规划方法 动态窗口法 xff0c 本文将介绍一种全局路径规划方法 Dijkstra算法 狄克斯特拉算法 Dijkstra算法是从一个顶点到其余各顶点的最短路径算法 xff0c
  • 语音信号处理 | 使用短时能量和谱质心特征进行端点检测

    文章目录 概述原理及MATLAB实现基本流程特征提取短时能量谱质心 阈值估计和阈值化处理提取语音片段 MATLAB2020a中的VAD函数参考 概述 在复杂的应用环境下 xff0c 从音频中分割出语音信号和和非语音信号 xff0c 是一个很
  • 语音信号处理 | 傅里叶变换、短时傅里叶变换、小波变换、希尔伯特变换、希尔伯特黄变换

    在信号处理领域 xff0c 存在诸多变换 xff0c 比如标题中的五个变换 本文将对这五个变换进行介绍和比较 在开始之前 xff0c 我们需要先理清什么是平稳信号 xff0c 什么是非平稳信号 我们知道 xff0c 自然界中几乎所有信号都是
  • clang-format格式文件。可以直接复制引用

    Language Cpp BasedOnStyle LLVM AccessModifierOffset 2 AlignAfterOpenBracket Align AlignConsecutiveMacros false AlignCons
  • 多线程之多核线上考试试题瞎解

    匆忙的大三早已结束 xff0c 时隔两月 xff0c 再以此文祭奠我炸掉的多核考试 这次考试真正能写出来的也就两道题 xff0c 以下简单地记录一下 第二题 随机产生2个10 10的浮点数矩阵A和B xff0c 先将矩阵A和B作转置 xff
  • 视觉SLAM | RealsenseD435i相机标定

    在运行VINS MONO VINS Fusion等SLAM方案的时候 xff0c 需要很准确的相机参数 xff0c 否则很容易漂移 本文是RealsenseD435i相机标定过程的记录 xff0c 标定主要有三个步骤 IMU标定相机标定IM
  • 视觉SLAM | 使用RealsenseD435i运行VINS-Fusion

    使用RealsenseD435i运行VINS Fusion VINS Fusion是基于双目的视觉惯导方案 xff0c 不太符合我目前的需求 xff0c 但这是我使用的第一个视觉SLAM方案 xff0c 接下来还是简单记录下 运行环境 硬件
  • 视觉SLAM | 在ROS上运行ORB-SLAM2

    本文直接使用的github上的orb slam 2 ros实现在ROS上运行ORB SLAM2 xff0c 这个ros包能够得到相机的位姿以及稀疏点云 xff0c 而且删掉了对Pangolin的依赖 xff0c 进行可视化时要用RViz 运
  • ROS报错记录及解决方法(不定期更新)

    ROS下载缓慢 如果是在国内安装 xff0c 建议在安装之前先配置国内的镜像源 xff0c 在软件和更新进行更改即可 参考 xff1a Ubuntu18 04下安装ROS 由于没有公钥 xff0c 无法验证下列签名 安装ROS时报错 xff
  • ROS与STM32通信

    ROS与STM32是用串口进行通信的 xff0c 主要有两种方式 xff0c 一是将STM32作为一个节点 xff0c 二是制作一个上位机节点 负责与STM32进行串口通信 xff0e 第一种方式需要专门的硬件 xff0c 所以我选择第二种
  • 使用VScode搭建ROS开发环境

    俗话说 xff02 工欲善其事必先利其器 xff02 xff0c 之前在Ubuntu上运行的ROS项目都是用vim或者gedit编写和修改代码 xff0c 然后在终端编译运行 xff0c 很不方便 xff0c 函数跳转查看都没办法实现 所以
  • TCP实时图像传输

    之前尝试过使用UDP进行图像传输 xff0c 而UDP协议要求包小于64K xff0c 对于较大的图像 xff0c 需要使用分片压缩的方式进行传输 xff0c 操作较复杂 xff0c 同时不能保证图片的每一部分都能够正确传输 详见 xff1
  • STM32部分BUG及解决方法记录(不定期更新)

    1 编译使用CubeMX生成的代码时报错 Error L6218E Undefined symbol HAL PWREx DisableUCPDDeadBattery referred from stm32g4xx hal msp o 解决
  • 语音信号处理 | Python实现端点检测

    由于项目需要 xff0c 我要使用Python对语音进行端点检测 xff0c 在之前的博客使用短时能量和谱质心特征进行端点检测中 xff0c 我使用MATLAB实现了一个语音端点检测算法 xff0c 下面我将使用Python重新实现这个这个