728x90

잘 정제된 음성 데이터셋의 경우 (특히 딥러닝 음성인식 및 오디오 처리 학습용의 경우) 거의 Mono channel로 공개가 되어있다.

 

하지만 다양한 실환경에서 녹음된 오디오 데이터셋의 경우, 녹음 기기에 따라 2이상의 channel로 구성된, Stereo channel로 공개가 된 데이터들이 많다.

 

간단하게 아래와 같이 음성을 로드하게 되면

import torchaudio
import torch

waveform = './test.wav'
y, sr = torchaudio.load(waveform)

 

y의 size를 출력해보면, 예를 들어, (2, 100) 과 같이 나올 수가 있다.

 

이를 Stereo channel이라고 한다.

 

간단하게 아래의 그림을 보면 이해가 쉽게 될 것 같은데, stereo는 채널 별 들리는 소리가 약간 다른 반면, mono는 stereo의 평균을 낸 것으로 간주할 수 있다.

https://soundskrit.ca/2023/06/26/stereo-sound-vs-mono-explained/

 

우리가 해결하고자 하는건, 어쨋든 n-channel 이상의 waveform에 대해 평균을 내주어 mono channel로 만드는 것으로 간주할 수 있다. 

 

아래와 같이 해결하면 된다.

 

import torchaudio
import torch

waveform = './test.wav'
y, sr = torchaudio.load(waveform)
waveform_mono = torch.mean(y, dim=0).unsqueeze(0)

 

위와 같이 진행하면 waveform_mono는 (1, 100)의 형태로 얻어질 수 있다.

728x90
728x90

LibriSpeech, Libri-Light, Multilingual LibriSpeech (MLS) 등...

 

나 같은 경우는 윈도우에서 다운받아서 그것을 작업 서버로 옮겨야하는,

 

굉장히 비효율적인 그런 행동을 자주 하곤 했다.

 

국내 AIHUB 데이터셋은 이런식으로 다운받아야해서 얼마나 짜증나는지 모르겠다. 

 

게다가 연구실에 대용량 데이터셋 다운받게 되면 전체적으로 인터넷도 느려지고,

 

MobaXterm으로 옮기다 보면 도중에 죽는 경우도 많아서 정말 짜증나는 일이다.

 

