728x90

딥러닝을 이용하여 음성 인식, 음성 처리, 화자 인식, 감정 인식 등에서 많이 쓰이는 음성의 특징 추출 방법은 1.Mel-Spectrogram, 2. MFCC가 있다. 오늘은 Mel-Spectrogram에 대하여 어떻게 추출하여 쓸 수 있는지 적어보겠다.

 

사용할 라이브러리는 librosa 이다.

https://librosa.github.io/librosa/generated/librosa.feature.melspectrogram.html

 

librosa.feature.melspectrogram — librosa 0.6.3 documentation

Parameters: y : np.ndarray [shape=(n,)] or None audio time-series sr : number > 0 [scalar] sampling rate of y S : np.ndarray [shape=(d, t)] spectrogram n_fft : int > 0 [scalar] length of the FFT window hop_length : int > 0 [scalar] number of samples betwee

librosa.github.io

위 사이트에서 자세히 설명이 나와있다. 그대로 따라해도 되지만, 이왕 하는김에 조금 자세히 알아보자.

사이트의 doc 을 보면 아래처럼 설명이 있다.

 

Parameters:Returns:

y:np.ndarray [shape=(n,)] or None

audio time-series

sr:number > 0 [scalar]

sampling rate of y

S:np.ndarray [shape=(d, t)]

spectrogram

n_fft:int > 0 [scalar]

length of the FFT window

hop_length:int > 0 [scalar]

number of samples between successive frames. See librosa.core.stft

power:float > 0 [scalar]

Exponent for the magnitude melspectrogram. e.g., 1 for energy, 2 for power, etc.

kwargs:additional keyword arguments

Mel filter bank parameters. See librosa.filters.mel for details.

S:np.ndarray [shape=(n_mels, t)]

Mel spectrogram

1. y

Mel-Spectrogram을 뽑기 위해서는 librosa.load 로 음성 데이터를 load하여 얻은 y를 넣으면 된다. 이렇게 나머지를 지정하지 않고 추출하였을 경우 default 값으로 추출이된다.

2. sr

y, sr = librosa.load(음성데이터) 를 하게 될 경우, 음성의 sr을 얻을 수 있다. 단 여기에서 중요한 것은, sr을 따로 설정해주지 않으면 librosa는 default인 22050을 주게 된다. 나는 이러한 이유로 soundfile을 사용하여 처음 보는 음성 데이터에 대하여 sr을 체크한 뒤, 그대로 아래처럼 설정해서 sr을 얻는 방법을 사용한다.

y, sr = librosa.load(wav_file, sr=16000)

3. S

S는 librosa.stft(y)를 하면 얻을 수 있다. 즉 Short-Time Fourier Transform을 하여 얻어진 magnitude 와 phase의 값인 것이다. 그렇지만 S는 사용하지 않고 음성 데이터인 y만 넣어도 되므로 pass하겠다.

 

4. n_fft

우리가 보유한 음성 데이터는 현재 time-magnitude domain이다. 이걸 왜 frequency로 바꾸냐면, 주파수 관점에서 바라보았을 때 얻을 수 있는 정보가 많기 때문이다.

음성은 연속적인 신호이다. 그러므로 연속적인 신호를 우리는 frequency로 바꿔야 한다.

 

n_fft는 이 때 사용되는데, 음성의 길이를 얼마만큼으로 자를 것인가? 라고 이해하면 될 것 같다. 이를 window라고 부른다.

사람의 목소리는 대부분 16kHz안에 포함이 된다. 만약 n_fft를 512라고 가정해보겠다. 이렇게 될 경우 1개의 n_fft는 16000/512 = 약 31.25가 된다. 이것이 자연수로 떨어지기 위해 올림을 해주어 32로 설정해준다. 총 음성 데이터의 길이가 500이라고 가정하면, 32만큼 잘라서 1칸을 그리겠다는 것으로 이해하면 된다. 

보유한 음성 데이터의 Sampling Rate가 16000이고, 우리는 n_fft를 512개만 사용할 것이라면, 아래의 공식을 이용하여 구할 수 있다.

 

input_nfft = int(round(sr*frame_length))
frame_length = input_nfft/sr

2번 째 줄을 보면 되겠다.

 

