【最新】PythonでNon Local Means Filterを実行してみた(サンプルコード掲載)

Image Processing

こんにちは、てつをです。今回は、画像のノイズ処理に利用される強力なフィルターであるNon Local Means Filter(非局所平滑化フィルター)を実際にPythonで実行してみたいと思います。私は本業でメソスケールのシミュレーションソフトの販売・サポートに携わっており、そのソフトにもNLM Filterの機能が実装されています。しかし、あくまで利用するだけにとどまっていたので今後複数回の記事の中で実際にPythonモジュールで実行してみたり、原理を深く理解していけたらなと思っています。 

目次

  1. Non Local Means Filterとは
  2. Non Local Means Filterを実行する手段
  3. 実際にNon Local Means Filterを動かしてみる

Non Local Means Filterとは

画像処理におけるノイズ処理について

画像処理におけるノイズ処理の目的は、ノイズを取り除くことで元の画像を復元することです。主な方法としては、局所的な場所ごとに何かしらの均す方法である平滑化があります。代表例としては、ガウシアンフィルター(Gaussian smoothing model)、the anisotropic filtering, the neighborhood filteringなどが挙げられます。今回検証を進めていくNon Local Means Filterも、ガウシアンフィルターと同様に画像を平滑化する役割を担っています。また、別の方法として周波数領域から適用するフィルターもあり、the empirical Wiener filterやwavelet thresholding methodなどがあります。

Non Local Means Filterの特徴

今回検証していくNon Local Means Filter(NLMFilter)については、名前の通りnon localなので「局所的ではない」フィルターです。つまり、画像全体の情報を加味してノイズ処理が行われていますのでその点ガウシアンフィルターとは異なっており、ガウシアンフィルターでは考慮できない部分まで考慮できるのでいわばガウシアンフィルターの発展系の位置付けになるかと思います。細かいアルゴリズムについては今回は触れないですが、NLMFilterを利用することでエッジを残したままノイズを取り除くことが可能です。 

Non Local Means Filterを実行する手段

Non Local Means Filterの機能が実装されている主なPythonモジュール 

Non Local Meansフィルターを実行する方法は多岐に渡りますが、今回はPythonで実施することを想定して手段を紹介します。Non Local Meansフィルターを利用できるPythonモジュールがいくつか存在するため、これらモジュールを紹介したいと思います。

OpenCV (Open Source Computer Vision Library) 

  • OpenCVは、コンピュータビジョン、画像処理、機械学習に関連するオープンソースのライブラリです。
  • 画像やビデオからオブジェクト、顔、さらには人間の筆跡を識別するためのリアルタイム処理に重要な役割を果たしています。
  • 主なモジュールには、コア機能、画像処理、画像ファイルの読み書き、ビデオI/O、GUI、ビデオ分析などが含まれます。

OpenCV公式HPはこちら

skimage (scikit-image)

  • skimage、またはscikit-imageは、Python用の画像処理とコンピュータビジョンのためのアルゴリズム集です。
  • 画像処理にはセグメンテーション、幾何学的変換、色空間の操作、フィルタリング、特徴検出などが含まれます。
  • 画像をNumPy配列として扱い、scipy.ndimageを拡張してより多くの画像処理機能を提供します。
  • これらのモジュールは、画像処理や機械学習の分野で広く利用されており、多様な機能と拡張性を提供しています。

skimageの公式HPはこちら

OpenCVで実際にNon Local Means Filterを動かしてみる

画像データの準備

今回、メソスケール解析に役立てばなという気持ちもありますので、μCTやSEMで取得したミクロな材料の画像を利用していきたいともいます。サンプル画像については以下のサイトから入手しました。
https://www.math2market.com/geodict-software/geodict-software-download.html

今回は、以下のNMCの電極構造のCT画像を利用していきたいと思います。

実行手順

では、実際にOpenCVでNon Local Meansフィルターを実行していきたいと思います。大まかな流れは以下のようになります。

  1. モジュールのインポート
  2. 画像の読み込み
  3. フィルター処理実行
  4. 結果確認

ここからは、1〜5の手順を実際のコードを見ながら確認していきます。

1. モジュールのインポート