오늘 예시로 해볼것은 우분투에서 wget을 사용하여 LibriSpeech (http://www.openslr.org/12) 의 dev-clean.tar.gz 을 다운받아보겠다.

 

아래는 해당 사이트 그림인데

 

 

여기에서 왼쪽 최하단의 부분을 보면 주소가 제공된다.

 

 

https://www.openslr.org/resources/12/dev-clean.tar.gz 

 

그다음 wget https://www.openslr.org/resources/12/dev-clean.tar.gz 하면 끝난다

 

우분투 내 실행화면은 아래와 같다

 

 

음 정말 간단하다

 

이렇게 하면 3TB 짜리 Libri-Light 받는 것도 부담 없겠다.

 

AIHUB 데이터셋은 특정 프로그램을 이용해서 다운받아야 되는데 정말 짜증나는 일이다. 

 

얼른 고쳐주면 좋겠다.

728x90
728x90

음성 인식 테스크에서 pcm 파일을 direct로 읽어야 할 때가 있는데, 이 때 두 가지의 코드를 주로 사용한다.

 

오늘은 이에 대해 생각 정리 겸 실험 내용을 저장하기 위해 작성한다.

 

1. numpy memmap으로 읽을 경우

 

이 때에는, 읽은 pcm 파일을 그대로 저장하게 되면 음성의 magnitude가 굉장히 크기 때문에 max value인 32767로 나눠주어야 한다.

 

signal = np.memmap(sample_data, dtype='h', mode='r').astype('float32')

signal = signal / 32767

 

2. numpy frombuffer + librosa로 읽을 경우

 

1번과는 다르게 따로 나눠줄 필요는 없다.

 

with open(sample_data, 'rb') as opened_pcm_file:
    buf = opened_pcm_file.read()
    pcm_data = np.frombuffer(buf, dtype = 'int16')
    wav_data = librosa.util.buf_to_float(pcm_data, 2)

 

3. 음질 결과 비교 

 

1번:

method1_16000.wav
0.07MB

 

2번: 

method2_16000.wav
0.07MB

 

 

1번의 32767로 normalized 한 파일과 2번 방식으로 읽은 파일은 서로 데이터의 크기가 같다.

 

4. 변환 속도 비교

1번과 2번의 시간 비교는 아래와 같다.

2번이 약 2배 이상 빠른것을 알 수 있다.

 

 

5. 결론

두 방법 모두 가능하지만, 대규모 데이터셋을 처리하기 위해서는 2번 방법을 사용하는 것이 좋을 것 같다.

 

예를 들어 KoSpeech 같은 경우 각 음성 파일의 길이 비교 없이 training set 62만개에 대해서 10에폭을 돌린다고 하면

 

1번 방법: 620,000 * 10 * 0.00054 = 3,348 = 0.93시간

2번 방법: 620,000 * 10 * 0.00026 = 1,612 = 0.44시간

 

의 차이가 data loading 할 때 발생한다.

 

 

전체 코드

 

import soundfile as sf
import os
import numpy as np
import time

sample_data = './45.pcm'
data_dir = './check_pcm/'

start = time.time()
signal = np.memmap(sample_data, dtype='h', mode='r').astype('float32')
print(signal.shape)

signal = signal / 32767
print('method1 takes {}'.format(time.time() - start))


sf.write(os.path.join(data_dir, 'method1_16000.wav'), signal, 16000, format='WAV', endian='LITTLE', subtype='PCM_16')

import librosa
start = time.time()
with open(sample_data, 'rb') as opened_pcm_file:
    buf = opened_pcm_file.read()
    pcm_data = np.frombuffer(buf, dtype = 'int16')
    wav_data = librosa.util.buf_to_float(pcm_data, 2)
print('method2 takes {}'.format(time.time() - start))    

sf.write(os.path.join(data_dir, 'method2_16000.wav'), wav_data, 16000, format='WAV', endian='LITTLE', subtype='PCM_16')

 

 

pcm 파일의 sampling rate는 어떻게 구할 수 있을까? 다음번에는 이 문제와 관련된 issue 정리해보기

728x90
728x90

오늘 포스트 할 내용은 flac 데이터셋을 wav로 변환하여 진행하는 것이다.

 

일반적으로 음성 처리 분야에서 22.5kHz, 16kHz를 많이 사용하는데,

 

TIDIGITS 데이터셋 같은 경우 20kHz로 되어 있다.

 

또한 flac 확장자로 되어 있는데, 이번 포스팅의 목표는 1) convert flac to wav, 2) downsampling 20kHz -> 16kHz 이다.

 

 

먼저 저장할 경로를 설정해주고, flac 음성을 불러온다.

그 뒤 음성의 path와 file id를 얻는다.

file id 는 file.flac 으로 되어 있기 때문에 [:-5]로 name만 얻어오는 작업을 하고,

soundfile 의 read 함수를 이용하여 data부분을 get한다.

 

그 뒤에 1단계인 flac -> wav 로 저장을 진행한다.

 

이 데이터셋은 20000Hz이기 때문에 2번째로 해야 할 일은 librosa로 wav를 read 한 뒤 16000으로 downsampling을 진행한다.

 

전체 코드는 아래와 같다.

import soundfile as sf
import librosa
import os
save_dir = '/mnt/junewoo/speech/TIDIGITS/tidigits_flac/' # save dir

w_flac = '/mnt/junewoo/speech/TIDIGITS/tidigits_flac/data/adults/train/woman/sp/o31oa.flac' # read file
m_flac = '/mnt/junewoo/speech/TIDIGITS/tidigits_flac/data/adults/train/man/st/ooo5a.flac'

_, w_id = os.path.split(w_flac) # get dir, file name for saving
_, m_id = os.path.split(m_flac)

w_id = w_id[:-5] # change name o31oa.flac -> o31oa
m_id = m_id[:-5] # change name ooo5a.flac -> ooo5a

w_data, w_sr = sf.read(w_flac) # data, sampling rate divide
m_data, m_sr = sf.read(m_flac)


sf.write(save_dir + w_id + '.wav', w_data, w_sr, format='WAV', endian='LITTLE', subtype='PCM_16') # file write for wav
sf.write(save_dir + m_id + '.wav', m_data, m_sr, format='WAV', endian='LITTLE', subtype='PCM_16')

w, sr = librosa.load(save_dir+w_id+'.wav', sr=20000)
m, _ = librosa.load(save_dir+m_id+'.wav', sr=20000)

