声音分类及其实战(一)

前言

玩过CV的都知道猫狗识别,通过输入一张猫狗图片之后经过神经网络就能知道这张图片属于猫还是狗,图像识别的输入是很直观的,这源于我们对图像理性的认识(RGB图像有三个通道,每个通道中每个像素点都是一个数字…)。但声音显然是一个朦胧的概念,声音是如何实现分类的呢?CV有人脸识别,声音也存在声纹识别,这些在人工智能里面都是如何判断的,让我们从一篇简单的声音分类开始吧!

音频简介

现在正式开启语音的学习历程,语音识别下面有很多任务,例如中文识别、文字转语音、语音唤醒以及说话人识别(声纹识别)等。我观察了很多demo,他们的语音识别所用的语音数据集文件基本上都是.wav文件,就算原始语音不是wav也会转为wav,所以我们首先了解一下wav文件是什么,以及其中的组成。

WAV是什么?

WAV格式是微软公司开发的一种声音文件格式,也叫波形声音文件,是最早的数字音频格式,被Windows平台及其应用程序广泛支持。WAV格式支持许多压缩算法,支持多种音频位数、采样频率和声道,采用44.1kHz的采样频率,16位量化位数,因此WAV的音质与CD相差无几,通常被称为无损音频。

采样率、位深

对于音频数据,比较重要的两个概率就是采样率和位深了,在此简单介绍一下这两个概念,以对声音的产生有一个直观的感受。先对他们有一个宏观的认识:声波,有频率和振幅,频率高低决定音调,振幅大小决定响度,采样率是对频率采样,位深是对振幅采样。

音频信号是一种连续的模拟信号,计算机不可能处理这种连续的模拟数据。它首先需要转换为一系列离散值,而“采样”就是这样做的。“采样率”和“位深”是离散化音频信号时最重要的两个要素。在下图中,我们可以看到它们与模数转换的关系。在图中,x 轴是时间,y 轴是幅度。“采样率”决定采样的频率,“位深度”决定采样的详细程度。
在这里插入图片描述
因此采样率是指:声音信号在“模→数”转换过程中单位时间内采样的次数,也就是每秒钟所采样样本的总数目是采样率。而每个样本中信息的比特数就是位深。

可能还是比较抽象,此处借助一个具体的例子来解释:

经常见到这样的描述: 44100HZ 16bit stereo 或者 22050HZ 8bit mono 等等.
44100HZ 16bit stereo: 每秒钟有 44100 次采样(采样率), 采样数据(即位深)用 16 位(2字节)记录, 双声道;
22050HZ 8bit mono: 每秒钟有 22050 次采样, 采样数据用 8 位(1字节)记录, 单声道;

每个采样数据记录的是振幅, 采样精度取决于储存空间的大小:

  • 1 字节(也就是8bit) 只能记录 256 个数, 也就是只能将振幅划分成 256 个等级;
  • 2 字节(也就是16bit) 可以细到 65536 个数, 这已是 CD 标准了;
  • 4 字节(也就是32bit) 能把振幅细分到 4294967296 个等级, 实在是没必要了;

如果是双声道(stereo), 采样就是双份的, 文件也差不多要大一倍。

人对频率的识别范围是 20HZ - 20000HZ, 如果每秒钟能对声音做 20000 个采样, 回放时就足可以满足人耳的需求。 所以 22050 的采样频率是常用的, 44100已是CD音质, 超过48000的采样对人耳已经没有意义。这一点在后面声音处理时会用到。

声音处理以及可视化

上面已经简单介绍了声音的基本知识,那么就来到我们的声音处理环节,可视化声音的波纹。首先引入我使用的第一个数据集:UrbanSound8K,可以点此处直接下载。如果使用的是服务器,可以使用wget下载解压,命令如下:

wget https://zenodo.org/record/1203745/files/UrbanSound8K.tar.gz

下载完成之后解压到指定文件夹:

 tar -zxvf UrbanSound8K.tar.gz -C /media/data/xxx/Speech_Recognition/

首先在Python中元数据的格式,其是一个csv文件,包含了所有声音文件的信息:

import pandas as pd

data = pd.read_csv("../Dataset/UrbanSound8K/metadata/UrbanSound8K.csv")
print(data)
print(data.head())
print(data.shape)

结果为:

         slice_file_name    fsID       start  ...  fold  classID             class
0       100032-3-0-0.wav  100032    0.000000  ...     5        3          dog_bark
1     100263-2-0-117.wav  100263   58.500000  ...     5        2  children_playing
2     100263-2-0-121.wav  100263   60.500000  ...     5        2  children_playing
3     100263-2-0-126.wav  100263   63.000000  ...     5        2  children_playing
4     100263-2-0-137.wav  100263   68.500000  ...     5        2  children_playing
...                  ...     ...         ...  ...   ...      ...               ...
8727     99812-1-2-0.wav   99812  159.522205  ...     7        1          car_horn
8728     99812-1-3-0.wav   99812  181.142431  ...     7        1          car_horn
8729     99812-1-4-0.wav   99812  242.691902  ...     7        1          car_horn
8730     99812-1-5-0.wav   99812  253.209850  ...     7        1          car_horn
8731     99812-1-6-0.wav   99812  332.289233  ...     7        1          car_horn