import cv2 as cv
from matplotlib import pyplot as plt

今回は、Non Local Meansフィルターを実行するOpenCVと、フィルター処理した画像を並べて表示するmatplotlibの2モジュールを利用します。

2. 画像の読み込み

image = cv2.imread('ここに画像ファイル名を入力')

imageという変数に画像データを格納します。

3. フィルター処理の実行

denoised_image = cv2.fastNlMeansDenoising(image, dst=None, h=10, templateWindowSize=7, searchWindowSize=21)

denoised_imageという変数に、フィルター処理をした画像データを格納します。今回利用する画像はCT画像なので、白黒画像となります。白黒画像の場合は、fastNlMeansDenoisingという関数を利用してフィルター処理を行います。(カラー画像の場合は、fastNlMeansDenoisingColored利用する。)カラー画像、白黒画像については「【調査してみた】カラー写真はいったいどのような数値情報を持っているのか、実際の写真で確認してみた」で説明しておりますので合わせてご参照ください。fastNlMeansDenoisingで設定できる引数は以下になります。

  1. src (最初の引数): これはノイズ除去を行いたい入力画像です。srcという単語をそのまま入力するとエラーが発生します。代わりに、画像データを含む変数を渡す必要があります。例えば、imageという変数に画像データが格納されている場合、src=imageのように指定します。
  2. dst (オプション): これはノイズ除去後の出力画像を格納する変数です。通常はNoneを指定し、関数が新しい画像データを作成するようにします。
  3. h: フィルタの強度を決定します。値が大きいほどノイズ除去の効果は強くなりますが、画像の詳細が失われやすくなります。
  4. WindowSize: ウィンドウのサイズを指定します。これは奇数でなければならず、通常は7が推奨されます。
  5. searchWindowSize: 検索ウィンドウのサイズを指定します。これも奇数でなければならず、値が大きいほど処理に時間がかかりますが、より良いノイズ除去が期待できます。

取り急ぎ、現時点ではデフォルトの値で進めていきたいと思います。

4. 結果表示

plt.imshow(image)
plt.show()
plt.imshow(denoised_image)
plt.show()

matplotlibを使ってフィルター処理をした画像と元画像を表示します。ここでは、横並びに表示はせず1つ1つ表示しています。

以上で、Non Local Means Filterの実行方法の紹介は終了となります。

入力パラメータを色々変更してみる

最後に、fastNlMeansDenoisingで設定できる引数の値を色々変更してみて、結果の違いを確認してみたいと思います。変更可能な引数は主に①強度h 、②Window Size、③Search Window Sizeの3つかと思いますので、これら3つのパラメータを変更していきます。以下にサンプルコードを掲載していますが、事前に先ほどの手順1のモジュールのインポートと手順2の画像の読み込みは実施した状態からのコードになりますのでご注意ください。

①強度h

まずは、強度を変更していきます。デフォルトの値がh=10なので、10,20,30と強度をあげてみました。以下、サンプルコードになります。

# 新しい図を作成します。
plt.figure(figsize=(20, 5))

# オリジナルの画像を最初のサブプロットとして追加します。
plt.subplot(1, 4, 1)
plt.imshow(image, cmap='gray')
plt.title('Original')
plt.xticks([]), plt.yticks([])

# ループを通じてフィルター処理された画像のサブプロットを作成します。
for i, h_value in enumerate([10, 20, 30], start=2):
    # Non Local Meansフィルターを適用します。
    img = cv2.fastNlMeansDenoising(image, None, h=h_value, templateWindowSize=7, searchWindowSize=21)
    
    # サブプロットを追加します。1行4列のグリッドのi番目の位置に配置します。
    plt.subplot(1, 4, i)
    plt.imshow(img, cmap='gray')
    plt.title('h = ' + str(h_value))
    plt.xticks([]), plt.yticks([])

# すべてのサブプロットを表示します。
plt.show()

②Window Size

続いて、Window Sizeを変更してみます。こちらはデフォルトの値がWindow Size = 7なので、3,7,15と変更して結果を確認してみます。

# 新しい図を作成します。
plt.figure(figsize=(20, 5))

