줄리아에서 이산 푸리에 변환 행렬 구현하기
説明
$N$次元ベクトル$\mathbf{x}$の離散フーリエ変換は以下のような行列積として表される。
$$ \widehat{\mathbf{x}} = F \mathbf{x} $$
このとき$F$は、$\omega = e^{-i2\pi/N}$と表すと、
$$ F = \begin{bmatrix} 1 & 1 & 1 & \cdots & 1 \\ 1 & \omega & \omega^2 & \cdots & \omega^{N-1} \\ 1 & \omega^2 & \omega^4 & \cdots & \omega^{2(N-1)} \\ \vdots & \vdots & \vdots & \ddots & \vdots \\ 1 & \omega^{N-1} & \omega^{2(N-1)} & \cdots & \omega^{(N-1)^{2}} \end{bmatrix} $$
逆変換は$F^{-1} = \dfrac{1}{N} \overline{F}$で表される。このとき$\overline{F}$は$F$の共役複素行列である。
実装
1D 離散フーリエ変換
Juliaでは以下のように実装することができる。
function dft_matrix(n)
ω = exp(-2π*im/n)
F = [ω^(i*j) for i in 0:n-1, j in 0:n-1]
return F
end
もちろん次のように一行で書いても良い。
function dft_matrix(n)
return [exp(2π *im*i*j/n) for i in 0:n-1, j in 0:n-1]
end
高速フーリエ変換パッケージであるFFTWを読み込んで確認してみると次のようになる。
using FFTW
using Plots
F = dft_matrix(128)
x = rand(128)
ξ = fft(x)
η = F*x
plot(
plot(abs.(ξ), label="FFTW"),
plot(abs.(η), label="DFT matrix"),
plot(abs.(ξ .- η), label="Difference"),
layout=(3,1)
)
2D 離散フーリエ変換
2次元変換を実装するために画像を一つ読み込もう。
using Tomography
p = phantom(128, 2)
heatmap(p, yflip=true, c=:viridis)
FFTWと比較してみると次のようになる。
F = dft_matrix(256)
ξ = fft(p)
η = F*p*transpose(F)
plot(
heatmap(abs.(ξ), yflip=true, title="FFTW"),
heatmap(abs.(η), yflip=true, aspect_ratio=1, title="DFT matrix"),
heatmap(abs.(ξ .- η), yflip=true, aspect_ratio=1, title="Difference"),
layout=(1,3)
)
逆変換
逆変換は変換行列の共役複素行列であるため、次のように実装できる。
function dft_inverse(F)
n = size(F, 1)
return conj(F) / n
end
F = dft_matrix(128)
x = rand(128)
F⁻¹ = dft_inverse(F)
plot(
plot(x, label="Original signal x"),
plot(real(F⁻¹*F*x), label="Reconstructed signal F⁻¹*F*x"),
plot(abs.(x .- F⁻¹*F*x), label="Difference"),
layout = (3,1)
)
2次元の場合も同様に、
p = phantom(256, 2)
F = dft_matrix(256)
F⁻¹ = dft_inverse(F)
p_reconstructed = F⁻¹*F*p*transpose(F)*transpose(F⁻¹)
plot(
heatmap(p, yflip=true, title="Original image"),
heatmap(real(p_reconstructed), yflip=true, title="Reconstructed image"),
heatmap(abs.(p .- real(p_reconstructed)), yflip=true, title="Difference", clims=(0, 1e-10)),
layout=(1,3)
)
fftshift
離散フーリエ変換は基本的に周波数が$0$である成分が最初に出る。
フーリエ変換に関連するパッケージでは$0$周波数成分が中央に来るように変更する関数が存在するが(多くはfftshift
という名前を持つ)、これを実装してみよう。
前の半分の成分と後の半分の成分を入れ替えれば良いので、次のように簡単に実装することができる。
Fs = 1000 # 진동수
T = 1/1000 # 샘플링 간격
L = 1000 # 신호의 길이
x = [i for i in 0:L-1].*T # 신호의 도메인
F = dft_matrix(L) # 0 주파수 성분이 첫번째 성분에 오도록 하는 푸리에 변환 행렬
S = shifted_matrix(F) # 0 주파수 성분이 중앙에 오도록 하는 푸리에 변환 행렬
y₁ = sin.(2π*100*x) # 진동수가 100인 사인파
y₂ = 0.5sin.(2π*200*x) # 진동수가 200인 사인파
y₃ = 2sin.(2π*250*x) # 진동수가 250인 사인파
y = y₁ + y₂ + y₃ + randn(L) # 잡음이 섞인 신호
ξ = Fs*[i for i in 0:L-1]/L # 주파수 도메인
plot(
plot(ξ , abs.(F*y), label="DFT of the signal" ),
plot(ξ .- 500, abs.(S*y), label="Shifted DFT of the signal"),
layout=(2,1)
)
2次元での結果は次のようになる。
p = phantom(256, 2)
F = dft_matrix(256)
S = shifted_matrix(F)
ξ = F*p*transpose(F)
η = S*p*transpose(S)
plot(
heatmap(abs.(ξ), yflip=true, clims=(0, 200)),
heatmap(abs.(η), yflip=true, clims=(0, 200)),
layout=(1,2),
)
コード全文
function dft_matrix(n)
ω = exp(-2π*im/n)
F = [ω^(i*j) for i in 0:n-1, j in 0:n-1]
return F
end
function dft_matrix(n)
return [exp(2π *im*i*j/n) for i in 0:n-1, j in 0:n-1]
end
using FFTW
using Plots
F = dft_matrix(128)
x = rand(128)
ξ = fft(x)
η = F*x
plot(
plot(abs.(ξ), label="FFTW"),
plot(abs.(η), label="DFT matrix"),
plot(abs.(ξ .- η), label="Difference"),
layout=(3,1)
)
using Tomography
p = phantom(128, 2)
heatmap(p, yflip=true, c=:viridis)
F = dft_matrix(256)
ξ = fft(p)
η = F*p*transpose(F)
plot(
heatmap(abs.(ξ), yflip=true, title="FFTW"),
heatmap(abs.(η), yflip=true, aspect_ratio=1, title="DFT matrix"),
heatmap(abs.(ξ .- η), yflip=true, aspect_ratio=1, title="Difference"),
layout=(1,3)
)
function dft_inverse(F)
n = size(F, 1)
return conj(F) / n
end
F = dft_matrix(128)
x = rand(128)
F⁻¹ = dft_inverse(F)
plot(
plot(x, label="Original signal x"),
plot(real(F⁻¹*F*x), label="Reconstructed signal F⁻¹*F*x"),
plot(abs.(x .- F⁻¹*F*x), label="Difference"),
layout = (3,1)
)
p = phantom(256, 2)
F = dft_matrix(256)
F⁻¹ = dft_inverse(F)
p_reconstructed = F⁻¹*F*p*transpose(F)*transpose(F⁻¹)
plot(
heatmap(p, yflip=true, title="Original image"),
heatmap(real(p_reconstructed), yflip=true, title="Reconstructed image"),
heatmap(abs.(p .- real(p_reconstructed)), yflip=true, title="Difference", clims=(0, 1e-10)),
layout=(1,3)
)
Fs = 1000 # 진동수
T = 1/1000 # 샘플링 간격
L = 1000 # 신호의 길이
x = [i for i in 0:L-1].*T # 신호의 도메인
F = dft_matrix(L) # 0 주파수 성분이 첫번째 성분에 오도록 하는 푸리에 변환 행렬
S = shifted_matrix(F) # 0 주파수 성분이 중앙에 오도록 하는 푸리에 변환 행렬
y₁ = sin.(2π*100*x) # 진동수가 100인 사인파
y₂ = 0.5sin.(2π*200*x) # 진동수가 200인 사인파
y₃ = 2sin.(2π*250*x) # 진동수가 250인 사인파
y = y₁ + y₂ + y₃ + randn(L) # 잡음이 섞인 신호
ξ = Fs*[i for i in 0:L-1]/L # 주파수 도메인
plot(
plot(ξ , abs.(F*y), label="DFT of the signal" ),
plot(ξ .- 500, abs.(S*y), label="Shifted DFT of the signal"),
layout=(2,1)
)
p = phantom(256, 2)
F = dft_matrix(256)
S = shifted_matrix(F)
ξ = F*p*transpose(F)
η = S*p*transpose(S)
plot(
heatmap(abs.(ξ), yflip=true, clims=(0, 200)),
heatmap(abs.(η), yflip=true, clims=(0, 200)),
layout=(1,2),
)
環境
- OS: Windows11
- Version: Julia 1.10.0, FFTW v1.8.0, Plots v1.40.3, Tomography v0.1.5