画像処理におけるフーリエ変換④〜pythonによるフィルタ設計〜

画像処理におけるフーリエ変換の最終回として、pythonによるフィルタ設定についてご紹介します。

目次

画像処理のフーリエ変換において忘れてはいけないのがフィルタ設計です。ハイパスフィルター、ローパスフィルタ、バンドパスフィルタなど聞いたことがある人もいらっしゃると思いますが、その類です。フィルタを上手に設計することで画像をもっと見やすくしたり必要な情報を取り出したりなど便利なんです。

画像における周波数とは

周波数とは振動数と同じで、単位時間にどのくらい振動しているかを表す指標です。単位は[Hz]で表されます。電力でよく60Hzとか50Hzとか聞くと思いますが、これは一秒間に信号が60回、50回振動しているという意味です。

上で述べた内容は1次元的な信号の場合ですが、画像のような2次元的な信号でも周波数を考えることができます。1ピクセル動いたときに、どのくらい画素が変化するかによって周波数を考えることができるのです。画素値の変化が大きいところは周波数大で画素値の変化が小さいところは周波数小です。

画像における周波数

画像処理におけるフィルタとは

現代の工学ではあらゆるところでフィルタが使われています。素の状態では扱いにくいものをフィルタを通して扱いやすいものにするイメージでしょうか。
例えばノイズ処理においてはローパスフィルタを通すことがあります。
他にも、画像処理で二値化を行うときに初めにバンドパスフィルタ(例えば細胞の幅のピクセル値をあらかじめ観測しておいて)を用いることもあります。


画像処理においても、様々なフィルタが用いられています。以下では有名な3つのフィルタを紹介します。

1.ハイパスフィルタ

高周波数のみ通すフィルターです。高周波である画素値の変化が大きいところ(細部の情報)のみを通し、画素値の変化が小さいところ(背景など)をカットします。

ハイパスフィルタ

2.ローパスフィルタ

低周波数のみを通します。さっきのハイパスフィルタとは逆のことをすればローパスになります。

ローパスフィルタ

3.バンドパスフィルタ

ある帯域のみを通したいときに使います。低周波部分をカットし、かつごま塩ノイズのようなノイズを消したい場合に用います。

バンドパスフィルタ

このようなことはImageJでもできますが、完全にカット、完全に通すなどの処理しかできません。うまい比重をかけてフィルタを設計するなどの必要性に迫られたときに困るわけです。

そこでOpenCVを使ってフィルタを実装してみよう、というのが今回の記事の狙うところです。

フーリエ変換の実行

以下、pythonでフーリエ変換を実行していきます。pythonにはnumpy,OpenCVなど、豊富なライブラリがたくさんあります。

それではさっそくフィルタの設計をしていきます。まずは必要なライブラリをimport しておきます。
#coding: UTF-8

import numpy as np
import cv 
import cv2
from PIL import Image
Package Import.py
今回使用する画像はこれです。(hoge01.pngと保存しています。)

以下のコードで画像が表示されるのは前回確認した通りです。
image = cv2.imread("hoge01.png",0)
cv2.imshow("original",image)
cv2.waitKey(0)
cv2.destroyWindow()
Picture show.py
それではこれをフーリエ変換してみます。numpyには高速フーリエ変換を実行するnumpy.fftというライブラリがあるので、それを使うことで画像のフーリエ変換を行っていきます。
fimage = np.fft.fft2(image)
print fimage
print fimage.shape
Fourier Transform.py
出力結果

となります。フーリエ変換により、512[math]\times[/math]512の行列が作られたことがわかります。

逆フーリエ変換は同じようにすればOKです。ただし一つ注意があります。逆変換したものを画像として出力しようとすると
mat data type = 15 is not supported
error
といったエラーがきます。これは逆変換したときに複素数が残ってしまっているからです。なので逆変換された後の配列の実数部分だけを取り出し、配列から出力を行います。
ifimage = np.fft.ifft2(fimage)
ifimage = ifimage.real
Image.fromarray(np.uint8(ifimage)).show()
Array to Image.py
※Image.fromarray(np.uint8())としなければいけない理由は、ピクセル値に基準をつけないといけないからです。例えば「30」というピクセル値があったとき、それがどのくらいの明るさなのかは白と黒をどのように設定しているかによります。ピクセル値をいろいろいじったあと、出力する画素値は「0が白、黒が255」であると指定して初めて画像が出力されます。

フィルタ設計

これからフィルタを設計していきます。上のようにnp.fft.fft2のフーリエ変換を行うと周波数原点が、画像の左上にきます。

NumpyとImageJとのフーリエ変換の違い

なので、ImageJと同じ処理をするためには画像をshiftしておく必要があります。フーリエ変換の中心位置を合わせるのは以下のようにすればOKです。
fimage  = np.fft.fftshift(fimage)
Shift.py
フィルターを設計していきます。前回の記事で書いたように、フィルタは周波数領域では要素の掛け算で表されました。この画像と同じサイズの行列を作ります。
size = image.shape
filter_matrix = np.zeros(size)
size.py
ローパスフィルタを作ってみましょう。周波数領域で中心部分だけ通し、他が0になるように行列を設計すればOKです。
length = size[0]
center = size[0]/2
R = 50
for i in range(0,length):
     for j in range(0,length):
            if (i-center)*(i-center) + (j-center)*(j-center) < R*R
                           filter_matrix[i][j] = 1
filter.py
フィルターとなる行列が作られました。画像をフーリエ変換したものにこれをかければローパスフィルタの完成です。

出力させてみましょう。
fimage = fimage*filter_matrix
fimage = np.fft.fftshift(fimage) #shiftする
fimage = fimage*filter_matrix
fimage = np.fft.fftshift(fimage) #shiftしたものをもとに戻す
ifimage = np.fft.ifft2(fimage) #Inverse Fourier Transform
ifimage = ifimage.real
Image.fromarray(np.uint8(ifimage)).show()
Show.py

フィルター後の出力結果

ローパスフィルタにより細部の情報が落ち、低周波成分のみが通過していることがわかります。

画像処理においてフーリエ変換は非常に使われています。式を追うと難解ですが、プログラムを動かしてみると画像は目に見えて変わるので面白いです。ぜひみなさんもフーリエ変換を使って画像処理やってみてください。