g_sr = 16000 # goal sampling rate

w_resample = librosa.resample(w, sr, g_sr)
m_resample = librosa.resample(m, sr, g_sr)

sf.write(save_dir + w_id + '_resample16k.wav', w_resample, g_sr, format='WAV', endian='LITTLE', subtype='PCM_16') # file write for wav
sf.write(save_dir + m_id + '_resample16k.wav', m_resample, g_sr, format='WAV', endian='LITTLE', subtype='PCM_16')

 

데이터 크기는 아래의 사진과 같다.

 

o31oa.wav 의 size는 73인데, 20kHz 에서 16kHz로 즉 20%만큼 downsampling을 해버렸기 때문에

73 * 0.8  = 58.4 ==> 58kB의 크기를 갖는다.

 

그러나 실제로 음성을 들어보면 큰 차이가 없음을 알 수 있다.

728x90
728x90

오늘은 Matlab에서의 Stft(short time fourier transform)와 python library인 librosa의 stft의 dimension 결과 및 각 vector값의 차이에 대해서 알아보겠다.

 

실제로 나는 Matlab에서 DTW 알고리즘으로 테스트할 것이 있어서 stft로 변환해본 뒤 python 결과와 비교해보았는데 dimension 크기가 달랐었다. 그리하여 그 이유를 정리하여 여기에 올리겠다.

 

우선 Matlab에서 spectrogram 을 변환하기 위한 코드는 아래와 같다.

 

>> [y, Fs] = audioread("filename1.wav");
>> [x, Fs] = audioread("filename2.wav");

 

음성은 16kHz 이고, nfft=512, hop=256으로 진행하겠다 ==> frame length를 32ms, 겹치는 hop을 16ms로 진행

 

>> s1 = spectrogram(x, 512, 256);
>> s2 = spectrogram(y, 512, 256);

 

matlab data 결과

 

첨부한 그림처럼,

s1: (257, 271)

s2: (257, 256) 으로, s1이 s2보다 길이가 크다고 볼 수 있다.

 

이번에는 python에서 librosa를 사용해서 진행하겠다.

 

nfft=512

hop_length=256

wavfile_male = '경로'

wavfile_female = '경로'

male_stft = librosa.stft(y_male, n_fft=nfft, hop_length=hop_length)
female_stft = librosa.stft(y_female, n_fft=nfft, hop_length=hop_length)
print("male_stft shape {} female_stft shape {}".format(np.shape(male_stft), np.shape(female_stft)))

 

첨부한 그림처럼

male(s1): 257, 273

female(s2): 257, 258

으로 나온다. s1이 s2보다 크다.

 

근데 여기서 차원이 다르다.

 

정확히 말하면 matlab 결과물이 python librosa 결과물보다 크다. 왜 그럴까?

 

계속 찾던 도중, 잘 설명해주는 글을 발견했다.

https://stackoverflow.com/questions/55474581/difference-between-output-of-python-librosa-core-stft-and-matlab-spectrogramx

 

요약하자면, matlab은 spectrogram의 마지막 window를 생략하면서, 각 window 가운데에 해당하는 시간 벡터를 출력한다. 예를 들어 3샘플 윈도우와 1개의 겸칩(overlap) 샘플을 갖는 10개의 샘플 길이의 신호는 다음과 같은 4개의 윈도우를 생성한다.

1:3

3:5

5:7

7:9,

m:n은 n번째 샘플을 포함하여 m에서 n까지의 샘플을 포함하는 윈도우를 나타낸다. 그러므로 window의 center가 2,4,6,8이다.

matlab은 (윈도우 크기-1) * 홉 길이 + 윈도우 사이즈 <= 샘플들의 개수로 되는 최대의 윈도우 크기를 필요로 하기 때문이다.

 

 

반면 python librosa stft 각 프레임의 첫 번째 샘플이 시간이고, 프레임은 입력 신호보다 많은 부분을 다룬다.

 

위의 matlab의 공식에 반해, librosa는

윈도우의 개수 * 홉 길이 > 샘플개수에 되는 최소한의 윈도우 크기를 요구한다.

 

정리

- MATLAB은 최대한의 윈도우 크기 요구

- Librosa는 최소한의 윈도우 크기 요구

 

그럼 MATLAB과 동일한 차원을 갖고 싶다면?

