FFT 와 DFT 성능 분석

알고리즘을 성능분석을 하는데는 여러가지 방법이 있겠지만 그중 시간복잡도를 이용해 성능을 분석해 볼 것이다.
앞서 말했듯이 DFT는 2중 반복문으로 구성되어있기 때문에 시간 복잡도가 O(N^2)가 나오는 반면,
FFT는 재귀로 함수를 계속 이분할하여 (Divide and Conquer) 시간복잡도가 O(logN)이 나오는 것 까지 알게되었다.
이번엔 직관적인 시간 측정을 통하여 성능이 어느 정도 차이나는지 확인해 볼 것이다.

source code
Python 소스코드 파일첨부 | 음원: NF - The search
run
전체 : 4.54s , FFT : 4.03 , DFT : immeasurable

Data, Moduls Loading and Config

Data Loading

import numpy as np 
import pygame as pg # pygame 이용해서 그래프 그림
import wave # 음원 분석 라이브러리
import time # 시간 측정 라이브러리
from FFT import * # 앞서 만든 FFT
from DFT import dft # 앞서 만든 DFT
# 음원 불러오기 (음원 파일 같이 첨부)
filename = "search.wav"

Basic Exploration

Read its basic properties

# WAV 파일을 'rb'(읽기전용) 모드로 실행
music = wave.open(filename, 'rb')

# 오디오가 모노인지 스테레오인지 확인한다
channels = music.getnchannels()

# 오디오의 샘플당 용량을 확인한다 (1비트 = 8비트, 2바이트 = 16비트)
width = music.getsampwidth()

# 오디오의 초당 샘플 갯수(샘플링 레이트)를 확인한다
rate = music.getframerate()

# 오디오파일의 전체 샘플 수 확인한다
frames = music.getnframes()

# 오디오를 한번에 불러올 샘플 개수 설정한다
chunk_size = 1024

주피터 노트북으로 확인결과

image

  • 오디오가 16비트이다 (output = 2)
  • 오디오가 스테레오타입이다 (output = 2)
  • 총 샘플 개수는 44100개이다.

The diffence betten Mono and Stereo

WAV 파일에서 MonoStereo 처리 과정에 중요하다

  • Mono : 좌우 구분 없음 (Channels = 1)
  • Stereo : 왼쪽(L), 오른쪽(R) 2개의 트랙으로 나뉜다 (Channels = 2)


ex> mono = [x0, x1, x2, x3, …] , stereo = [L0, R0, L1, R1, L2, R2, …]
만약 Stereo일 경우 바로 Fourier Transfrom을 할 경우 정상적인 값이 안나온다
따라서 한쪽 채널만 추출해서 처리한다 (x = x[::2])

Fourier Transfrom

Audio Preeprocsssing

# 10초 분량의 음원만 사용 (처리 오래걸림) 
duration_seconds = 10

# 10초 * 초당 샘플수 를 통해 읽을 샘플량을 저장
frames = rate * duration_seconds

# 10초 분량의 데이터를 읽는다(raw binary) -> 숫자아님
data = music.readframes(frames)

music.close()
# 음원이 16비트일 경우 (부호 존재)
if width == 2:
    # 부호의 존재로 인해 부호있는 정수형으로 바꾼다
    audio_data = np.frombuffer(data, dtype=np.int16)

# 음원이 8비트일 경우 (부호 없음)
elif width == 1:
    # 부호가 없기 때문에 부호 없는 정수 (uint8)로 바꾼다
    audio_data = np.frombuffer(data, dtype=np.uint8)
else:
    raise ValueError("파일 값 오류")

# 스테레오 일 경우 한쪽 음원만 사용한다
if channels == 2:
    audio_data = audio_data[::2]  # 2의 배수만 사용

# 전체 코드 시간 측정
start = time.time()

Discrete Fourier Transform

# 푸리에 변환 부분 따로 측정
ft_start = time.time()

dft_transfrom = dft(audio_data)

ft_end = time.time()

Fast Fourier Transform

ft_start = time.time()

fft_transfrom = FFT(audio_data)

ft_end = time.time()

Visualization

Normalization

# 일부 주파수만 시각화 한다
magnitude = np.abs(fft_transfrom)[:400]

# ft 절댓값이 너무 크면 가독성이 떨어지기 때문에 0~1 비율 맞춰줌
magnitude = magnitude / np.max(magnitude) * 180 

Drawing Audio Spectrum

pg.init()
WIDTH, HEIGHT = 800, 400
screen = pg.display.set_mode((WIDTH, HEIGHT))

screen.fill((20, 10, 30)) 
bar_color = (255, 102, 255) 

# 그래프는 화면 중앙에 상하 대칭으로 그린다
for i, mag in enumerate(magnitude):
    x = i * 2
    y_center = HEIGHT // 2
    pg.draw.rect(screen, bar_color, (x, y_center - mag, 1, mag))
    pg.draw.rect(screen, bar_color, (x, y_center, 1, mag))

pg.display.flip()
end = time.time()

print(end - start)
print(ft_end - ft_start)

running = True
while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False

pg.quit()

Output:
image


image

위 : 전체 코드 , 아래 : fft 시간