[8732 rows x 8 columns]
      slice_file_name    fsID  start  ...  fold  classID             class
0    100032-3-0-0.wav  100032    0.0  ...     5        3          dog_bark
1  100263-2-0-117.wav  100263   58.5  ...     5        2  children_playing
2  100263-2-0-121.wav  100263   60.5  ...     5        2  children_playing
3  100263-2-0-126.wav  100263   63.0  ...     5        2  children_playing
4  100263-2-0-137.wav  100263   68.5  ...     5        2  children_playing

[5 rows x 8 columns]
(8732, 8)
  • slice_file_name:音频文件的名称
  • fsID:摘录的录音的 FreesoundID
  • start:切片的开始时间
  • end:切片的结束时间
  • salience:声音的显着性等级。1 = 前景,2 = 背景
  • fold:此文件已分配到的折叠编号(1-10)
  • classID:
    0 = air_conditioner
    1 = car_horn
    2 = children_playing
    3 = dog_bark
    4 = drilling
    5 = engine_idling
    6 = gun_shot
    7 = jackhammer
    8 = siren
    9 = street_music
  • class:类别名称

接下来来看看数据的分布情况:

appended = []
for i in range(1, 11):
    appended.append(data[data.fold == i]['class'].value_counts())
class_distribution = pd.DataFrame(appended)
class_distribution = class_distribution.reset_index()
class_distribution['index'] = ["fold" + str(x) for x in range(1, 11)]
print(class_distribution)

结果为:

    index  jackhammer  children_playing  ...  siren  car_horn  gun_shot
0   fold1         120               100  ...     86        36        35
1   fold2         120               100  ...     91        42        35
2   fold3         120               100  ...    119        43        36
3   fold4         120               100  ...    166        59        38
4   fold5         120               100  ...     71        98        40
5   fold6          68               100  ...     74        28        46
6   fold7          76               100  ...     77        28        51
7   fold8          78               100  ...     80        30        30
8   fold9          82               100  ...     82        32        31
9  fold10          96               100  ...     83        33        32

通过最后两列可以发现,数据的分布是不平衡的。更直观的观察是每类的频率:

print(data['class'].value_counts(normalize=True))

其结果为:

[10 rows x 11 columns]
dog_bark            0.114521
children_playing    0.114521
street_music        0.114521
engine_idling       0.114521
drilling            0.114521
jackhammer          0.114521
air_conditioner     0.114521
siren               0.106390
car_horn            0.049130
gun_shot            0.042831
Name: class, dtype: float64

以上就是数据的具体信息,那么接下来就是对wav文件的详细分解。

首先可视化一个声音文件:

import os
import struct
import pandas as pd
from scipy.io import wavfile as wav
import matplotlib.pyplot as plt

data = pd.read_csv("../Dataset/UrbanSound8K/metadata/UrbanSound8K.csv")

def path_class(filename):
    excerpt = data[data['slice_file_name'] == filename]
    path_name = os.path.join('../Dataset/UrbanSound8K/audio', 'fold' + str(excerpt.fold.values[0]), filename)
    return path_name, excerpt['class'].values[0]


def wav_plotter(full_path, class_label):
    rate, wav_sample = wav.read(full_path)
    print(wav_sample)
    wave_file = open(full_path, "rb")
    riff_fmt = wave_file.read(36)
    bit_depth_string = riff_fmt[-2:]
    bit_depth = struct.unpack("H", bit_depth_string)[0]
    print('sampling rate: ', rate, 'Hz')
    print('bit depth: ', bit_depth)
    print('number of channels: ', wav_sample.shape[1])
    print('duration: ', wav_sample.shape[0] / rate, ' second')
    print('number of samples: ', len(wav_sample))
    print('class: ', class_label)
    plt.figure(figsize=(12, 4))
    plt.plot(wav_sample)
    plt.show()
    
fullpath, label = path_class('100263-2-0-117.wav')
wav_plotter(fullpath, label)

可视化结果:

在这里插入图片描述
那么从这幅图中我们可以看到采样数据大约17600个,而这段音频我已经知道大约四秒左右,也就是说这段音频的采样率应该是44100HZ,而振幅很显然是大于2000的,那么其位深就可以猜测为16bit(32bit很少),并且我们可以看到图中有两种颜色的图,也就对应了双声道,是一个立体声。

那么结果是否准确,我们可以通过上面的代码获得具体结果:

sampling rate:  44100 Hz
bit depth:  16
number of channels:  2
duration:  4.0  second
number of samples:  176400
class:  children_playing

但此处有一个问题,就是Urbansound8K有一个注释说“8732个WAV格式的城市声音音频文件。采样率、位深度和通道数与上传到 Freesound 的原始文件相同(因此可能因文件而异)。”这意味着数据中可能有许多不同的采样率。此外,不同的位深度意味着它们可以取不同的值范围。其中一些可能是立体声,而另一些则是单声道。这些都很难处理。