librosa.stft(y, nfft=, hop=, center=False)

위의 코드처럼 맨 뒤에 center=False를 해주면 된다.

 

자세한 내용은 librosa 공식 사이트를 확인해보자 

https://librosa.github.io/librosa/generated/librosa.core.stft.html

 

center:boolean

  • If True, the signal y is padded so that frame D[:, t] is centered at y[t * hop_length].
  • If False, then D[:, t] begins at y[t * hop_length]

위의 center=False로 돌려보면,

(257, 271) (257, 256) 으로 Matlab과 동일한 차원을 갖는 Spectrogram이 나오게 된다.

 

하지만 Matlab과 librosa의 각 spectrogram의 벡터값들이 모두 같진 않다.

얼핏 보기엔 유사하지만 같지 않다.

아무래도 각 팀들에서 Nomalize등의 기법을 다르게 하여 그런 것 같다.

 

 

 

Matlab 코드

[y, Fs] = audioread("filename1.wav"); #여자
[x, Fs] = audioread("filename2.wav"); #남자

s1 = spectrogram(x, 512, 256); 
s2 = spectrogram(y, 512, 256);

Python 코드

import numpy as np

nfft=512
hop_length=256
male_name = 'masch0'
female_name = 'fekmh0'
import pickle
import scipy
from scipy.spatial.distance import euclidean

wavfile_female = '/mnt/junewoo/speech/KAIST_wav/new_Korean_Voice_DB/public/falhj0/PBSG001.wav'

wavfile_male = '/mnt/junewoo/speech/KAIST_wav/new_Korean_Voice_DB/public/masch0/PBSG001.wav'

y_female, sr = librosa.load(wavfile_female, sr=16000)
y_male, sr = librosa.load(wavfile_male, sr=16000)
male_stft = librosa.stft(y_male, n_fft=nfft, hop_length=hop_length, center=False)
female_stft = librosa.stft(y_female, n_fft=nfft, hop_length=hop_length, center=False)
print("male_stft shape {} female_stft shape {}".format(np.shape(male_stft), np.shape(female_stft)))
print('Done')
728x90
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
728x90

음성 처리를 하다 보면 음성 데이터를 Down Sampling 하거나, Up sampling 할 경우가 있다.

예를 들어 보유한 데이터의 Sampling rate가 44.1kHz 인데, n_fft가 너무 크기 때문에 모든 것을 다 함축하여 Mel scale log spectrogram으로 담지 못할 경우가 있다.

이럴 때 자주 사용하는 것이 Down sampling이라고 한다.

오늘은 librosa 라이브러리를 이용하여 down sampling을 해보겠다.
우선 librosa 라이브러리를 설치해야 한다.

설치하였다고 가정하고,  함수를 하나 만들어서 진행하겠다. 함수 이름은 def down_sample이다.

def down_sample(input_wav, origin_sr, resample_sr):
    y, sr = librosa.load(input_wav, sr=origin_sr)
    resample = librosa.resample(y, sr, resample_sr)
    print("original wav sr: {}, original wav shape: {}, resample wav sr: {}, resmaple shape: {}".format(origin_sr, y.shape, resample_sr, resample.shape))

이 함수에서는 wav 파일과 기존 original sr, resample 할 sr을 받고 있다.

보유한 데이터의 음성은 16kHz이므로, original_sr = 16000, resample_sr = 8000으로 진행한다.

 

음성 처리에 있어서 librosa 라이브러리가 정말 잘 지원해주고 있다. resample을 하기 위해서는 3번째 줄의 resample = librosa.resample(y, sr, resample_sr)을 해주면 된다.

 

그 아래의 print 한 결과는 다음과 같다.

 

즉 기존의 y data는 53720이고, 그의 절반으로 하였기 때문에 26860으로 출력이 된다.

 

이렇게만 보면 이해하기가 어렵다. 아래의 코드를 활용하여 직접 그림을 그려보자.

 

    plt.figure(figsize=(10, 4)) # 10,4 크기로 그림
    plt.subplot(2, 1, 1) # 2개를 그릴건데, (총 개수, 가로, 세로) 순서로 그림
    time1 = np.linspace(0, len(y) / sr, len(y)) # 음성의 데이터를 이용하여 음성 총 길이를 구함
    plt.plot(time1, y) # 음성 flot
    plt.title('Original Wav')

    plt.subplot(2, 1, 2)
    time2 = np.linspace(0, len(resample) / resample_sr, len(resample))
    plt.plot(time2, resample)
    plt.title('Resampled Wav')

    plt.tight_layout()
    plt.savefig('compare_16k_vs_8k.png') # 저장

