洋食の日記

「た・である」調ではなく「です・ます」調で書きはじめれば良かったなと後悔してる人のブログです

Numo::Pocketfftにフーリエ変換による畳み込み演算するメソッドを追加した

はじめに

畳み込み演算は、そのまま実装すると、データが大きくなると重くなる。一方、フーリエ変換により、畳み込み演算は単純な掛け算に変換される。これを応用して、畳み込み演算したい二つの配列をフーリエ変換し、乗算を行った後に、フーリエ逆変換する高速化手法が一般に利用される。これをNumo::Pocketfftに実装しversion 0.2.0とした。

numo-pocketfft | RubyGems.org | your community gem host

使い方

Numo::Pocketfftはgemコマンドでインストールできる。pocketfftのコードを同梱しているので、外部ライブラリを別でインストールする必要はない。

$ gem install numo-pocketfft

SciPyにあやかって、メソッド名はfftconvolveとした。二つのNumo::NArrayな配列を渡せば、その(離散)畳み込み演算の結果を返す。

irb(main):001:0> require 'numo/pocketfft'
=> true
irb(main):002:0> Numo::Pocketfft.fftconvolve(Numo::DFloat[1, 2, 3], Numo::DFloat[4, 5])
=> Numo::DFloat#shape=[4]
[4, 13, 22, 15]

画像処理への応用

画像のフィルタリングは、画像とフィルタの畳み込みで実現される。Magroも使って、アイコンに使ってるハンバーグの画像にSobelフィルタをかけて、エッジを抽出してみる。

require 'magro'
require 'numo/pocketfft'

# Lena画像を読み込む.
img = Magro::IO.imread('lena.png')

# グレイスケール化する.
gray = img.median(axis: 2)

# Sobelフィルタを定義する.
fx = Numo::DFloat[[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]
fy = Numo::DFloat[[-1, -2, -1], [0, 0, 0], [1, 2, 1]]

# 畳み込みによりSobelフィルタをかける.
gx = Numo::Pocketfft.fftconvolve(gray, fx)
gy = Numo::Pocketfft.fftconvolve(gray, fy)

# エッジ画像を得る.
g = Numo::NMath.sqrt(gx**2 + gy**2)
g[g>255] = 255
g[g<0] = 0

# 畳み込みによりできた画像の端を削る.
# ※
# 元の画像サイズは512x512であり, フィルタのサイズが3x3であることから, 
# 畳み込みにより (512 + 3 - 1) x (512 + 3 - 1) の画像ができる.
# 上下左右の端を1ピクセルずつ削り512x512の画像にする.
h, w = gray.shape
fh, fw = g.shape
sy = (fh - h) / 2
sx = (fw - w) / 2
g = Numo::UInt8.cast(g[sy..h, sx..w])

# 画像を保存する.
Magro::IO.imsave('lena_fil.png', g)

これを実行すると、以下のような画像が得られる。Sobelフィルタによりエッジが抽出されていることがわかる。

f:id:yoshoku:20200712193223p:plain

おわりに

Numo::NArrayな配列で畳み込み演算ができるようになった。使い方の例のように、画像のフィルタリングも簡単になったので、Magroの開発も進むかな?

github.com