那么接下来我们就可以统计这个数据集的每种采样率,位深共包含多少声音文件,在统计之前,我们首先需要了解一下wav文件的组成, 一幅图概括:
在这里插入图片描述
那么从这幅图可以很清晰的看出声道数量,采样频率,位深(采样位数)所对应的字段,接下来就可以获取具体的信息:

def wav_fmt_parser(file_name):
    full_path, _ = path_class(file_name)
    wave_file = open(full_path, "rb")
    riff_fmt = wave_file.read(36) #只读取前36位信息
    n_channels_string = riff_fmt[22:24] # 通道数
    n_channels = struct.unpack("H", n_channels_string)[0]
    s_rate_string = riff_fmt[24:28] # 采样率
    s_rate = struct.unpack("I", s_rate_string)[0]
    bit_depth_string = riff_fmt[-2:] # 倒数两位即位深
    bit_depth = struct.unpack("H", bit_depth_string)[0]
    return n_channels, s_rate, bit_depth

接下来就是遍历所有声音文件进行信息的捕捉:

wav_fmt_data = [wav_fmt_parser(i) for i in data.slice_file_name]

这样获得的信息全在一个元组里面,数据格式为:

(2, 44100, 16), (2, 44100, 16), (2, 44100, 16), (2, 44100, 16), (2, 44100, 16)

将其利用pandas加入到data原始数据:

data[['n_channels', 'sampling_rate', 'bit_depth']] = pd.DataFrame(wav_fmt_data)

此时得到:

         slice_file_name    fsID  ...  sampling_rate  bit_depth
0       100032-3-0-0.wav  100032  ...          44100         16
1     100263-2-0-117.wav  100263  ...          44100         16
2     100263-2-0-121.wav  100263  ...          44100         16
3     100263-2-0-126.wav  100263  ...          44100         16
4     100263-2-0-137.wav  100263  ...          44100         16
...                  ...     ...  ...            ...        ...
8727     99812-1-2-0.wav   99812  ...          44100         16
8728     99812-1-3-0.wav   99812  ...          44100         16
8729     99812-1-4-0.wav   99812  ...          44100         16
8730     99812-1-5-0.wav   99812  ...          44100         16
8731     99812-1-6-0.wav   99812  ...          44100         16

注意看后几列,之后统计每个采样率的音频数量:

print(data.sampling_rate.value_counts())
print(data.n_channels.value_counts())
print(data.bit_depth.value_counts())

结果为:

[8732 rows x 11 columns]
44100     5370
48000     2502
96000      610
24000       82
16000       45
22050       44
11025       39
192000      17
8000        12
11024        7
32000        4
Name: sampling_rate, dtype: int64
2    7993
1     739
Name: n_channels, dtype: int64
16    5758
24    2753
32     169
8       43
4        9
Name: bit_depth, dtype: int64

可以发现数据的格式还是非常混乱的,如果不做归一化处理可能无法直接使用。那么接下来就是对数据进行转换以获得可以在同一情况下直接比对的数据。所幸万能的Python有着一种直接的方法对数据进行转换,名为:Librosa。

默认情况下,Librosa 的加载函数会将采样率转换为 22.05khz,并将通道数减少到 1(单声道),并对数据进行归一化,使值范围从 -1 到 1。

fullpath, class_name = path_class('100652-3-0-1.wav')
librosa_load, librosa_sampling_rate = librosa.load(fullpath)
print('converted sample rate:', librosa_sampling_rate)
print('converted wav file min~max range:', np.min(librosa_load), '~', np.max(librosa_load))

此时结果为:

converted sample rate: 22050
converted wav file min~max range: -0.7296108 ~ 0.74331266

而原始结果:

scipy_sampling_rate, scipy_load = wav.read(fullpath)
print('original sample rate:', scipy_sampling_rate)
print('original wav file min~max range:', np.min(scipy_load), '~', np.max(scipy_load))
original sample rate: 44100
original wav file min~max range: -30926 ~ 30119

我们可以将转换后的数据和原始数据的左右声道都绘制出来:

plt.subplot(3, 1, 1)
plt.plot(librosa_load)
plt.subplot(3, 1, 2)
plt.plot(scipy_load[:, 0])
plt.subplot(3, 1, 3)
plt.plot(scipy_load[:, 1])
plt.show()

可以看到结果图为:
在这里插入图片描述
从这幅图中我们可以粗略看出,该工具很好的将原始数据在不损失其质量的前提下转化成功。OK,本节结束,对此声音的特征提取和网络的分类可以见下节。

参考博客:

  1. https://www.cnblogs.com/ranson7zop/p/7657874.html
  2. https://blog.csdn.net/cindywry/article/details/108244610
  3. https://towardsdatascience.com/urban-sound-classification-part-2-sample-rate-conversion-librosa-ba7bc88f209a
  • 7
    点赞
  • 72
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码匀

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

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

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

打赏作者

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

抵扣说明:

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

余额充值