空間フィルタリングによる平滑化とエッジ検出

畳み込み処理

画素値を操作する際に、当該画素の周辺に存在する画素も用いて計算を行うことができます。例えば当該画素を含む周囲9ピクセルの画素値の平均を計算し、その値を当該画素の新しい画素値とする操作を行えば、画像をぼかす効果が得られます。このような画素値の処理の仕方を空間フィルタリングと呼びます。

例を挙げて説明します。3 x 3のサイズのフィルタを使った空間フィルタリングをするとします。着目画素とその周囲を合わせた9つの画素値について、フィルタの値(重み)との積和を計算します。得られた結果の値を、着目画素の画素値とします。このような操作を画像の全画素に対して行うのが空間フィルタリングです。

ただし画像のボーダーについては、対応する画素値が元画像に無いので、隣接するピクセルの値を代用したり、0で埋めたりといった処理がなされます。

画像の画素値とフィルタ(カーネルとも呼ぶ)の重みとで積和を計算する処理のことを畳み込みと呼びます。畳み込み処理を用いた空間フィルタリングによって、画像の局所的な情報を利用した画像の変換処理が可能になります。
フィルタリングによりどのような効果が得られるかはフィルタの重みをどう設計するかによって決まります。代表的な例として画像の平滑化や、輝度勾配やエッジの検出などがあります。

平滑化

図にいくつかの平滑化フィルタの例を挙げます。左端は 3 x 3 および 5 x 5 サイズの平均化フィルタの例です。全ての重みが同じになっており、カーネルの範囲内の画素値を平均することになります。フィルタリング結果として得られる画像は元画像をボケさせたようなものになります。

imager では convolve という関数を使って畳み込み処理が簡単に行えます。変換したい画像とフィルタを引数に取りますが、フィルタもまた画像データである必要があります。array 関数などで重みの配列を作り、as.cimg 関数を使ってそれを cimg 画像に変換することでフィルタを作ります。

library( imager )
# 平均化フィルタ
averagingFilter = as.cimg( array( 1/9, c(3,3) ) )
img = convolve( boats, averagingFilter )
plot( img )

重みの与え方を変えることで様々な平滑化フィルタを作ることができます。ガウス分布の重みを使えばガウシアンフィルター(ガウスぼかし)になりますし、特定方向のみに重みを持たせればモーションブラーのような効果を得られます。

gaussianFilter = as.cimg( array( c(1,2,1,2,4,2,1,2,2), c(3,3) ) )
motionBlur = as.cimg( diag( 1/9, c( 9, 9 ) ) )
plot( imlist( 
  original = boats,
  gaussian = convolve( boats, gaussianFilter ),
  motion = convolve( boats, motionBlur )
  ), layout = "row" )

ガウシアンフィルタなどぼかし系のフィルタ操作をしたい場合には専用の関数が用意されているのでそれを使うのが便利です。カーネルサイズ(フィルタの大きさ)も引数で設定できます(カーネルサイズが大きいほど強くボケる)。

ぼかし処理の用途のひとつとして、ノイズ除去があります。画像中の細かなノイズはぼかし処理をすると消すことができるからです。実験として、画像にノイズを付加し、様々なタイプのブラー系フィルタをかけて出力結果を比較してみましょう。

noisyBoats = ( boats + .1 * rnorm( prod( dim( boats ) ) ) )
plot( imlist( 
  original = boats,
  noisy = noisyBoats,
  isoblur = isoblur( noisyBoats, 3 ),
  medianblur = medianblur( noisyBoats, 3 ),
  anisotropic = blur_anisotropic( noisyBoats, ampl = 1e4, sharp = 1 ) ),
  layout = "row" )


isoblur はガウスフィルタです(厳密にはその近似)。medianblur はフィルタ範囲内の median となる画素値を取るというフィルタ(メディアんフィルタ)で、ノイズを除去しつつ元画像のエッジ情報もそれなりに保たれています。blur_anisotropic は元画像のエッジを保存する性能がより高いフィルタです。

エッジ検出

輪郭線などのエッジ部分では、輝度の急激な変化が起こっていると考えられます。そこで、隣り合う画素同士の画素値の差分を取るようなフィルタ(微分フィルタ)を用いれば、画像のエッジを検出できます。輝度勾配は画像の横方向と縦方向について調べることができます。

# 微分フィルタ
diffX = as.cimg( array( c(0,0,0,-1,1,0,0,0,0), c(3,3) ) )
diffY = as.cimg( t( array( c(0,0,0,-1,1,0,0,0,0), c(3,3) ) ) )
plot( imlist( diffX = convolve( grayscale( boats ), diffX ),
              diffY = convolve( grayscale( boats ), diffY ) ), layout = "row" )

横方向に勾配を取る際に同時に縦方向には平滑化処理を行う方法もあり、平滑化の方法によってプリューウィットフィルタソーベルフィルタなどがよく知られています。

# プリューウィットフィルタ
prewittX = as.cimg( array( c(-1,0,1,-1,0,1,-1,0,1), c(3,3) ) )
prewittY = as.cimg( t( array( c(-1,0,1,-1,0,1,-1,0,1), c(3,3) ) ) )
plot( imlist( prewittX = convolve( grayscale( boats ), prewittX ),
              prewittY = convolve( grayscale( boats ), prewittY ) ), layout = "row" )

# ソーベルフィルタ
sobelX = as.cimg( array( c(-1,0,1,-2,0,2,-1,0,1), c(3,3) ) )
sobelY = as.cimg( t( array( c(-1,0,1,-2,0,2,-1,0,1), c(3,3) ) ) )
plot( imlist( sobelX = convolve( grayscale( boats ), sobelX ),
              sobelY = convolve( grayscale( boats ), sobelY ) ), layout = "row" )

x 方向や y 方向の勾配が欲しい場合は専用の関数が用意されているのでそれを用いるのが便利でしょう。imgradient 関数を使えば 2 つの勾配をまとめてリストとして得られます。

# 画素値の勾配は imgradient 関数を使って得るのが手軽で良い
gradients = imgradient( boats, "xy", scheme = 2 )
# scheme の値でどのようなフィルタを使うかを指定する
#-1 = Backward finite differences
# 0 = Centered finite differences
# 1 = Forward finite differences
# 2 = Using Sobel masks 
# 3 = Using rotation invariant masks
# 4 = Using Deriche recursive filter
# 5 = Using Van Vliet recursive filter

特定方向の勾配というよりは画像中の輪郭線を抽出したいという場合には cannyEdges 関数を使うのが便利です。閾値を指定することで抽出する度合いの調整も可能です。

cannyEdges( boats ) %>% plot
cannyEdges( boats, alpha = 0.4) %>% plot # Make thresholds less strict
cannyEdges( boats, alpha = 1.4) %>% plot # Make thresholds more strict
plot( imlist(
  Original = grayscale( boats ),
  LowThreshold = as.cimg( cannyEdges( boats, alpha =.4 ) ),
  HighThreshold = as.cimg( cannyEdges( boats, alpha = 1.4 ) ) ), layout = "row" )

線画を白背景に黒線で表示してみましょう。

img = load.image( "myimage.jpg" )
plot( imlist( Original = img,
              Edges = as.cimg( 1 - cannyEdges( img, alpha = .2 ) )
              ), layout = "row" )



コメント