위의 코드에 자세히 주석을 달아놓았다. 위의 코드처럼 입력을 하면 다음의 그림을 얻을 수 있다.

 

 

코드에서 subplot은 2,1,1 -> 총 2개의 사진 중 맨 위를, 2,1,2 -> 총 2개의 사진 중 맨 아래에 그림을 그리겠다는 뜻이다.

 

육안으로 보았을 때 magnitude나 phase의 변화는 없다. 다만 자세히 들여다보면 data의 magnitude가 2개 중 1개씩 비어있다. 즉, 데이터가 총 100개면, resample 하였을 경우 (1/2로 하였기 때문에) 50개의 데이터만 남아있다. 이때, 데이터의 위치는 변화하지 않고 중간중간만 비어지는 것이다.

 

이제 음성 데이터를 저장하여 들어보겠다.

 

    sf.write('./' + '16k.wav' , y, origin_sr, format='WAV', endian='LITTLE', subtype='PCM_16')
    sf.write('./' + '8k.wav', resample, resample_sr, format='WAV', endian='LITTLE', subtype='PCM_16')

 

아래 글에 작성한 것처럼, sf.write를 사용하여 현재 directory에서 16k.wav 라고 저장하는 코드이다. 

 

실제로 음성 첨부를 하여 들어보면,

 

 

 

8kHz

 

16kHz

 

육안으로 들었을 때, 두 데이터의 차이는 크지 않다.

 

저장되는 크기는 어떨까? 예상한 것처럼, 절반으로 resampling 하였기 때문에 데이터의 크기 또한 절반으로 reduce 된다.

 

 

전체 코드는 아래와 같다.

import librosa
import numpy as np
import soundfile as sf
import matplotlib.pyplot as plt

def down_sample(input_wav, origin_sr, resample_sr):
    y, sr = librosa.load(input_wav, sr=origin_sr)
    resample = librosa.resample(y, sr, resample_sr)
    print("original wav sr: {}, original wav shape: {}, resample wav sr: {}, resmaple shape: {}".format(origin_sr, y.shape, resample_sr, resample.shape))

    plt.figure(figsize=(10, 4))
    plt.subplot(2, 1, 1)
    time1 = np.linspace(0, len(y) / sr, len(y))
    plt.plot(time1, y)
    plt.title('Original Wav')

    plt.subplot(2, 1, 2)
    time2 = np.linspace(0, len(resample) / resample_sr, len(resample))
    plt.plot(time2, resample)
    plt.title('Resampled Wav')

    plt.tight_layout()
    plt.savefig('compare_16k_vs_8k.png')

    sf.write('./' + '16k.wav' , y, origin_sr, format='WAV', endian='LITTLE', subtype='PCM_16')
    sf.write('./' + '8k.wav', resample, resample_sr, format='WAV', endian='LITTLE', subtype='PCM_16')

man_original_data = '/mnt/junewoo/workspace/transform/test_wav/man_wav1.wav'
down_sample(man_original_data, 16000, 8000)

 

728x90
728x90

음성 데이터를 resampling 해야 할 경우가 있다. 


예를 들어 44.1kHz 에서 16000으로 변경을 해준다던지, 16000에서 22050으로 변경하여 음성 처리를 해줄 때가 있다.(Downsampling)


python에서 librosa 라이브러리를 이용하여 resampling을 간단히 해줄 수 있는데, 이 때 우분투에서 작업하던 음성 파일이 resampling 되고나서 librosa를 이용하여 그대로 저장을 하고 윈도우에서 그 음성을 다운받아서 확인해볼 때 읽히지 않을때가 있다.


결론적으로 말하자면, librosa 로 resample 이후에 soundfile의 write를 사용하여 저장하면 이 문제를 해결할 수 있다.



librosa.output.write_wav(output_dir+'.wav', data, sr) # 깨지는 현상 존재
sf.write(output_dir, data, sr, format='WAV', endian='LITTLE', subtype='PCM_16') # 깨지지 않음


위의 soundfile write내의 내용을 librosa write_wav의 내용을 그대로 넣어주면 된다.


728x90
728x90