frame_length = 512/16000 = 0.032 => 32ms가 나온다.

 

즉 1개의 window의 크기를 32ms로 잡으면 되고, 이것을 window_length 라고도 부른다.

 

5. hop_length

hop_length는 음성의 magnitude를 얼만큼 겹친 상태로 잘라서 칸으로 보여줄 것인가? 라고 생각하면 편할 것 같다. 즉 window_length - frame_stride 만큼 겹쳐서 1칸을 뽑겠다는 뜻이다. 

 

예를 들어 우리가 보유한 데이터가 7초이고, window_length를 0.025초, frame_stride를 0.010초(10ms)단위로 뽑는다고 가정하면, 1칸은 0.015초(15ms)가 겹치도록 하여 총 700칸을 얻을 수 있다.

 

6. 7은 넘어가도록 하겠다.

 

 

자 그럼 이제 실제로 코드를 돌려서 결과를 얻어 볼 것이다.

 

나는 window length 를 0.025로 두고, stride는 0.010로 진행하여 0.01초마다 frequency를 뽑을 것이다.

 

frame_length = 0.025
frame_stride = 0.010

def Mel_S(wav_file):
    # mel-spectrogram
    y, sr = librosa.load(wav_file, sr=16000)

    # wav_length = len(y)/sr
    input_nfft = int(round(sr*frame_length))
    input_stride = int(round(sr*frame_stride))

    S = librosa.feature.melspectrogram(y=y, n_mels=40, n_fft=input_nfft, hop_length=input_stride)
    
    print("Wav length: {}, Mel_S shape:{}".format(len(y)/sr,np.shape(S)))

 

아래처럼 함수를 선언하였다. 여기에서 중요한 것은, n_mels이다. 만약 16kHz를 Mel_S로 뽑는다면 약 8kHz에 해당하는 주파수를 얻을 수 있다. 이때, 8kHz를 n_mels 크기 만큼으로 나눠준다. 즉 1개의 n_mels의 height는 0.4kHz를 포함하고 있다. 

 

음성 데이터의 길이와 얻은 Mel_Spectrogram의 크기를 출력해보면 다음과 같다.

 

총 길이는 3.35초이고, 0.01 단위에서 올림을하여 3.36이 된다. 그리고 0.01에 1칸이므로 336칸을 얻는다.

 

이렇게 Mel_Spectrogram을 얻었고, 이것을 그려보도록 하겠다. 아래의 코드를 복사하면 된다.


    plt.figure(figsize=(10, 4))
    librosa.display.specshow(librosa.power_to_db(S, ref=np.max), y_axis='mel', sr=sr, hop_length=input_stride, x_axis='time')
    plt.colorbar(format='%+2.0f dB')
    plt.title('Mel-Spectrogram')
    plt.tight_layout()
    plt.savefig('Mel-Spectrogram example.png')
    plt.show()

얻을 Mel_Spectrogram 그림은 아래와 같다.

 

 

전체코드는 아래와 같다.

 

# -*- coding: utf-8 -*-
import librosa
import librosa.display
import numpy as np
import matplotlib.pyplot as plt

frame_length = 0.025
frame_stride = 0.010

def Mel_S(wav_file):
    # mel-spectrogram
    y, sr = librosa.load(wav_file, sr=16000)

    # wav_length = len(y)/sr
    input_nfft = int(round(sr*frame_length))
    input_stride = int(round(sr*frame_stride))

    S = librosa.feature.melspectrogram(y=y, n_mels=40, n_fft=input_nfft, hop_length=input_stride)

    print("Wav length: {}, Mel_S shape:{}".format(len(y)/sr,np.shape(S)))


    plt.figure(figsize=(10, 4))
    librosa.display.specshow(librosa.power_to_db(S, ref=np.max), y_axis='mel', sr=sr, hop_length=input_stride, x_axis='time')
    plt.colorbar(format='%+2.0f dB')
    plt.title('Mel-Spectrogram')
    plt.tight_layout()
    plt.savefig('Mel-Spectrogram example.png')
    plt.show()

    return S
man_original_data = '/mnt/junewoo/workspace/transform/test_wav/man_wav1.wav'
mel_spec = Mel_S(man_original_data)
728x90

+ Recent posts