こしあん
2019-11-04

モルフォロジー変換は実はMaxPoolingだったという話(TensorFlowでの実装)

Pocket
LINEで送る


画像処理の重要な変換に膨張(Dilation)や収縮(Erosion)といったモルフォロジー変換があります。実はこれはディープラーニングでよく使われるMaxPoolingフィルターで置き換えることができます。TensorFlowの実装で見ていきます。

モルフォロジー変換

OpenCVでのモルフォロジー変換の解説がとてもわかりやすいのでこちらを参照してください。

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_morphological_ops/py_morphological_ops.html

膨張(Dilation)

OpenCVではある適当なパッチサイズに対して、「パッチ内で1つでも1が含まれれば1を返す処理」と紹介されています。簡単に3×3フィルターで考えると

このように1ピクセルでも1はあったら1を返すような処理です。

一見論理和のように見えますが、より一般的にはパッチ内の最大値を取ればいいです。例えば、TensorFlowのtf.image.dilation2dという関数ではMaxと書かれています。

収縮(Erosion)

膨張とは逆で、「パッチ内で1つでも0が含まれていれば0を返す処理」です。同様に3×3フィルターで考えると

論理積のような処理ですね。すべてのピクセルが1の場合のみ1を返します。

より一般的にはパッチ内の最小値を取ればいいです。例えば、TensorFlowのtf.image.erosion2dという関数ではMinと書かれています。

最大値を取るのはMaxPooling、最小値は?

「パッチ内の最大値を取る」というのは実はニューラルネットワークでは頻繁に使われています。CNNでよくあるMaxPoolingがそうです。

ただしCNNでのMaxPoolingはおもにダウンサンプリング(解像度を小さくするの)を目的するのに対して、モルフォロジー変換ではダウンサンプリングは行いません。これはMaxPoolingのstrideの値を変えることで再現できます。

通常、ダウンサンプリングでPooling層を使う場合は、カーネルサイズとストライドの大きさを同じにしますが、モルフォロジー変換の場合はStrideを1にします。Inceptionモデル内のモジュールでも同じ使い方をしています。

では、最小値を取る処理はどうすればいいのかというと、TensorFlowにはMinPoolingという関数はありません。しかし、MaxPoolingでMinPoolingを再現することができます。マイナスを取ってMaxPoolingを取り、更に符号を反転させればMinPoolingとなります。例えば、「-1, 3, 5」という配列のMaxは5ですが、マイナスを取ってMaxを取ると1(元が-1)が出力されます。この値の符号を反転させれば最小値の-1が出てくるわけです。

膨張(Dilation)の実装

5×5のカーネルでモルフォロジー変換をします。膨張処理から。

import tensorflow as tf
# 膨張
def dilation(tensor):
    # 5x5のMaxフィルター
    return tf.nn.max_pool2d(tensor, 5, strides=1, padding="SAME")  # stride=1がポイント

カーネルサイズを5、Strideを1でMaxPoolingすればいいです。解像度が変わらないようにPaddingを入れています。OpenCVの解説同じ画像でやってみましょう。画像はOpenCVのページのものを使っています。

また、読み込み用、表示用に以下の2つの関数を定義しておきます。

import matplotlib.pyplot as plt

def load_tensor(filename):
    x = tf.io.decode_png(tf.io.read_file(filename))[:,:,:3] # RGB
    x = tf.image.rgb_to_grayscale(x)
    x = tf.expand_dims(x, axis=0) # 3rank -> 4rank
    return tf.image.convert_image_dtype(x, tf.float32)

def show_tensor(tensors):
    for i, tensor in enumerate(tensors):
        ax = plt.subplot(1, len(tensors), i + 1)
        ax.imshow(tensor[0,:,:,0].numpy(), cmap="gray")
    plt.show()

膨張のモルフォロジー変換を行ってみましょう。

if __name__ == "__main__":
    base = load_tensor("j.png")
    x = dilation(base)
    show_tensor([base, x])

いい感じに太くなりました。

収縮(Erosion)の実装

