【Python/OpenCV】高速フーリエ変換+ローパスフィルタでノイズ除去

Python版OpenCVとNumPyを用いてフーリエ変換とローパスフィルタを実装し、画像から輪郭を取り出す方法をソースコード付きで解説します。

フーリエ変換とは

フーリエ変換(Fourier Transform)とは、信号を時間領域から周波数領域に変換する処理です。
フーリエ変換では「一定の周期をもつ信号は複数の正弦波の和で表現できる」というフーリエ級数の性質を使って、周波数領域に変換します。
これにより、信号に「どのような周波数成分がどれだけ含まれているのか」を解析することができます。
変換後の信号、正弦波の和はsin(wt)でなく、複素正弦波e^{jwt}を使うため、複素数で表現されます。

逆に、周波数領域に一度変換された信号を時間領域に戻すこともできます。
これを逆フーリエ変換(Inverse Fourier Transform)といいます。フーリエ変換の詳細については以下ページで解説しています。

【フーリエ変換】計算式の原理・意味
フーリエ変換の原理について入門者向けに紹介します。

画像処理におけるフーリエ変換

フーリエ変換は周波数解析ができる便利なデータ変換です。
画像に対しても利用できますが、周波数の考え方が通常の信号とは異なるので注意する必要があります。

  • 通常の信号データ
    • 周波数=単位時間内にどのくらい振動するか
  • 画像データ
    • 周波数=単位ピクセル内に画素値がどのくらい変化するか

よって、画像データの周波数は1[px]移動したときの画素値の変化が激しいほど高周波となります。画像処理でのフーリエ変換は「時間領域→周波数領域」ではなく「空間領域→空間周波数領域」となります。

また、画像データは2次元であり、水平方向と垂直方向の2つの空間周波数成分を持っています。
画像データに対する2次元FFTは次の手順で行います。

①画像データの水平方向に1次元FFTを行います。
②画像を転置し、再び水平方向に1次元FFTを行います。
③もう1度画像を転置すれば完成です。

手順①~③で結果的に画像データに対して水平方向と垂直方向に1次元FFTを行うことになります。

ただし、FFTを利用するには画像の縦横の画素数[px]が2の冪乗である必要があります。

画像の振幅スペクトル

画像データに対して前述の2次元FFT(手順①~③)を行うと次のような振幅スペクトルが得られます。

プロ生ちゃんの画像をお借りして入力しました。

この振幅スペクトルは、中心から離れるに従って低周波数成分になるスペクトルで、画像データの周波数分布を表します。
画素値が大きい(白っぽい)ほど、その周波数成分が多く含まれていることになります。
つまり、中心付近に白い画素が集中するほど画像に高周波成分が多く含まれることを意味します。
(逆に、四隅付近に集中すれば低周波数成分が多く含まれる)

このように画像の振幅スペクトルからも(空間)周波数成分の解析ができます。

空間周波数領域の入れ替え

振幅スペクトルを利用する場合、第1象限と第3象限、第2象限と第4象限を入れ替えて利用するのが一般的です。
その際、中心から離れるに従って高周波数成分となるスペクトルへ変換されます。
入れ替えにより、後述する空間周波数フィルタリングを簡単に行うことができるようになります。

■入れ替えた例

空間周波数フィルタリング

空間周波数フィルタリングでは、画像から特定の周波数成分のみを取り出します。
代表的なものは「ローパスフィルタ」「ハイパスフィルタ」「バンドパスフィルタ」です。

  • ローパスフィルタ
    • 低周波数成分のみを通過させるフィルタ
    • 画像のノイズ除去など
  • ハイパスフィルタ
    • 高周波成分のみを通過させるフィルタ
    • 画像の輪郭、特徴点抽出など
  • バンドパスフィルタ
    • 特定範囲の周波数成分のみを通過させるフィルタ
    • 画像のデータ圧縮など(見た目の影響が少ない成分を除去)

空間周波数フィルタの設計例と操作手順

周波数領域の象限を入れ替えることで次のように空間周波数フィルタリングを簡単に行うことができます。

ローパスフィルタ(a)では、中心付近にある低周波数成分のみを通過させます。
一方、ハイパスフィルタ(b)では端の方にある高周波数成分のみを通過させます。
プログラムで実装する場合、カットする領域のスペクトルのみを0にします。

空間周波数フィルタリングの基本的な操作手順は次の通りです。

①画像を2次元FFTします。
②周波数領域を入れ替えます。
③各種フィルタリングを行います。(例えばローパスフィルタならカット領域のスペクトルを0にします)
④周波数領域を入れ替えます。(元に戻す)
⑤2次元の逆FFTをします。
⑥フィルタリングされた画像が完成します。

ローパスフィルタとは、その名の通り、信号の低周波数成分のみを通過させます。
画像データの場合は、画素値の変化が小さい部分が低周波成分となります。(ノイズは高周波成分)
つまり、画像処理においてはローパスフィルタでノイズ除去などに利用できます。原理の詳細は以下ページで解説しています。