# オリジナルの画像を最初のサブプロットとして追加します。
plt.subplot(1, 4, 1)
plt.imshow(image, cmap='gray')
plt.title('Original')
plt.xticks([]), plt.yticks([])

# ループを通じてフィルター処理された画像のサブプロットを作成します。
for i, window in enumerate([3, 7, 15], start=2):
    # Non Local Meansフィルターを適用します。
    img = cv2.fastNlMeansDenoising(image, None, h=10, templateWindowSize=window, searchWindowSize=21)
    
    # サブプロットを追加します。1行4列のグリッドのi番目の位置に配置します。
    plt.subplot(1, 4, i)
    plt.imshow(img, cmap='gray')
    plt.title('Window Size = ' + str(window))
    plt.xticks([]), plt.yticks([])

# すべてのサブプロットを表示します。
plt.show()

結果は上図のようになりました。Window Sizeを変更する場面やメリットデメリットをまとめておきます。

変更する場面:

画像の詳細が細かい、またはエッジが多い場合、ウィンドウサイズを小さくすることで、これらの詳細を保持することができます。 画像に広範囲にわたる均一な領域がある場合、ウィンドウサイズを大きくすることで、より効果的にノイズを除去できます。

効果:
  • 小さいウィンドウサイズ: ノイズ除去は局所的に行われ、画像の細部が保持されますが、ノイズ除去の効果は穏やかになります。
  • 大きいウィンドウサイズ: より広範囲のピクセルを考慮に入れるため、ノイズ除去の効果は強くなりますが、画像の細部がぼやける可能性があります。
デメリット:
  • ウィンドウサイズが大きすぎると、画像のエッジやテクスチャが失われる可能性があります。
  • ウィンドウサイズが小さすぎると、ノイズ除去の効果が不十分になることがあります。

③Search Window Size

最後に、Search Window Sizeを変更してみます。こちらはデフォルトの値が21になりますので、11,21,49と変更して結果を比較してみました。

# 新しい図を作成します。
plt.figure(figsize=(20, 5))

# オリジナルの画像を最初のサブプロットとして追加します。
plt.subplot(1, 4, 1)
plt.imshow(image, cmap='gray')
plt.title('Original')
plt.xticks([]), plt.yticks([])

# ループを通じてフィルター処理された画像のサブプロットを作成します。
for i, search in enumerate([11,21,49], start=2):
    # Non Local Meansフィルターを適用します。
    img = cv2.fastNlMeansDenoising(image, None, h=10, templateWindowSize=7, searchWindowSize=search)
    
    # サブプロットを追加します。1行4列のグリッドのi番目の位置に配置します。
    plt.subplot(1, 4, i)
    plt.imshow(img, cmap='gray')
    plt.title('Search Window Size = ' + str(search))
    plt.xticks([]), plt.yticks([])

# すべてのサブプロットを表示します。
plt.show()

こちらも、変更する場面やメリットデメリットをまとめておきたいと思います。

変更する場面:

画像に広範囲のノイズが存在する場合、検索ウィンドウサイズを大きくすることで、より多くのピクセルを比較してノイズを除去できます。 画像が比較的クリーンで、局所的なノイズ除去が必要な場合は、検索ウィンドウサイズを小さくすることができます。

効果:
  • 小さい検索ウィンドウサイズ: 計算速度が向上し、局所的なノイズ除去に適していますが、広範囲のノイズには効果が限定的です。
  • 大きい検索ウィンドウサイズ: より多くのピクセルを比較するため、ノイズ除去の効果は向上しますが、計算時間が長くなります。
デメリット:
  • 検索ウィンドウサイズが大きすぎると、計算コストが増加し、処理時間が長くなります。
  • 検索ウィンドウサイズが小さすぎると、ノイズ除去の効果が不十分になる可能性があります。

これらのパラメータを調整することで、画像の特性に合わせてノイズ除去のバランスを取ることができます。ただし、最適なパラメータは画像ごとに異なるため、実際の画像に対して試行錯誤することが重要です。また、計算コストとノイズ除去の効果のバランスを考慮する必要があります 。

検証は以上となります。次回以降、別のフィルターに関しての検証やNon Local Meansフィルターのアルゴリズム詳細について解説していきたいと思います。

以上