Conclusion

  • 위에 시간 결과값은 FFT알고리즘의 시간 측정이다
  • DFT는 시간을 1초까지 줄였는데도 오래걸려서 측정 불가
  • 샘플수가 40000개가 넘어가서 더 많이 차이나는 것으로 보임

Source Code

import numpy as np
import pygame as pg
import wave
import time
from FFT import *
from DFT import dft

filename = "search.wav"
music = wave.open(filename, 'rb')
channels = music.getnchannels()
width = music.getsampwidth()
rate = music.getframerate()
frames = music.getnframes()
chunk_size = 1024

duration_seconds = 10
frames = rate * duration_seconds
data = music.readframes(frames)
music.close()

if width == 2:
    audio_data = np.frombuffer(data, dtype=np.int16)

elif width == 1:
    audio_data = np.frombuffer(data, dtype=np.uint8)
else:
    raise ValueError("파일 값 오류")

if channels == 2:
    audio_data = audio_data[::2]

start = time.time()

ft_start = time.time()
fft_transfrom = FFT(audio_data)
ft_end = time.time()

magnitude = np.abs(fft_transfrom)[:400] 
magnitude = magnitude / np.max(magnitude) * 180

pg.init()
WIDTH, HEIGHT = 800, 400
screen = pg.display.set_mode((WIDTH, HEIGHT))

screen.fill((20, 10, 30)) 
bar_color = (255, 102, 255) 

for i, mag in enumerate(magnitude):
    x = i * 2
    y_center = HEIGHT // 2
    pg.draw.rect(screen, bar_color, (x, y_center - mag, 1, mag))  # 위
    pg.draw.rect(screen, bar_color, (x, y_center, 1, mag))        # 아래

pg.display.flip()
end = time.time()

print(end - start)
print(ft_end - ft_start)

running = True
while running:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False

pg.quit()

추가 : 실시간 오디오 스펙트럼

음악 전체의 오디오 스펙트럼을 표현하는 것 뿐만 아닌 실시간으로 표현할 수 있다

Data Loading

import pygame as pg
import numpy as np
import wave
import sys
from FFT import *
from DFT import *
filename = "search.wav"
music = wave.open(filename, 'rb')
channels = music.getnchannels()
width = music.getsampwidth()
rate = music.getframerate()
frames = music.getnframes()
chunk_size = 1024

Play a audio file

pg.mixer.init(frequency=rate)
pg.mixer.music.load(filename)
pg.mixer.music.play()

Pygame init

pg.init()
screen = pg.display.set_mode((800, 400))
clock = pg.time.Clock()

Audio preprocessing

running = True
while running and music.tell() < frames:
    data = music.readframes(chunk_size)
    if width == 2:
        audio_data = np.frombuffer(data, dtype=np.int16)
    else:
        audio_data = np.frombuffer(data, dtype=np.uint8)
    if channels == 2:
        audio_data = audio_data[::2] 


  • 음악이 실행되고 프레임이 끝날때 까지 반복

Fourier Transfrom

    fft_result = FFT(audio_data)
    magnitude = np.abs(fft_result)[:200]
    if np.max(magnitude) > 0:
        magnitude = magnitude / np.max(magnitude) * 300 

Visualization

    screen.fill((20, 10, 30)) 
    bar_color = (255, 102, 255)
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False

    for i, mag in enumerate(magnitude):
        x = i * 4
        y_center = 200 
        height = mag

        pg.draw.rect(screen, bar_color, (x, y_center - height, 3, height))
        pg.draw.rect(screen, bar_color, (x, y_center, 3, height))

    pg.display.flip()
    clock.tick(60)

music.close()
pg.quit()

전체 코드

import pygame as pg
import numpy as np
import wave
import sys
from FFT import *
from DFT import *

filename = "search.wav"
music = wave.open(filename, 'rb')
channels = music.getnchannels()
width = music.getsampwidth()
rate = music.getframerate()
frames = music.getnframes()
chunk_size = 1024

pg.mixer.init(frequency=rate)
pg.mixer.music.load(filename)
pg.mixer.music.play()

pg.init()
screen = pg.display.set_mode((800, 400))
clock = pg.time.Clock()

running = True
while running and music.tell() < frames:
    data = music.readframes(chunk_size)
    if width == 2:
        audio_data = np.frombuffer(data, dtype=np.int16)
    else:
        audio_data = np.frombuffer(data, dtype=np.uint8)
    if channels == 2:
        audio_data = audio_data[::2]

    fft_result = FFT(audio_data)
    magnitude = np.abs(fft_result)[:200]
    if np.max(magnitude) > 0:
        magnitude = magnitude / np.max(magnitude) * 300 


    screen.fill((20, 10, 30))
    bar_color = (255, 102, 255)
    for event in pg.event.get():
        if event.type == pg.QUIT:
            running = False

    for i, mag in enumerate(magnitude):
        x = i * 4
        y_center = 200
        height = mag

        pg.draw.rect(screen, bar_color, (x, y_center - height, 3, height))
        pg.draw.rect(screen, bar_color, (x, y_center, 3, height))

    pg.display.flip()
    clock.tick(60)

music.close()
pg.quit()