画像のノイズ除去

目的

このチュートリアルでは

  • 画像中のノイズを除去するNon-local Means Denoisingアルゴリズムを学びます.
  • cv2.fastNlMeansDenoising()cv2.fastNlMeansDenoisingColored() といった関数を学びます.

理論

これまでのチュートリアルでガウシアンフィルタリングや中央値フィルタリングなど様々な画像の平滑化技術を学んできました.これらの技術はノイズが小さい時にはうまく動いてくれます.ある画素の近傍画素に対して何かしらの処理を施すわけですが,これらの技術はいうなればノイズ除去は注目画素の近傍で局所的に行われるわけです.

ノイズにはある性質があります.一般的には平均ゼロのランダムな値であると考えられます.ノイズがかかった画素値を以下のようにモデル化します. p = p_0 + n ここで p_0 はノイズが含まれない本来の画素値を表し n は画素に加えられたノイズの値を表します.同様の画素を様々な画像から集め(例えば N),その平均値を計算します.ノイズの平均値はゼロであるので,理想的には p = p_0 となるはずです.

これは単純な設定で確認できます.ある場所にカメラを固定し数秒間撮影すると,大量の静止画を撮影できます.動画像中の全フレームの平均値を計算するためにちょっとしたコードを書きます(既に色々なチュートリアルを経験しているあなたにとっては簡単すぎるでしょう).最初のフレームと最終結果を比較してみてください.ノイズ除去の効果が分かります.残念ながらこの単純な方法はカメラやシーンの動きに対してロバストではありません.また,動画像を利用可能なことはあまりなく,たいていはノイズがかかった一枚の静止画しか利用できません.

アイディアは単純で,ノイズを平均処理によって除去するために似たような画像を何枚か必要なわけです.画像中の小領域(例えば5x5)を考えてみましょう.画像中の別の場所に同じようなパッチが存在する可能性は高いでしょう.注目画素の周辺にあるかもしれません.これらの似ているパッチ(小領域)を使って画素の平均を取ってみてはどうでしょうか?特定の小領域のためなら問題ないでしょう.以下の画像をみてください:

Similar patches

画像中の青いパッチ似ています.緑のパッチも似ています.ある画素に対して周囲に小さな小領域を定義し,画像中から似ているパッチを見つけ,似ているパッチの平均値を出力画像の対応する画素の画素値とします.これがNon-Local Means Denoisingと呼ばれる手法です.先ほど言及した平滑化法に比べて処理時間はかかるものの,非常に良い結果が得られます.詳細な情報とオンラインデモは補足資料の一番目のリンクを参照してください.

カラー画像のノイズ除去を行う場合は,画像をCIELAB色空間へと変換してからL成分とAB成分に対して独立してノイズ除去を適用します.

OpenCVを使ったノイズ除去

OpenCVはこの手法に関して4つの関数を提供しています.

  1. cv2.fastNlMeansDenoising() - 一枚のグレースケール画像に対する関数
  2. cv2.fastNlMeansDenoisingColored() - 一枚のカラー画像に対する関数
  3. cv2.fastNlMeansDenoisingMulti() - 短時間に撮影されたグレースケール画像列に対する関数
  4. cv2.fastNlMeansDenoisingColoredMulti() - 短時間に撮影されたカラー画像列に対する関数
共通する引数:
  • h : フィルタの強さを決定するパラメータ.hの値が大きいとノイズをより消せますが,画像の詳細な部分も失ってしまいます(10であればOK).
  • hForColorComponents : カラー画像用のフィルタの強さを決定するパラメータ(hと同様10はOK).
  • templateWindowSize : テンプレートとなるウィンドウの大きさ.奇数でなければならない(7が推奨されている).
  • searchWindowSize : 探索ウィンドウの大きさ.奇数でなければならない(21が推奨されている).

これらのパラメータに関する詳細な説明は補足資料の一番目のリンクを参照してください.

ここでは2番目と3番目の関数を使用してみます.残りの関数は自身で試してみてください.

1. cv2.fastNlMeansDenoisingColored()

上述したようにカラー画像に対するノイズ除去のための関数です(ノイズは白色雑音を想定しています).以下のコードを見てください:

import numpy as np
import cv2
from matplotlib import pyplot as plt

img = cv2.imread('die.png')

dst = cv2.fastNlMeansDenoisingColored(img,None,10,10,7,21)

plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(dst)
plt.show()

以下に結果画像のズームアップを示します.入力画像は分散 \sigma = 25 の白色雑音が足されています.結果を見てください:

Result of denoising

2. cv2.fastNlMeansDenoisingMulti()

今度は動画像に対してこの手法を適用します.第一引数は劣化画像列のリストです.第二引数は第二引数 imgToDenoiseIndex はノイズ除去を適用する画像のフレーム番号です.第三引数 temporalWindowSize はノイズ除去に使うための近傍フレームの数(奇数)を指定します.このように設定すると全部で temporalWindowSize 枚の画像の内,中央のフレームの画像に対してノイズ除去を行います.例えば入力画像として5フレームのリストを与えたとします. imgToDenoiseIndex = 2`temporalWindowSize = 3`とします.そうすると,frame-2番目の画像のノイズ除去を行うためにframe-1,frame-2,frame-3番目の画像が使われます.それでは以下のコードを見てください.

import numpy as np
import cv2
from matplotlib import pyplot as plt

cap = cv2.VideoCapture('vtest.avi')

# create a list of first 5 frames
img = [cap.read()[1] for i in xrange(5)]

# convert all to grayscale
gray = [cv2.cvtColor(i, cv2.COLOR_BGR2GRAY) for i in img]

# convert all to float64
gray = [np.float64(i) for i in gray]

# create a noise of variance 25
noise = np.random.randn(*gray[1].shape)*10

# Add this noise to images
noisy = [i+noise for i in gray]

# Convert back to uint8
noisy = [np.uint8(np.clip(i,0,255)) for i in noisy]

# Denoise 3rd frame considering all the 5 frames
dst = cv2.fastNlMeansDenoisingMulti(noisy, 2, 5, None, 4, 7, 35)

plt.subplot(131),plt.imshow(gray[2],'gray')
plt.subplot(132),plt.imshow(noisy[2],'gray')
plt.subplot(133),plt.imshow(dst,'gray')
plt.show()

以下に結果画像を拡大した画像を示します:

Denoising a frame

この処理は計算に時間がかかります.上記の図は左から入力画像,劣化画像,復元画像を載せています.

補足資料

  1. http://www.ipol.im/pub/art/2011/bcm_nlm/ (このチュートリアルで紹介したノイズ除去法の詳細,オンラインでもなどが載っています.ぜひ参照してください.我々が使ったテスト画像はこのリンクから生成しました.)
  2. coursera のオンライン講義 (最初の画像はここから使っています)

課題