逆に収縮のTensorFlowでの実装は次のようになります。符号を反転してMapPoolingを取るとMinPoolingになるのがポイント。

# 収縮
def erosion(tensor):
    # 5x5のMinフィルター
    return -tf.nn.max_pool2d(-tensor, 5, strides=1, padding="SAME")  # Min->マイナスを取ってMax
if __name__ == "__main__":
    base = load_tensor("j.png")
    x = erosion(base)
    show_tensor([base, x])

今度は線が細くなりました。

オープニング(Opening)

収縮→膨張としたものは、オープニング処理と呼ばれます。以下のような外側のノイズを取るのに適しています。

def opening(tensor):
    x = erosion(tensor)
    return dilation(x)

if __name__ == "__main__":
    base = load_tensor("outer_noise.png")
    x = opening(base)
    show_tensor([base, x])

クロージング(Closing)

オープニングと逆で、膨張→収縮としたものをクロージング処理と言います。内側のノイズを取ってみましょう。

def closing(tensor):
    x = dilation(tensor)
    return erosion(x)

if __name__ == "__main__":
    base = load_tensor("inner_noise.png")
    x = closing(base)
    show_tensor([base, x])

モルフォロジー勾配

膨張と収縮の差を取るもの。輪郭線の抽出の一方法としては便利だと思います(アニメから線画を自動で作成する一例にも似たような手法を使っています)。

def morphology_gradient(tensor):
    return dilation(tensor) - erosion(tensor)

if __name__ == "__main__":
    base = load_tensor("j.png")
    x = morphology_gradient(base)
    show_tensor([base, x])

まとめ

モルフォロジー変換は実はMaxPooling。符号を反転することでMinPoolingを再現可能で、Pooling1つでかなり多くの処理を再現できるということでした。ディープラーニングの関数がこんな使い方できるというのも面白いですね。

あとなぜtf.image.dilation2dではなく、MaxPoolingを使っているかというと、MaxPoolingなら大抵のデバイス(特にTPU)や型で実行できるからです。

Related Posts

Kerasで複数のラベル(出力)があるモデルを訓練する... Kerasで複数のラベル(出力)のあるモデルを訓練することを考えます。ここでの複数のラベルとは、あるラベルとそれに付随する情報が送られてきて、それを同時に損失関数で計算する例です。これを見ていきましょう。 問題設定 MNISTの分類で、ラベルが奇数のときだけ損失を評価し(categorical...
Pythonで画像のカラーヒストグラムを簡単に表示する方法... 画像で赤、緑、青の画素がどのような分布になっているかという「カラーヒストグラム」を見たいことがあります。しかしいざ探すとツールが少ないのです。Pythonならほんの数行で出せます。 PillowとPyplotでとてもお手軽 カラーヒストグラムの原理は単純で、縦横カラーチャンネルの画像を、カラー...
pix2pix HDのCoarse to fineジェネレーターを考える... pix2pix HDの論文を読んでいたら「Coarse to fineジェネレーター」という、低解像度→高解像度と解像度を分けて訓練するネットワークの工夫をしていました。pix2pixはGANですが、このジェネレーターや訓練の工夫は、Non-GANでも理屈上は使えるはずなので、この有効性をImag...
TPUでアップサンプリングする際にエラーを出さない方法... 画像処理をしているとUpsamplingが必要になることがあります。Keras/TensorFlowではUpsampling2Dというレイヤーを使ってアップサンプリングができますが、このレイヤーがTPUだとエラーを出すので解決法を探しました。自分でアップサンプリングレイヤーを定義するとうまく行った...
KerasのLambdaレイヤーの引数をループ内で変えるときにハマった話... KerasのLambdaレイヤーはとても便利で、自作の関数ほか、TensorFlowの組み込みの強力な関数もレイヤーとして扱うことができます。しかし、ループごとに引数を変えながらLambdaレイヤーでラップするようなケースでハマってしまいました。depth_to_spaceを例に見ていきます。 ...
Pocket
LINEで送る
Delicious にシェア

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です