음성 데이터(Speech, Sound 등)를 python을 이용하여 처리하여(sampling rate나 일정 부분을 자는 것 등) 저장하는 것을 해보려 한다.


사용하는 라이브러리는 librosa


우선 음성을 저장된 디렉토리로부터 불러온다. 

아래의 코드를 사용하여 불러오면 file_dir와 file_id가 출력이 된다.

wav = '/data/dataset/IEMOCAP_wavonly/IEMOCAP_Wavonly/Wav/Ses01F_impro01/Ses01F_impro01_F005.wav'
(file_dir, file_id) = os.path.split(wav)
print("file_dir:", file_dir)
print("file_id:", file_id)

- 결과

file_dir: /data/dataset/IEMOCAP_wavonly/IEMOCAP_Wavonly/Wav/Ses01F_impro01

file_id: Ses01F_impro01_F005.wav

Process finished with exit code 0



그 다음 이 음성 파일을 librosa로 불러올 것이다.

librosa는 별도로 sr(sampling rate)를 설정하지 않으면 default sr이 22500으로 되어있다.

내가 불러올 음성 파일은 이미 16000 sr이므로 반드시 sr=16000을 붙여주도록 한다.


y, sr = librosa.load(wav, sr=16000)
time = np.linspace(0, len(y)/sr, len(y)) # time axis

fig, ax1 = plt.subplots() # plot
ax1.plot(time, y, color = 'b', label='speech waveform')
ax1.set_ylabel("Amplitude") # y 축
ax1.set_xlabel("Time [s]") # x 축
plt.title(file_id) # 제목
plt.savefig(file_id+'.png')
plt.show()

이 음성 파일을 plot하여 그린 그림은 아래와 같다.




약 4초정도의 길이를 갖는 음성파일이다.


이 음성 파일의 절반만을 가져오고 다시 저장하는 단계를 해보겠다.


우선 아래의 코드를 통해 음성 신호를 절반만을 가져오고, 절반을 그려보도록 하자.


half = len(y)/2
y2 = y[:round(half)]
time2 = np.linspace(0, len(y2)/sr, len(y2))
fig2, ax2 = plt.subplots()
ax2.plot(time2, y2, color = 'b', label='speech waveform')
ax1.set_ylabel("Amplitude") # y 축
ax1.set_xlabel("Time [s]") # x 축
plt.title('cut '+file_id)
plt.savefig('cut_half '+file_id+'.png')
plt.show()


이 부분의 결과는 아래의 그림과 같다. 약 절반정도가 잘렸다.




이제 이 음성을 저장할건데, librosa 라이브러리를 사용하여 쉽게 저장할 수 있다. 


librosa.output.write_wav('cut_file.wav', y2, sr)


아래에서 기존 음성과 자른 부분의 음성을 비교해볼 수 있다.


Original 음성:  




절반 Cut 한 음성: 




전체 코드

import librosa
import os
import numpy as np
import matplotlib.pyplot as plt


wav = '/data/dataset/IEMOCAP_wavonly/IEMOCAP_Wavonly/Wav/Ses01F_impro01/Ses01F_impro01_F005.wav'
(file_dir, file_id) = os.path.split(wav)
print("file_dir:", file_dir)
print("file_id:", file_id)

# original
y, sr = librosa.load(wav, sr=16000)
time = np.linspace(0, len(y)/sr, len(y)) # time axis
fig, ax1 = plt.subplots() # plot
ax1.plot(time, y, color = 'b', label='speech waveform')
ax1.set_ylabel("Amplitude") # y 축
ax1.set_xlabel("Time [s]") # x 축
plt.title(file_id) # 제목
plt.savefig(file_id+'.png')
plt.show()
librosa.output.write_wav('original_file.mp3', y, sr) # original wav to save mp3 file

# cut half and save
half = len(y)/2
y2 = y[:round(half)]
time2 = np.linspace(0, len(y2)/sr, len(y2))
fig2, ax2 = plt.subplots()
ax2.plot(time2, y2, color = 'b', label='speech waveform')
ax1.set_ylabel("Amplitude") # y 축
ax1.set_xlabel("Time [s]") # x 축
plt.title('cut '+file_id)
plt.savefig('cut_half '+file_id+'.png')
plt.show()
librosa.output.write_wav('cut_file.mp3', y2, sr) # save half-cut file



728x90

+ Recent posts