ローパスフィルタの実装

今回は、Python+OpenCVを用いて画像のノイズ除去を行うためのローパスフィルタを作成します。
大まかな処理手順は以下のようになります。

①高速フーリエ変換で画像データを空間領域から空間周波数領域に変換します。
②零周波数成分を中心に移動します。(空間周波数領域の中心ほど低周波数成分)
③ハイパスフィルタで高周波成分のみを取り出します。(空間周波数領域の中心のデータは0に置換)
③高速逆フーリエ変換でデータを空間領域に戻します。

【画像処理】フーリエ変換の原理・実装例
この記事では、画像処理におけるフーリエ変換について解説します。

サンプルコード

サンプルプログラムのソースコードです。


実行結果

サンプルプログラムの実行結果です。

■入力画像(左)と出力画像(右)

一部分を拡大すると以下のとおりです。ノイズが低減(平滑化)されていることがわかります。

■入力画像(左)と出力画像(右)

サンプルコードの詳細解説

上記サンプルコードの詳細解説を行います。

インポートと初期設定

# -*- coding: utf-8 -*-
import numpy as np
import cv2
  • numpycv2(OpenCV)ライブラリをインポートします。
  • # -*- coding: utf-8 -*-は、ファイルのエンコーディングを指定しています。

ローパスフィルタ関数

def lowpass_filter(src, param = 0.5):
    # 高速フーリエ変換(2次元)
    src = np.fft.fft2(src)

    # 画像サイズ
    height, width = src.shape

    # 画像の中心座標
    cy, cx =  int(height/2), int(width/2)

    # フィルタのサイズ(矩形の高さと幅)
    rh, rw = int(param*cy), int(param*cx)

    # 第1象限と第3象限、第1象限と第4象限を入れ替え
    fsrc =  np.fft.fftshift(src)

    # 入力画像と同じサイズで値0の配列を生成
    fdst = np.zeros(src.shape, dtype=complex)

    # 中心部分の値だけ代入(中心部分以外は0のまま)
    fdst[cy-rh:cy+rh, cx-rw:cx+rw] = fsrc[cy-rh:cy+rh, cx-rw:cx+rw]

    # 第1象限と第3象限、第1象限と第4象限を入れ替え(元に戻す)
    fdst =  np.fft.fftshift(fdst)

    # 高速逆フーリエ変換
    dst = np.fft.ifft2(fdst)

    # 実部の値のみを取り出し、符号なし整数型に変換して返す
    return  np.uint8(dst.real)
  1. 高速フーリエ変換: np.fft.fft2(src)で画像を周波数領域に変換します。
  2. 画像サイズと中心座標の取得: 画像の高さと幅を取得し、その中心座標を計算します。
  3. フィルタサイズの計算: フィルタのサイズを指定されたパラメータに基づいて計算します。
  4. フーリエ変換のシフト: np.fft.fftshift(src)でフーリエ変換結果をシフトし、低周波成分を中心に移動します。
  5. フィルタの適用: 中心部分の値を保持し、他の部分をゼロにします。
  6. 逆シフトと逆フーリエ変換: フィルタ適用後のデータを元に戻し、逆フーリエ変換を行います。
  7. 実部の取り出し: 結果の実部を取り出し、符号なし整数型に変換して返します。

メイン関数

def main():

    # フィルタのサイズ(倍率)、小さいほどフィルタの影響が強くなる
    param = 0.3

    # 入力画像を読み込み
    img = cv2.imread("C:/github/sample/python/opencv/fft/sample.jpg")

    # RGB画像をRed, Green, Blueの1チャンネル画像に分割
    img_blue, img_green, img_red = cv2.split(img)

    # ローパスフィルタ処理
    himg_blue = lowpass_filter(img_blue, param)
    himg_green = lowpass_filter(img_green, param)
    himg_red = lowpass_filter(img_red, param)

    # RGB画像に戻す
    himg = cv2.merge((himg_blue, himg_green, himg_red))

    # 処理結果を出力
    cv2.imwrite("C:/github/sample/python/opencv/fft/lowpass_filter.jpg", himg)

if __name__ == "__main__":
    main()
  1. フィルタのサイズ設定: param = 0.3でフィルタの影響範囲を設定します。
  2. 画像の読み込み: cv2.imreadで画像を読み込みます。
  3. RGBチャンネルの分割: cv2.splitで画像をRGBの各チャンネルに分割します。
  4. ローパスフィルタの適用: 各チャンネルに対してローパスフィルタを適用します。
  5. RGB画像の再構成: cv2.mergeでフィルタ処理後の各チャンネルを再度結合します。
  6. 結果の保存: cv2.imwriteで処理結果を保存します。

おすすめ記事

【Python版OpenCV超入門】使い方とサンプルコードを解説
Python版OpenCVで画像処理プログラミングを行う方法を入門者向けにソースコード付きで解説するページです。
【画像処理入門】アルゴリズム&プログラミング
画像処理における基本的なアルゴリズムとその実装例(プログラム)についてまとめました。

コメント