요약하자면, 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 각 프레임의 첫 번째 샘플이 시간이고, 프레임은 입력 신호보다 많은 부분을 다룬다.
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개만 사용할 것이라면, 아래의 공식을 이용하여 구할 수 있다.
아래처럼 함수를 선언하였다. 여기에서 중요한 것은, 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을 얻었고, 이것을 그려보도록 하겠다. 아래의 코드를 복사하면 된다.
이 함수에서는 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개의 데이터만 남아있다. 이때, 데이터의 위치는 변화하지 않고 중간중간만 비어지는 것이다.
예를 들어 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의 내용을 그대로 넣어주면 된다.
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()