画像データへの応用
画像データに対して辞書学習を適用するときには、画像をパッチと呼ばれる小領域に切り出して扱うことが多いです。下図のように、画像の赤で囲まれた長方形領域に注目し、このパッチは辞書を少数組み合わせると作り出すことができるはずだと思うわけです。このとき、辞書もパッチと同じサイズの画像と思ってもよく、一般的に辞書はエッジやテクスチャを表すと考えられています。
学習をする際は、画像のいろんなところからパッチを切り出し、それらをベクトル化することで行列を作ります。それでは、実際にやってみましょう。K-SVDアルゴリズムでは標準化をしておいた方が再構成しやすくなるので忘れずに行っておきましょう。
from PIL import Image
from sklearn.preprocessing import StandardScaler
from spmimage.feature_extraction.image import extract_simple_patches_2d, reconstruct_from_simple_patches_2d
img = np.asarray(Image.open("img/recipe.jpg").convert('L'))
patch_size = (8, 8)
patches = extract_simple_patches_2d(img, patch_size)
patches = patches.reshape(-1, np.prod(patch_size)).astype(np.float64)
scl = StandardScaler()
Y = scl.fit_transform(patches)
そして、得られた\(Y\)に対してK-SVDアルゴリズムを適用してみます。
ksvd = KSVD(n_components=50, transform_n_nonzero_coefs=5) X = ksvd.fit_transform(Y) D = ksvd.components_
まずは、うまく学習ができたかどうかを再構成誤差を求めることで確かめてみましょう。パッチごとに再構成誤差を求める方法もありますが、ここでは、再構成されたパッチから再構成画像を構築し、元の画像と比較する方法で確かめてみます。
reconstructed_patches = np.dot(X, D) reconstructed_patches = scl.inverse_transform(reconstructed_patches) reconstructed_patches = reconstructed_patches.reshape(-1, patch_size[0], patch_size[1]) reconstructed_img = reconstruct_from_simple_patches_2d(reconstructed_patches, img.shape) reconstructed_img[reconstructed_img < 0] = 0 reconstructed_img[reconstructed_img > 255] = 255 reconstructed_img = reconstructed_img.astype(np.uint8)
左が元画像、右が再構成画像です。人が見ても大きな違いはないように見えます。PSNR、SSIMという画像の類似度を定量的に測る一般的な指標の計算もしてみましょう。
from skimage.metrics import peak_signal_noise_ratio as compare_psnr
from skimage.metrics import structural_similarity as compare_ssim
print("PSNR:", compare_psnr(img, reconstructed_img))
print("SSIM:", compare_ssim(img, reconstructed_img))
PSNR: 34.77376225983931 SSIM: 0.9279544257655109
どちらの指標においても、両者の画像はかなり似ているということが言えます。次に、得られたスパースコードの行列と、辞書を二次元化したものを見てみましょう。
辞書を見ると、エッジを表現するための成分や、質感を表すテクスチャの成分などが抽出されていることがわかります。そして、このスパースコードの行列を見ることによって、各パッチを表現するためにはどのように辞書を組み合わせればいいかを知ることができます。
画像のノイズ除去
最後に画像の辞書学習再構成をうまく活用してノイズ除去に挑戦してみましょう。冒頭では、ランダムな行列はスパースに表現することはできないので、再構成がうまくできないという話をしました。一方で自然画像に対しては、パッチに区切ることでうまくスパースに表現することができました。では、綺麗な画像にランダムなノイズが付与されている場合、そのような画像に対して、辞書学習したらどうなるでしょうか。辞書学習によって、辞書とスパースコードには綺麗な画像に関する成分のみ抽出される、つまり、得られた辞書とスパースコードで再構成画像を作ると綺麗な画像を復元することができるのでしょうか。実際にやってみましょう。
先ほどの画像に、各画素-10~10のランダムな値を加えてみます。
true_img = np.asarray(Image.open("img/recipe.jpg").convert('L'))
noise = (np.random.rand(*img.shape) - 0.5) * 20
img = true_img.copy().astype(np.float64) + noise
このときの、元の画像とノイズ付加画像のPSNR、SSIMは次の通りです。
PSNR: 17.07698724442016 SSIM: 0.6886488280378513
このノイズが付加された画像に対して、先ほどと全く同様の手順で辞書学習を適用し、再構成画像を計算してみましょう。
元画像、ノイズ付加画像、再構成画像を左から順に下図に示します。
そして、元画像と再構成画像のPSNRとSSIMは次の通りになりました。
PSNR: 34.034784549441824 SSIM: 0.8847876523679297
ノイズ付加画像と比べると、PSNR、SSIMともに劇的に改善されていることがわかります。このように、単純に辞書学習を適用するだけで、画像のノイズ除去を行うことができます。逆にいうと、辞書学習によって、再構成画像を求めるということは画像の平滑化のようなフィルタをかけていると言うこともできます。
最後に
さて、ここまで、辞書学習について、画像データへの応用を交えながら説明してきましたが、辞書学習のエッセンスを理解していただけたでしょうか。ここで用いたソースコードについては、GitHubで公開しています。
詳細な理論やアルゴリズムについては説明を割愛しましたが、興味のある方は以下の文献を当たってみることをおすすめします。
- スパースモデリング ― l1 / l0 ノルム最小化の基礎理論と画像処理への応用 ―
- K-SVD: An Algorithm for Designing Overcomplete Dictionaries for Sparse Representation
- Efficient Implementation of the K-SVD Algorithm and the Batch-OMP Method
実は、ここで紹介した画像のノイズ除去は辞書学習の応用事例のうちの一つにすぎず、辞書学習は異常検知や超解像のような様々な応用事例があります。次回の投稿では、辞書学習の発展的な応用と実ビジネスでの活用などを紹介する予定ですので、お楽しみに。
